深入理解C++模板
一.函数模板
1.模板的意义
假如我们要写一系列函数,用于比较两个数的大小,但是两个数的类型有很多种,可以是整形,浮点型,可以是字符类型,那么常规的方法,是为每一个类型都写一个函数,但是我们会发现,这很麻烦,并且进行比较的函数都十分的相似,只有参数列表不同。为了在这样的情况中简化程序,我们可以使用函数的模板,所谓函数的模板,实际上就是对函数的形参类型进行了参数化。
2.函数模板的实现
如图:我们实现支持多种类型的比较函数:
这里的typename也可以用class替换。
当我们要传入两个整形进行比较时可以通过compare<int>(10,20)(这里的compare实际上上一个模板,模板名加参数列表后才称为函数);来调用。
我们称这里的compare<int>(10,20)为函数的调用点,在函数的调用点,编译器用用户指定的类型,从原模板实例化一份函数代码出来(也就是说,在编译器的角度,仍然是编译多个模板函数(为compare的重载函数),编译的函数数量并未减少)。
但同时这里也可以直接使用compare(10,20)来调用,这是由于函数模板存在模板实参推演,可以通过用户传入的参数推导出来模板的类型。
但是如果我们这里compare(10,20.5)那么这里就无法正确推演出T的具体类型为什么,就会出现错误,但是这时如果我们使用compare<int>(10,20.5),这里就不会出现错误,会将double类型的数据强转为int类型。
由于函数模板不确定T的类型,所以函数模板不进行编译,编译的是模板函数。
我们再来看,假如我们要比较两个字符串,使用compare("aaa","bbb"),从结果上看确实能够进行比较,但是模板识别的T为const char* 这里比较使用>实际上是两个字符串的地址大小,明显与我们的期望不符,比较字符串我们应该使用strcmp等字符串比较函数,不能直接>。
面对这种情况,我们就要对字符串单独采用一个模板实现,即模板的特例化(专用化)如图:
如果我们再定义一个非模板函数(普通函数)也进行字符串的比较,那么编译器会优先调用非模板函数(更为高效)。
由于函数模板不进行编译,所以在多文件工程中,不能在一个文件中声明使用模板,在另一个文件中实现模板,但对于特例化的模板可以正常使用(因为模板的特例化,实际上生成了一个模板函数)所以一般模板都是定义在头文件中,在源文件中直接#include(##include导入的头文件直接在预编译阶段展开)。
当然,这种情况也是有办法解决的(不过并不推荐),我们可以在函数模板定义后,直接告诉编译器进行指定类型的模板实例化(如:templete bool compare<int>(int,int))。
模板非类型参数:我们可以在模板中定义非类型参数,不过只能使用整数类型(地址,引用也可),并且为常量。
二.类模板
如下:我们利用模板实现一个顺序栈:
template<typename T> class stack { private: vector<T>mpstack; public: stack() { } public: //入栈操作 void push(T val) { mpstack.push_back(val); } //出栈操作 void pop() { if (mpstack.empty()) { throw "mpstack is empty"; } mpstack.pop_back(); } //获取栈顶操作 int top()const { if (mpstack.empty()) { throw "mpstack is empty"; } return mpstack.back(); } //判断栈是否为空 bool empty()const { return mpstack.empty(); } };此时stack已经不在是一个类,而是一个类模板,此时的类名称为模板名称+类型参数列表。
同时,对于所有所有到类的地方,除了构造与析构函数不用加<>,其他地方最好都加上(虽然不加编译器会自动帮我们加)。
类模板会进行选择实例化,如果我们利用模板类定义了一个对象,编译器并不会将类中所有的成员方法都进行实例化,而是看我们调用了那些。
