前言
C++ 相较于 C 语言最核心的突破,在于引入了类和对象的面向对象编程范式,但这并非全部 —— 为了让代码更通用、更高效,C++ 还设计了模板这一核心特性。模板作为泛型编程的基石,能让我们摆脱'为每种数据类型重复写相同逻辑'的低效模式,只用一份代码就能适配多种类型,堪称 C++ 提升开发效率、实现代码复用的利器。
正文
一、泛型编程
简单来说,泛型编程就是让代码'通用化',写一份代码就能处理多种不同类型的数据,而不用为每种类型都写一遍重复代码。
下面来看不使用模板的代码:
// 这是 C++ 中利用函数重载后的交换函数
void Swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
void Swap(double& a, double& b) {
double tmp = a;
a = b;
b = tmp;
}
void Swap(char& a, char& b) {
char tmp = a;
a = b;
b = tmp;
}
我们很明显的能观察到这很麻烦,每有一种类型的交换,我们都得写一种函数来处理,甚至我们很难处理两种类型不同的对象进行交换,而 C++ 中的模板就能很好的处理这一情况。
模板有两种:一种是函数模板,另一种是类模板:

二、函数模板
- 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
- 函数模板的使用格式:
template<typename T1, typename T2, ..., typename Tn>
返回值类型 函数名 (参数列表) {}
- 例:
template <class T>
void Swap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
这里的 class 和 typename 一样,具有相同的作用,但是切记这里不能用 struct。
那么如何理解模板函数是什么东西呢?
可以认为模板就是一个函数的模具,将你想要的类型输入,他就能生成对应的函数:

以上是函数模板的格式,那么真实的函数要如何使用呢?
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
显式实例化
在使用函数的时候,在函数名的后面加 <类型>。
// 以交换函数为例:
template <class T>
void Swap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 1, b = 2;
cout << a << " " << b << endl;
Swap<int>(a, b);
cout << a << " " << b << endl;
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, a2); // ①
Add(a1, d2); // ②
return 0;
}
在上方代码中,①表达式编译器是可以识别的,②则不可以,因为编译器识别到了两个不同的类型,无法判断。
有读者会想到:double 不是可以隐式类型转换成 int 吗?但是实际上编译器在不知道用户想要的是哪种结果的情况下不会轻易的隐式类型转换,如果想要解决这种问题,就只能:① 用户自己显式转换;② 显式实例化。
三、模板参数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数:
// 通用加法函数
template <class T>
T Add(T left, T right) {
return left + right;
}
void Test() {
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的 Add 版本
}
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
// 专门处理 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(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
}
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
四、类模板
- 类模板的定义格式
template <class T1, class T2, ..., class Tn>
class 类模板名 {
// 类内成员定义
};
- 例:
// 类模版
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; // int
Stack<double> st2; // double
return 0;
}
- 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟 <>,然后将实例化的类型放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Stack 是类名,Stack<int>才是类型
Stack<int> st1; // int
Stack<double> st2; // double


