C++第四讲:模板初阶
1.泛型编程2.函数模板2.1什么是函数模板2.2函数模板的使用2.3函数模板的原理2.4函数模板的实例化2.4.1隐式实例化2.4.2显式实例化 2.5模板参数的匹配原则2.5.1原则12.5.2原则22.5.3原则3 3.类模板3.1类模板的定义格式3.2类模板的实例化
1.泛型编程
如果我们要实现一个对于所有类型都能够使用的交换函数,我们应该如何实现?
对于不知道泛型编程的人来说,应该会这样写:
void Swap(int& left, int& right){int temp = left;left = right;right = temp;}void Swap(double& left, double& right){double temp = left;left = right;right = temp;}void Swap(char& left, char& right){char temp = left;left = right;right = temp;}
但是观察可以发现,这三个函数仅仅只是函数参数不同,函数内容没有任何不同的地方,所以,就推出了泛型编程,其实就是给出一个函数模板,通过不同的类型生成不同的函数
2.函数模板
2.1什么是函数模板
函数模板代表了一个函数家族,函数模板与类型无关,在使用是被参数化,根据实参类型产生函数的特定类型版本
2.2函数模板的使用
//函数模板在使用之前都需要加上关键字:template<typename T>//也可以使用class,这样就变成了:template<class T>template<typename T>//template<class T>//然后就可以像函数那样,只需要将类型改为T即可(T为type,也可以起别的名字,一般起T)void Swap(T& x, T& y){T tmp = x;x = y;y = tmp;}int main(){int a = 10;int b = 20;Swap(a, b);double c = 1.1;double d = 2.2;Swap(c, d);return 0;}
调用的函数如下(可以看出,实例化出的函数名称竟然也是不相同的!):
但是,要注意的是,当在一个函数中使用模板参数T时,T的作用域仅限于该函数的模板定义,当再次定义一个函数时,尽管这个函数也是模板函数,需要再次声明模板参数T:
template<typename T>void func1(T param){//在这里,T时func1的模板参数}//需要重新声明:template<typename T>void func2(T param){//在这里,T需要被重新声明,因为它是func2的模板参数}
如果我们想要将两种不同的参数进行交换,那么就需要设置两个模板参数来使用:
//设置两个不同的模板参数template<typename T1, typename T2>void Swap(T1& x, T2& y){T1 tmp = x;x = y;y = tmp;}int main(){int a = 10;int b = 20;Swap(a, b);double c = 1.1;double d = 2.2;Swap(c, d);//当我们要交换两个不同的参数时,需要设置两个不同的模板参数cout << "a -> " << a << endl;cout << "c -> " << c << endl;Swap(a, c);//但是需要注意的是:这里会丢失精度cout << "a -> " << a << endl;cout << "c -> " << c << endl;return 0;}
2.3函数模板的原理
知道了函数模板的使用之后,我们应该会有疑问:函数模板的原理是什么?不同类型的参数难道使用的是同一个函数吗?当然不是,int和double类型占用的字节数都不同,肯定不能使用同一个函数,那么函数模板的原理究竟是什么呢?
函数模板,我们可以看成是一个模板,它本身并不是函数,而是编译器通过传入的实参类型进行推演,使用该类型又生成了一个函数,比如,当double类型使用函数模板时,在编译阶段,编译器通过对实参类型的推演,将T确定为double型,然后产生一份专门处理double类型的代码,其它类型的实现也是如此
2.4函数模板的实例化
用不同类型的参数使用函数模板时,称为模板的实例化,其实也就是编译器根据提供的类型参数创建特定类型版本的函数的过程,模板参数实例化分为:隐式实例化和显式实例化
2.4.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;}
如果我们此时想要将int类型和double类型的数据相加,就会出现问题,改正方法:
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, d1);//err:没有与参数列表匹配的函数模板Add实例//解决方式1:强制类型转换Add(a1, (int)d1);//解决方式2:使用显式实例化Add<int>(a1, d1);Add<double>(a1, d1);return 0;}
2.4.2显式实例化
在函数名后的<>中指定模板参数的实际类型即可
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<int>(a1, d1);Add<double>(a1, d1);return 0;}
如果类型不匹配,编译器会尝试隐式类型转换,如果无法成功转换,编译器会报错
2.5模板参数的匹配原则
2.5.1原则1
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数,但是如果没有显式实例化,编译器会使用那个非模板函数
//专门处理int的加法函数,非模板函数int Add(int left, int right){return left + right;}//模板函数template<class T>T Add(T left, T right){return left + right;}void Test(){Add(1, 2); //与非模板函数匹配,编译器不需要特化Add<int>(1, 2); //使用模板函数方法}
2.5.2原则2
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。但是如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
//专门处理int的加法函数,非模板函数int Add(int left, int right){return left + right;}//模板函数template<class T1, class T2>T1 Add(T1 left, T2 right){return left + right;}void Test(){Add(1, 2); //与非模板函数匹配,编译器不需要特化Add(1, 2.0); //如果模板函数可以产生更加匹配的版本,编译器会根据实参生成更加匹配的Add函数被调用}
2.5.3原则3
模板函数不允许自动类型转换,但普通函数可以自动类型转换
3.类模板
3.1类模板的定义格式
template<class T1, class T2, ..., class Tn>class 类模板名{// 类内成员定义};
我们之前实现的栈,需要typedef DataType int(或者using DataType = int)对类型进行限制,但是如果我们想要同时创建存储int类型和double类型数据的栈时就需要重新设计一个栈,这是非常麻烦的,这里,类模板就会显得十分有用了:
//类模版template<typename T>class Stack{public:Stack(size_t capacity = 4){_array = new T[capacity];_capacity = capacity;_size = 0;}void Push(const T& data);private:T* _array;size_t _capacity;size_t _size;};//模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误,具体原因后面会讲template<class T>void Stack<T>::Push(const T& data){//扩容_array[_size] = data;++_size;}int main(){Stack<int> st1; //intStack<double> st2; //doublereturn 0;}
这里要注意的是:这时的类名并不是类型名,所以此时要创建一个栈变量是就要注意<>的出现
3.2类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
//Stack是类名,Stack<int>才是类型Stack<int> st1; //intStack<double> st2; //doubleStack st3;//err:st3:未知的大小