写 C++ 难免跟模板打交道。它是泛型编程的基石,让你写出类型无关的代码,代价为零运行时开销——因为实例化在编译期就完成了。
从重载到模板
考虑一个求最大值的函数,想支持 int、double 和 string。用函数重载的话,每个类型都要写一份类似的逻辑。
#include <iostream>
#include <string>
using namespace std;
int max(int a, int b) {
cout << "int version\n";
return a > b ? a : b;
}
double max(double a, double b) {
cout << "double version\n";
return a > b ? a : b;
}
string max(const string& a, const string& b) {
cout << "string version\n";
return a > b ? a : b;
}
int main() {
cout << max(3, 5) << "\n";
cout << max(3.14, 2.7) << "\n";
cout << max("hello", "world") << "\n";
return 0;
}
每次增加新类型就得添一个重载,改算法还得动所有版本。下面用模板来统一处理:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
T max(T a, T b) {
cout << "template version for " << typeid(T).name() << "\n";
return a > b ? a : b;
}
int main() {
cout << max(3, 5) << "\n";
cout << max(3.14, 2.7) << "\n";
cout << max("hello", "world") << "\n";
cout << max('a', 'b') << "\n";
return 0;
}
一份代码覆盖所有支持 > 的类型,编译器为每种实际类型生成具体的函数,效率跟手写重载一样。
函数模板
模板定义以关键词 template 开头,后面跟上模板参数列表。参数可以用 class 或 typename,习惯上多用 typename。
// 交换两个数
template<class T>
void Swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// 多参数版本
template<class T1, class T2>
void func(const T1& x, const T2& y) {
cout << x << " " << y << endl;
}
模板本身不是函数,是编译器用来生成函数的'配方'。
隐式与显式实例化
隐式实例化就是让编译器根据实参推导类型:
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); // T 推导为 int
Add(d1, d2); // T 推导为 double
return 0;
}
但如果你混着传 Add(a1, d1),编译器就犯难了——没法把 int 和 double 统一成一个 T,它也不会帮你隐式转换。解决办法有两个:手动强转 Add(a1, (int)d1),或者显式指定 Add<int>(a1, d1),后者会把 d1 截成 int(可能有精度损失)。
显式实例化就是直接告诉编译器用哪个类型:
Add<int>(a1, d1);
与非模板函数的匹配
如果有个同名的普通函数,模板并不总是靠边站。规则大致是:先看看有没有完美匹配的非模板;如果没有,才考虑模板实例化。
int Add(int left, int right) { return left + right; } // 普通函数
template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; }
void Test() {
Add(1, 2); // 匹配普通函数,类型完全吻合
Add(1, 2.0); // 普通函数不匹配,模板上
}
混合使用重载和模板时,这个优先级值得记住。
类模板
类模板也不是真正的类,它描摹了一个类家族,必须绑上具体类型才变成真实的类。
template <typename T>
class Stack {
public:
void push(const T& x);
T pop();
private:
T data[100];
int top;
};
成员函数在类外定义要带上模板前缀:
template <typename T>
void Stack<T>::push(const T& x) {
data[++top] = x;
}
template <typename T>
T Stack<T>::pop() {
return data[top--];
}
类模板必须显式实例化,不像函数模板那样可以靠参数推导。例如 Stack<int> 和 Stack<double> 是两种独立的类型。
一个自带扩容的栈
template<class T>
class Stack {
public:
Stack(int n = 4) : _array(new T[n]), _capacity(n), _size(0) {}
~Stack() { delete[] _array; }
void Push(const T& x) {
if (_size == _capacity) {
int newCapacity = _capacity * 2;
T* tmp = new T[newCapacity];
memcpy(tmp, _array, _capacity);
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = x;
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
int main() {
Stack<int> st1;
st1.Push(1);
st1.Push(2);
Stack<double> st2;
st2.Push(1.1);
st2.Push(1.2);
return 0;
}
注意:Push 里用了 memcpy 扩容,这只对平凡类型(如 int、double)安全。如果你的栈保存的是有构造函数或虚表的对象,最好改用 std::copy 或者逐个移动构造,否则会引发未定义行为。很多教学示例会这样写,实际工程里要谨慎。

