1.泛型编程
如何实现一个通用的交换函数?这在C语言中是无法实现的,但是C++却可以。
C语言一次只能实现一个类型的交换:
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;
}
使用函数重载虽然可以实现,但是有一些问题:只要交换的类型不同,就需要增加对应的函数;重载的函数只是类型不同,代码复用率比较低;代码可维护性较低,一个函数出错所有重载都会出错。
解决这些问题的简单方法就是使用泛型编程。
泛型编程:编写与类型无关的通用代码,从而实现代码的复用。
像古代的活字印刷术,只要有了一个模板,就可以反复的造轮子;
C++中的模板可以分为函数模板和类模板。
2.函数模板
2.1 函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2.2函数模板格式
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
由于函数模板的模板参数类型可以有多个,所以这里使用T1, T2 … Tn来标识。
typename后面的类型名T1,T2……是随便取的,但是要尽量做到见名知意,一般首字母大写。
使用函数模板定义一个通用的交换函数:
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)。
2.3 函数模板的原理
下面从汇编语言看一下,当不同类型的变量调用Swap函数的时候调用的是不是同一份:
不同的类型编译器会推演出不同的函数,调用的也就是不同的函数。
交换函数使用的频率还是很高的,C++库(在std命名空间中)中有一个交换函数swap是可以直接使用的。
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);
//Add(a1, d1); 编译报错
return 0;
}
在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型。
int main()
{
int a = 10;
double b = 20.0;
Add<int>(a, b);
return 0;
}
2.5 模板参数的匹配原则
模板函数使得我们的代码更简洁,但是也有着匹配规则,下面看这样一段代码:
int Add(int left, int right)
{
return left + right;
}
template<class T>
T Add(T left, T right)
{
return left + right;
}
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
两个函数模板同时出现,还有一个处理int类型的函数,下面看一下调用规则:
Add(1, 2); // 与非模板函数匹配,编译器不需要实例化模板,直接调用
Add<int>(1, 2); // 显示实例化
Add(1, 2.0);//需要实例化两个类型参数的模板
Add(1.0, 2.0); //优先实例化一个类型参数的模板
3.类模板
相比于函数模板,类模板并没有特殊的地方,只是类模板只能显示实例化。
下面以一个栈(Stack)为例:
typedef int STDataType;
class Stack
{
private:
STDataType* _a;
int top;
int capacity;
};
数据类型是使用typedef进行重命名的,那么要想同时存储两种数据类型,还需要写一份完全一样的代码,只是类型不同;这样的话就很麻烦,所以使用到了类模板。
template<class T>
class Stack
{
private:
T* _a;
int top;
int capacity;
};
这样编译器就可以根据不同的类型进行显示实例化。
int main()
{
Stack<int> s1;
Stack<char> s2;
return 0;
}