PoEdu培训 STL班 第二课 模板编程(二)
文章类别: 培训笔记 0 评论

PoEdu培训 STL班 第二课 模板编程(二)

文章类别: 培训笔记 0 评论

STL(2)

知识巩固

以下整个函数, 我们称之为_模板函数_

        // 模板参数
template <typename T>   // 函数模板
const T& Max()
{
}

模板的实例化

  1. 检查语法错误, 是在模板函数的层面来检测, 比如缺少分号....

    • 确保当前的模板函数是正确的, 是没有语法错误的.
    • 如果检测失败, 则不进行第二步, 停止编译.
  2. 检查调用错误

    • 在通过了语法检测之后, 生成了对应的函数, 然后进行调用检查.
// 检查调用错误
template <typename T>   
const T Max(T lhs, T rhs)
{
    return lhs > rhs ? lhs : rhs;
}

class Test{}

int main()
{
    Max(1, 2);  // OK
    Test t1, t2;
    Max(t1, t2);    // 错误, 因为Test没有重载 > 操作符, 这就是调用检查发现的错误
    return 0;
}

模板的声明和实现分离

模板的声明和实现分离的时候, 会在链接的时候出现找不到符号引用的错误.
这是为什么?

编译器在编译模板的.h文件时,只是读到了函数模板的实现,
并没有读到任何需要生成函数模板实例的语句,所以不会生成任何模板函数的实例。
而在编译调用方cpp时(比如main.cpp),虽然用到了一个函数模板实例,
但因为main.cpp只是将模板的.h头文件包含进来,而后者只有一个函数模板的声明,
并无具体函数体实现,此时编译器也无法生成函数模板实例,只好预留一个调用链接,
期望在最后的链接过程中可以找到函数实现。
但很遗憾这样的实现并不存在,于是最后链接时出错,找不到符号。

怎么解决呢?

模板函数的重载

以下模板函数能够构成重载

template <typename T>
const T Max(T lhs, T rhs)
{
    return lhs > rhs ? lhs : rhs;
}

// 模板参数列表不同
template <typename T, int num>  // 注意, num 不要有默认实参, 否则容易出现二义性
const T Max(T lhs, T rhs)
{
    return lhs > rhs ? lhs : rhs;
}

// 模板参数列表不同, 函数参数列表不同
template <typename T1, typename T2>
const T1 Max(T1 lhs, T2 rhs)
{
    // ....
}

// 模板参数列表不同
template <typename T1, typename T3>     // 这句话是错误的 参见备注1
const T1 Max(T1 lhs, T1 rhs)
{
    // ....
}

// 函数参数列表不同
template <typename T>
const T Max(T lhs, T rhs, T other)
{
    // ....
}

// 普通函数
const int Max(int lhs, int rhs)
{
    // ...
}

构成重载的条件:

备注1: 模板参数列表中的元素必须在函数参数列表中使用一次
因为只有使用了一次才能使用自动推导.

小扩展:

template <typename T>
const T Max(int lhs, int rhs)
{
    return lhs > rhs ? lhs : rhs;
}

int main()
{
    double dRet = Max<double>(1, 2);
    return 0;
}

在不使用自动推导的情况下, 模板参数列表中的元素可以不出现在函数的参数列表中.
以上例子将返回一个double的值.
这属于函数模板的自由性

模板参数列表的值必须是常量

template <typename T, int num>
const T Max(T lhs, T rhs)
{
    return lhs > rhs ? lhs : rhs;
}

int main()
{
    int num = 10;
    const int numC = 100;
    Max<int, num>(1, 2);    // 错误, 必须传递常量值
    Max<int, 10>(1, 2);
    Max<int, numC>(1, 2);   // 正确
    return 0;
}

我们的模板在实例化的时候, 是在编译时进行的, 必须知道值是多少
所以我们的函数模板中的类型, _不能_是_指针_或者_引用_这种_运行时才能确定_的类型

函数模板的特化

template <typename T>
T Max(T lhs, T rhs)
{
    return lhs > rhs ? lhs : rhs;
}

// 函数模板的特化
template <>
const char* Max(const char* lhs, const char* rhs)
{
    return strcmp(lhs, rhs) > 0 ? lhs : rhs;
}

// 重载函数
// const char* Max(const char* lhs, const char* rhs)
// {
//     return strcmp(lhs, rhs) > 0 ? lhs : rhs;
// }

int main()
{
    Max(1, 2);
    Max("a", "b");  // 会调用特化的函数
    return 0;
}

虽然利用函数重载可以实现我们的功能, 但是性质不同.
函数模板的特化是我们模板函数中一系列函数中的一个.
当函数模板的 T == const char* 的时候, 你就可以不生成了
如果通篇没有使用到特化函数, 它是不会生成的.

调用规则:

  1. 当我们有一个非模板函数并且参数符合时(含有提升转换), 会首先调用非模板函数.
  2. 然后寻找特化函数, 此处无提升转换.
  3. 然后在去调用我们 模板函数 生成的函数.

模板类和类模板

// 以下Demo类我们称之为 模板类

template <typename T>   // 类模板
class Demo
{
public:
    Demo(int num):num_(num) {}
private:
    int num_;
};

类模板是无法被推导的, 这是和函数模板的不同
模板类可以不使用类模板中的类型T, 因为类模板是无法被推导的, 使用时必须显示指定.

继承

模板类是可以被继承的

template <typename T>
class Array
{
private:
    int num_;
};

class IntVector : public Array<int>
{

};

template <typename T>
class IntArray : public Array<T>
{

};

此时, 我们的继承了Array<int>, 我们的IntVector并不是一个模板类, 它继承了我们模板类的某个指定类型的实例.
我们经常拿来做某些特定的适配器.

适配器


template <typename T, typename CON = std::vector<T> >   // 注意> >中间加空格, 防止编译器出错
class stack
{
private:
    CON data_;
public:
    void PushBack(T t)
    {
        data_.push_back(t);
    }
};

int main()
{
    stack<int> demo;
    stack<int, std::deque<int> > demo1;
    stack<int, std::map<int, int> > demo2;
    return 0;
}

我们的stack就是一个适配器.

未完待续...

如有错误,请提出指正!谢谢.

回复