泛型编程基础
在 C++ 开发中,我们经常遇到需要处理不同类型数据的场景。比如交换两个变量的值,如果分别写 int、double、char 的函数重载,代码量会迅速膨胀且难以维护。
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;
}
这种重复劳动正是模板存在的意义。就像模具一样,填入不同的类型参数,编译器就能生成具体的代码。泛型编程就是编写与类型无关的通用代码,而模板是它的基石。
一、函数模板
1. 格式与原理
函数模板的基本结构如下:
template<typename T1, typename T2, ..., typename Tn>
返回类型 函数名 (参数列表)
{
// 函数体
}
注意,typename 是定义模板参数的关键字,这里也可以用 class 替代,但不能用 struct。模板参数的作用域紧跟在 {} 之前。
template<typename T> // 模板参数 -- 类型
void Swap(T& left, T& right) {
T temp = left;
left = right;
right = temp;
}
template<typename T1, typename T2>
T1 Func(const T1& x, const T2& y) {
cout << x << " " << y << endl;
return x;
}
在实际调用时,我们不需要关心具体类型,直接传参即可:
int main() {
int a = 0, b = 1;
double c = 1.1, d = 2.2;
Swap(a, b);
Swap(c, d);
// Date d1(1949, 10, 1), d2(2015, 7, 3);
Func(1, 2);
Func(1, 2.2);
return 0;
}
底层机制上,调用的是模板实例化出的函数。简单来说,就是把本该由我们手动编写的重复工作交给了编译器去处理。

其实 C++ 标准库已经内置了这些功能(如 std::swap),通常不需要自己手写,直接使用即可。
int main() {
int a = 0, b = 1;
double c = 1.1, d = 2.2;
swap(a, b); // 使用标准库
swap(c, d);
return 0;
}
2. 函数模板的实例化
模板不会立即生成代码,只有在被调用时才会实例化。主要有两种方式:
- 隐式实例化:编译器根据实参自动推演模板参数的实际类型。
- 显示实例化:在函数名后的
< >中显式指定模板参数的实际类型。
template<typename T>
T Add(const T& left, const T& right) {
return left + right;
}
template<typename T>
T* Alloc(int n) {
return new T[n];
}
int main() {
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
cout << Add(a1, a2) << endl;
cout << Add(d1, d2) << endl;
// 以下报错:编译器无法确定 T 的类型
// cout << Add(a1, d1) << endl;
// 解决方案 1: 用户强制转换
// 注意:Add 参数加了 const,因为类型转换会产生临时变量
cout << Add(a1, (int)d1) << endl;
cout << Add((double)a1, d1) << endl;
// 解决方案 2: 显示实例化
cout << Add<int>(a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
// 有些函数无法自动推演,只能显示实例化
double* p1 = Alloc<double>(10);
return 0;
}
二、类模板
类模板的定义与普通类类似,但类名和类型并不完全一致。例如普通类叫 Stack,而类模板实例化后是 Stack<T>。
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
关键点:构造函数不一定能利用模板参数进行推演,因此类模板通常都需要显示实例化。
template<class T>
class Stack {
public:
Stack(int capacity = 4) {
_a = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data) {
// CheckCapacity();
_a[_size] = data;
_size++;
}
~Stack() {
if (_a) {
delete[] _a; // 修正:new[] 对应 delete[]
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
T* _a;
int _capacity;
int _size;
};
int main() {
Stack<int> s1; // 正确
// Stack<double> s2; // 正确
// Stack<char> s3; // 正确
// Stack<int, double> s4; // 多个模板参数需同时指定
return 0;
}
关于类模板的声明和定义分离:虽然可以分开写,但最好不要将声明和定义拆分到两个文件中,否则容易引发编译错误(这涉及到模板实例化的时机问题,后续章节会深入讲解)。
模板是 C++ 泛型编程的核心工具,理解其工作原理对于编写高效、通用的组件至关重要。掌握实例化机制和内存管理细节,能让你在实战中避免许多潜在的坑。

