模板是 C++ 泛型编程的核心:写一份代码,让它适用于任意类型。STL 的 vector<int>、vector<string> 背后全是模板。掌握模板,你才能看懂标准库,也才能写出真正的通用代码。
1. 为什么需要模板
没有模板时,想支持 int 和 double 的加法,得复制两份几乎相同的函数。模板把"类型"变成参数,编译器在编译期按需生成对应版本:
template <typename T>
T my_max(T a, T b) {
return a > b ? a : b;
}
int main() {
cout << my_max(3, 7); // T 推导为 int
cout << my_max(3.5, 2.1); // T 推导为 double
cout << my_max('a', 'z'); // T 推导为 char
}
2. 函数模板
typename 和 class 在这里等价。多个参数类型可以不同:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // C++11 尾置返回类型
return a + b;
}
// 更简洁:C++14 起直接用 auto
template <typename T, typename U>
auto add2(T a, U b) { return a + b; }
// 显式指定类型(模板参数无法推导时)
auto v = my_max<double>(3, 2.5);
3. 类模板
类模板让"容器"成为可能。下面实现一个迷你栈:
template <typename T>
class MiniStack {
vector<T> data;
public:
void push(const T& v) { data.push_back(v); }
void pop() { data.pop_back(); }
T& top() { return data.back(); }
bool empty() const { return data.empty(); }
};
MiniStack<int> s1;
MiniStack<string> s2; // 同一份代码,两种类型
注意:类模板的成员函数定义在类外时要重复写 template <typename T>。由于模板要"看到完整定义才能实例化",实现通常直接写在头文件里。
4. 模板特化
某些类型需要特殊处理时,可以特化。例如 const char* 的比较不能用 >(那是在比地址):
// 全特化:为特定类型提供专用版本
template <>
const char* my_max(const char* a, const char* b) {
return strcmp(a, b) > 0 ? a : b; // 按内容比较
}
5. 非类型模板参数
模板参数不一定是类型,也可以是编译期常量(整数、指针等),常用于固定大小:
template <typename T, size_t N>
class FixedArray {
T arr[N]; // N 是编译期常量
public:
size_t size() const { return N; }
T& operator[](size_t i) { return arr[i]; }
};
FixedArray<int, 8> buf; // 8 个 int 的定长数组
6. 模板与 STL 的关系
| STL 组件 | 模板类型 | 例子 |
|---|---|---|
| 容器 | 类模板 | vector<T>, map<K,V> |
| 算法 | 函数模板 | sort(begin, end) |
| 迭代器 | 类模板 | vector<T>::iterator |
| 函数对象 | 类模板 | greater<T>, less<T> |
💡 模板报错信息又长又吓人,这是正常的。遇到编译错误,从第一行"error"往前看,一般真正的错误在文件头部。建议练习:自己写一个 my_vector<T>,你就懂了为什么 STL 头文件里全是代码。