前言
主要讲述一下模板怎么用,以及注意事项让我们可以使用模板,后续细节会在模板进阶的时候讲到.
引
先用一下模板给大家看看吧.
template<typename T> void Swap(T& a, T& b) { T tmp = a; a = b; b = tmp; }
我们这个Swap函数就可以完成任何类型的调用(前提是a,b类型相同–原因后面会讲).如我们传两个int两个double我们的Swap函数都可以完美运行.来看看吧
这个功能的实现其实也是很简单的我们的编译器会根据传来的类型生成一个对应类型的函数,我们先简单看一下VS的反汇编代码吧.
看上面两个函数的指令我们就可以看出我们的Swap函数生产了两个.
而这就是模板的主要功能:
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本.
也就是说我们的模板可以无视类型来帮你生成一个相对应类型的函数.
很像auto关键字,但是auto无法作为函数参数,而我们的模板用于函数.
但是模板有他的使用规则.
模板
函数模板
模板使用:
template<typename T1, typename T2,......,typename Tn> TN 函数名(TN ..,T. .. , ...){} //返回值类型 函数名(参数列表){}
注意: 我们每创建一个模板函数都要在其上方写template<….>两者必须对应.
我们再看看我们上面的例子
template<class T> void Swap(T& a, T& b) { T tmp = a; a = b; b = tmp; }
如果我们还想再创建一个模板函数
如下
看右图我们的错误演示和左图的正确演示就可以看出了.我们每个模板函数都要与template<..>对应.
模板声明问题
我们大部分人呢,习惯于将声明放在.h的头文件里,把函数主体放在.cpp中再把main函数单独放在另一个.cpp文件中,但是模板函数不允许我们这样做.
来看看问题吧
结构如下(注:声明也是需要加上template<……>)
发生错误如下,这些是连接错误,其实原因也很简单
我们的test.cpp因为没办法接收到参数,所以test.cpp也就没办法在创建符号表的时候创建出对应名称的连接处,所以我们的main.cpp部分的文件其实就只是得到了一个声明仅此而已.
类似下面代码,报错也相同.
毕竟我们的头文件会展开的=.=
问题解决
所以我们如果非要声明让模板部分和main函数部分分隔开的话,我们建议是使用.hpp为后缀的头文件并把模板函数定义和声明都放在.hpp后缀的文件内.
其实原因也很简单因为我们的.hpp会在main.cpp哪里展开所以其实也就类似于我们没有分开.
但是如果还是想分成.h .cpp .cpp 的格式的话.
我们就可以选择实例化指定.
如下图:我们虽然还是.h .cpp .cpp的格式但是却可以正常运行
这样我们的test.cpp部分函数会因为下面的实例化指定在汇编的时候生成对应声明的实例化函数.
来看看linux的反汇编是否和我们推理相同呢?
是的通过objdump工具我可以看出我们的两种不同Swap函数(注:模板生成的函数的反汇编名字和普通函数反汇编名有所不同.)
(ps:如果想自己操作的话请转至[linux操作](# linux操作))
类模板
结构如下:
template<class T1, class T2, ..., class Tn> class 类模板名 { // 类内成员定义 };
事例如下:
// 动态顺序表 // 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具 template<class T> class Vector { public: Vector(size_t capacity = 10) : _pData(new T[capacity]) , _size(0) , _capacity(capacity) {} // 使用析构函数演示:在类中声明,在类外定义。 ~Vector(); void PushBack(const T& data); void PopBack(); // ... T& operator[](size_t pos) { assert(pos < _size); return _pData[pos]; } private: T* _pData; size_t _size; size_t _capacity; }; // 注意:类模板中函数放在类外进行定义时,需要加模板参数列表 template <class T> Vector<T>::~Vector() { if (_pData) delete[] _pData; _size = _capacity = 0; }
注意我们上面的类只是一个模板我们实例化格式如下
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
其实在使用的时候也就是在类名和对象名之间加个<类型>.
模板使用实例
先看看函数模板的使用吧.
如果我们想两个不同类型的做函数形参要这么办呢?
很简单如下:
我们可以创建个两个模板参数的模板.
#include<iostream> using namespace std; template <typename T1, typename T2> void Test(T1 a, T2 b) { return; } int main() { Test(1, 1.0); return 0; }
模板参数也可以当做函数的返回值.
而且我们可以给我们的模板参数赋予缺省值
赋值方式如下
template <typename T1 = char, typename T2 = double>
我们也可以在使用的时候显示实例化来创建一个指定类型的模板实例化.
#include<iostream> using namespace std; template <typename T1 = char, typename T2 = double> void Test(T1 a, T2 b) { return; } int main() { Test<int, double>(1, 1.0);//其中<>里的两个类型会分别传给T1和T2 return 0; }
如果我们定义了一个和模板功能名称相同的函数会发生什么呢?
代码如下
#include<iostream> using namespace std; int Add(int a, int b) { return a + b; } template <typename T> T Add(T& a, T& b) { return a + b; } int main() { int a = 1; int b = 2; Add<int>(a, b); Add(a, b); return 0; }
让我们到linux里看看
虽然在我们的印象里他们是相同的但是其实在反汇编后的命名还是不相同的.所以两者可以共存.
linux操作
创建两个文件一个是test.cpp 一个是main.cpp 然后把代码放入
//test.cpp #define _CRT_SECURE_NO_WARNINGS 1 template<class T> void Swap(T& a, T& b); template<class T> void Swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } template void Swap<int>(int& a, int& b); template void Swap<double>(double& a, double& b); //main.cpp #include<iostream> using namespace std; template<class T> void Swap(T& a, T& b); int main() { int a = 1; int b = 2; Swap(a, b); double c = 1.2; double d = 2.1; Swap(c, d); cout << "a>: " << a << endl << "b>: " << b << endl << "c>: " << c << endl << "d>: " << d << endl; return 0; }
保存退出后
在命令行分别写下如下指令
g++ test.cpp main.cpp -o TEST
objdump -S TEST
就可以查找到Swap的两个函数了