C++模板初识
- 1.泛型编程
- 2.函数模板
- 2.1.函数模板概念
- 2.2.函数模板格式
- 2.3.函数模板使用的原理
- 2.4.函数模板的实例化
- 2.5.模板参数的匹配原则
- 3.类模板
- 3.1.类模板格式
- 3.2.类模板的实例化
1.泛型编程
在实际编程中,经常会用到交换函数。比如有整型值的交换,浮点型值的交换,字符型值的交换…
像下面代码这样:
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
void Swap(double& x, double& y)
{
double tmp = x;
x = y;
y = tmp;
}
void Swap(char& x, char& y)
{
char tmp = x;
x = y;
y = tmp;
}
但是,难道对于每一种类型不同的值的交换,我们都要重新写一个对应的交换函数吗?这样的话,代码的复用率和可维护性都会降低。
可以发现,上面的交换代码,仅仅是类型上有所区别,但在交换的逻辑上都是一致的。所以我们能否给编译器声明一个模具,让编译器根据这个模具来对不同类型的数据进行相同逻辑的操作呢。
这就是要提的泛型编程了。在C++中模板又是泛型编程的基础。而模板又分为函数模板和类模板,下面来依次介绍。
2.函数模板
2.1.函数模板概念
简单说,函数模板就是一系列函数的模具,编译器会在实际使用时根据传递的参数类型对函数模板进行实例化,来产生特定类型的函数。
2.2.函数模板格式
template<typename T1, typename T2, ..., typename Tn>
返回值类型 函数名(){}
例如:
template<typename T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
这里typename
是用来定义模板参数的关键字,同时也可以是用class
替代。
T
代表一个模板类型(也叫虚拟类型)。
2.3.函数模板使用的原理
模板的使用其实是将本来我们要做的重复的工作交给了编译器来处理。
编译器在程序编译阶段,会根据传入的参数类型来自动生成对应类型的函数以供程序调用。
当传递int
类型的参数时,编译器通过对参数类型的推演,将T
确定为int
类型,然后会产生一份专门用于处理int
类型的代码。当然对于浮点型和字符型也是同样的道理。
2.4.函数模板的实例化
用不同类型的参数使用函数模板时,就称为函数模板的实例化。函数模板的实例化又分为两种:隐式实例化和显式实例化。
- 隐式实例化:让编译器根据参数类型来推演模板参数的实际类型。
template<class T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int i1 = 1;
double d1 = 1.1;
Add(i1, d1); // error
}
上面的代码在编译期间,编译器在对函数模板进行实例化时,会进行参数类型的推演。通过实参i1
将T
推演为int
类型,通过实参d1
将T
推演为double
类型。但是该模板参数列表只有 1 个T
,编译器此时会因无法确定将T
确定为int
类型还是double
类型而报错。
上述问题有两种处理方式:
一个是使用强制类型转化来解决。
Add((double)i1, d1);
Add(i1, (int)d1);
还可以使用函数模板的显式实例化来解决。
2. 显示实例化:在函数名之后用< >
指定模板参数的实际类型
Add<int>(i1, d1);
Add<double>(i1, d1);
显示实例化中,参数传递如果类型不匹配,编译器会尝试进行隐式类型转换。
2.5.模板参数的匹配原则
当一个普通函数和一个同名的函数模板同时出现时,而且该普通函数还可以通过函数模板被实例化出来。那么在函数调用时程序会优先调用普通函数而不会从模板中产生实例。但是当模板可以产生一个匹配得更好的函数时,那么还是会选择函数模板的实例化。
int Add(int x, int y)
{
return x + y;
}
template<class T1, class T2>
T2 Add(T1 x, T2 y)
{
return x + y;
}
int main()
{
Add(1, 2); // 与普通函数完全匹配,不需要函数模板实例化
Add(1, 2.2); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
return 0;
}
需要注意的是:在传参时,模板函数不允许自动类型转换,而普通函数可以进行自动类型转换。
3.类模板
3.1.类模板格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
模板不支持分离编译,即不支持声明放在.h
文件,定义放在.cpp
文件。所以类模板中成员函数的定义必须和声明在一起,但这样都定义在类的内部,可能会形成内联函数。如果不希望这样,也是可以将声明和定义分离,但要在同一个文件中,因为模板在同一个文件中是可以声明和定义分离的。
3.2.类模板的实例化
类模板必须使用显式实例化。
vector<int> v1;
vector<double> v2;
vector<char> v3;