C++ 模板

一、引言

C++ 通过函数重载可以实现对不同数据类型完成同一功能,但是这样写重复的代码就显得很臃肿,且代码的复用率较低,每当出现一个 新类型,就需要增加对应的函数。

由此,引入了模板。所谓模板,顾名思义就是一个通用的描述。也就是使用泛型来定义函数,就是编写与类型无关的代码,其中泛型可通过具体的类型来(如 int 或 double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的的函数。

二、函数模板

2.1 函数模板格式

函数模板的基本格式如下:

1
2
3
4
5
6
7
template<typename T1, typename T2, ... , typename Tn>
return_type function_name(argument-list)
{

}
// 在标准 C++98 添加关键字 typename 之前,C++ 使用关键字 class 来创建模板,也就是可以写做 template<class T1>
// 这里用 typename 和 class 是等价的,但不能使用 struct

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template <typename anyType>
void Swap(anyType &a, anyType &b)
{
anyType temp;
temp = a;
a = b;
b = temp;
}

int main()
{
int i = 10, j = 20;
Swap(i, j);
cout << "i:" << i << " j:" << j << endl;

double a = 3.14, b = 2.718;
Swap(a, b);
cout << "a:" << a << " b:" << b << endl;

return 0;
}

// i:20 j:10
// a:2.718 b:3.14

模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换 int 的函数时,编译器将按模板模式创建这样的函数,并用 int 代替 anyType。

这就是相当于创建了两个函数 void Swap(int &a, int &b)void Swap(double &a, double &b)

2.2 实例化

隐式实例化、显式实例化和显式具体化统称为具体化(specialization)。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。

2.2.1 隐式实例化

隐式实例化就是让编译器根据实参推演模板参数的实际类型。就像上面的例子那样,只管调用 Swap(i, j) 即可,具体的 i 和 j 的类型由编译器自行推导。

但若是这样调用:Swap(i, a) 编译就会报错,因为编译器无法辨认这里的 ansType 到底是 int 类型还是 double 类型。一个方法就是我们自己来强制转换,另一个方法就是下面要提到的显示实例化。

2.2.2 显式实例化

显示实例化就是在函数名后的 <>中指定模板参数的实际类型,例如:

1
2
Swap<int>(i, j);
Swap<double>(a, b);
1
2
3
4
5
6
7
8
9
10
template <class T>
T Add(T a, T b)
{
return a + b;
}

...
int m = 6;
double x = 10.2;
cout << Add<int>(m, x) << endl;

这里会将 x 强制转换为 int 类型。那如果对 Swap 做类似的处理呢?

这里如果调用 Swap<int>(i, a) 则会报错。注意,这里的形参类型为 double &,所以不能指向 int 类型的变量 i。

2.2.3 显示具体化(特化)

模板是一种泛型技术,它能接受的类型是宽泛的、没有限制的,并且对这些类型使用的算法都是一样的(函数体或类体一样)。但是现在我们希望改变这种“游戏规则”,让模板能够针对某种具体的类型使用不同的算法(函数体或类体不同),这在 C++ 中是可以做到的,这种技术称为模板的显示具体化(Explicit Specialization)

下面看一个例子就明白了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct Job {
double salary;
int floor;
};

template <typename anyType>
void Swap(anyType &a, anyType &b)
{
anyType temp;
temp = a;
a = b;
b = temp;
}

template<> void Swap<Job>(Job &a, Job &b)
{
double t1;
t1 = a.salary;
a.salary = b.salary;
b.salary = t1;

int t2;
t2 = a.floor;
a.floor = b.floor;
b.floor = t2;
}

int main() {
int i = 10, j = 20;
Swap(i, j);
cout << "i:" << i << " j:" << j << endl;

Job a = { 5.0, 1}, b = { 6.0, 2};
Swap(a, b);
printf("a.salary: %lf, a.floor: %d\n", a.salary, a.floor);
printf("b.salary: %lf, b.floor: %d\n", b.salary, b.floor);

return 0;
}

显示具体化有下面两种写法,它们是等价的:

1
2
template<> void Swap<Job>(Job &a, Job &b);
template<> void Swap(Job &a, Job &b); // 因为函数的形参已经表明,这是 Job 类型的一个具体化,编译器能够逆推出 T 的具体类型。所以函数声明可以简写

它和显式实例化的区别在于,这些声明的意思是“不要使用 Swap() 模板来生成函数定义,而应使用专门为 Job 类型显式得定义的函数定义“。这些原型必须有自己的函数定义。显式具体化声明在关键字 template 后包含 <>,而显式实例化没有。

在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错

2.3 模板参数的匹配规则

对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析(overloading resolution)。大致过程如下。

  1. 创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数
  2. 使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用 float 参数的函数调用可以将该参数转换为 double,从而与 double 形参匹配,而模板可以为 float 生成一个实例
  3. 确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错

参数匹配过程中,从最佳到最差的顺序如下所述:

  1. 完全匹配,但常规函数优先于模板
  2. 提升转换(例如,char 和 short 自动转换为int,float 自动转换为 double)
  3. 标准转换(例如,int 转换为 char,long 转换为 double)
  4. 用户定义的转换,如类声明中定义的转换

其中完全匹配允许的无关紧要转换:

从实参 到形参
Type Type &
Type & Type
Type [] * Type
Type (argument-list) Type (*) (argument-list)
Type const Type
Type volatile Type
Type * cosnt Type
Type * volatile Type *

三、类模板


C++ 模板
http://example.com/2026/04/14/C++-模板/
作者
Yu xin
发布于
2026年4月14日
许可协议