一、本章介绍
1、泛型编程
2、函数模板
3、类模板
二、泛型编程
什么是泛型编程?不知道你有没有经历这样的情景:你需要写一个交换函数,可以交换int、double、char、自定义类型。那么这对于C语言来说,就得专门写一个int交换函数、再写一个double交换函数、再写一个char交换函数。同时C语言还不支持重载,你还得写不一样的函数名。那如果我想写一个支持任意类型的交换函数,我们就不得不学习泛型编程。简单来说,泛型编程就是编写与类型无关的通用性代码,而模板就是泛型编程的基础。
三、函数模板
先写一个交换函数的模板
template<class T> void Swap(T& a, T& b) { T temp = a; a = b; b = temp; }
其中<class T>中的class可以换成typename,这两者在这里的使用没有区别,具体区别在迭代器那章会介绍。
需要注意的是,函数模板与具体的函数是有区别的,函数模板负责的是:你使用一个Swap<int>(a,b),它就会实例化一份Swap(int& a,int& b)。同样的,你使用一个Swap<double>(a,b),它就会实例化一份Swap(double& a,double& b),也就是说,编译器负责了跟多的工作,我们只需要传对应的参数,编译器就会依照函数模板自动实例化出你需要的函数。
3.1函数模板的实例化
函数模板的实例化分两种:
一、隐式实例化:让编译器根据实参推演模板参数的实际类型
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2); Add(d1, d2); return 0; }
像普通函数一样使用,编译器会根据你传参的类型实例化出对应的函数。
如果传的类型有矛盾,编译器会报错,如下:
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, d2); return 0; }
因为a1是int类型,d2是double类型,编译器无法推出T是什么类型,如果把T实例化为nt,那么d2传过来会发生隐式类型转换,编译器不允许这样的事情发生,因此直接报错。
解决办法1:
int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, (int)d2); return 0; }
解决办法2:
使用显示实例化
int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add<int>(a1, d2); return 0; }
二、显式实例化:在函数名后的 <> 中指定模板参数的实际类型int main() { int a = 1; double b = 1.1; Add<int>(a, b); return 0; }
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。~提问环节~问一:如果同时存在普通函数和函数模板,使用时会选择哪个呢?template<class T> void Add(const T& left, const T& right) { cout << "T Add(const T& left, const T& right)" << endl; } void Add(int& a, int& b) { cout << "int Add(int& a, int& b)" << endl; } int main() { int a = 0; int b = 5; Add(a, b); return 0; }
如果有现成的函数,编译器就不会实例化了,而是直接调用这个现成的函数。
问二:下面这段代码,该输出什么?template<class T1,class T2> void Add(const T1& left, const T2& right) { cout << "void Add(const T1& left, const T2& right)" << endl; } void Add(int& a, int& b) { cout << "int Add(int& a, int& b)" << endl; } int main() { int a = 0; double b = 5.5; Add(a, b); return 0; }
由于模板函数可以生成更加匹配的版本,因此编译器根据实参实例化出更加匹配的Add函数。
这里再次说明一下:模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
四、类模板
我们用C语言写一个顺序表的时候,通常用改变typedef int DateType来改变存储的类型。
但如果我们想一个顺序表存int,另一个顺序表存double,这个时候我们就不得不写两个存不同类型的顺序表了,但这样写了太多重复的代码了,这对于代码人来说是不好忍受的。因此我们需要学习类模板,站在底层的角度就是让编译帮我们写重复的代码,我们只需要写一个类模板出来,想实例化存什么类型的类,直接用seqlist<int>、seqlist<double>即可。
4.1类模板的定义格式
template<class T1, class T2, ..., class Tn> class 类模板名 { // 类内成员定义 };
template<class T> class Seqlist { public: Seqlist() { //构造函数 } ~Seqlist() { //析构函数 } private: T* _a; size_t _size; size_t _capacity; }; int main() { Seqlist<int> s1;//存int的顺序表 Seqlist<double> s2;//存double的顺序表 return 0; }
需要注意的是Seqlist不是类型,因为它还没实例化,只能说是类名,Seqlist<int>才是类型。