STL(2)
知识巩固
- 泛型编程的出现能够使我们节省大量的代码量, 它是一种生成代码的代码.
- 通过函数模板写的模板函数,在实例化(编译的时候, 非运行时)的时候生成了很多的代码
多态是运行时的宏是预编译时进行替换, 它没有类型检测
以下整个函数, 我们称之为_模板函数_
// 模板参数
template <typename T> // 函数模板
const T& Max()
{
}模板的实例化
- 模板在实例化时有两个步骤
检查语法错误, 是在模板函数的层面来检测, 比如缺少分号....
- 确保当前的模板函数是正确的, 是没有语法错误的.
- 如果检测失败, 则不进行第二步, 停止编译.
检查调用错误
- 在通过了语法检测之后,
生成了对应的函数, 然后进行调用检查.
- 在通过了语法检测之后,
// 检查调用错误
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头文件包含进来,而后者只有一个函数模板的声明,
并无具体函数体实现,此时编译器也无法生成函数模板实例,只好预留一个调用链接,
期望在最后的链接过程中可以找到函数实现。
但很遗憾这样的实现并不存在,于是最后链接时出错,找不到符号。怎么解决呢?
- 不进行分离, 全部写在头文件中
伪分离(自我安慰)
- 在.h的最后加入一句
#include "xxx.cpp". 需要注意的是, cpp文件中不能有#include "xxx.h"语句.
- 在.h的最后加入一句
- 直接include我们模板的实现cpp文件
显示实例化
- 实现写在cpp文件中, 在cpp的末尾, 使用template class语法进行显式实例化
分离模型- 使用C++ export关键字声明导出
- 可惜, 实际从C++标准提出之后主流编译器没有支持过,并且在最新的C++11标准中已经废除此特性,export关键字保留待用
模板函数的重载
以下模板函数能够构成重载
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* 的时候, 你就可以不生成了
如果通篇没有使用到特化函数, 它是不会生成的.
调用规则:
- 当我们有一个非模板函数并且参数符合时(含有提升转换), 会首先调用非模板函数.
- 然后寻找
特化函数, 此处无提升转换. - 然后在去调用我们 模板函数 生成的函数.
模板类和类模板
// 以下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就是一个适配器.
未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-03-07 at 04:44 pm