C++ 模板
模板(template)是 C++ 最强大也最复杂的特性之一。简单说,模板让你写一份"通用代码",适用于任意类型——这就是泛型编程(generic programming)。C++ 标准库(STL)几乎全部由模板构成,template 是 STL 的基石。
一、为什么需要模板?
假设你要写一个 max 函数,接受两个数返回较大值。不用模板,你得为每种类型写一份:
int max(int, int)double max(double, double)string max(string, string)- ...... 一旦加新类型,又得重写一份。
函数重载缓解了"代码使用"的复杂度,但实现仍是重复的。模板让你只写一份。
二、函数模板
#include <iostream>
using namespace std;
// 没有模板:针对每种类型都写一份
// int maxx(int a, int b) { return a > b ? a : b; }
// double maxx(double a, double b) { return a > b ? a : b; }
// string maxx(string a, string b) { return a > b ? a : b; }
// 函数模板:用一个虚拟类型 T 代替具体类型
template <typename T>
T maxx(const T& a, const T& b) {
return a > b ? a : b;
}
int main() {
cout << maxx(3, 5) << endl; // 5(T=int)
cout << maxx(3.14, 2.71) << endl; // 3.14(T=double)
cout << maxx(string("apple"), string("banana")) << endl; // banana(T=string)
return 0;
}语法解释:
template <typename T>:声明一个虚拟类型 T。typename也可以写成class,完全等价(惯例:用 typename)。- T 是占位符:调用
maxx(3, 5)时,编译器把 T 替换成 int,生成一份真正的函数。 - 类型推导:不用显式写
maxx<int>(3, 5),编译器根据参数自动推导 T。 - const T&:用 const 引用,既支持大对象(避免拷贝)又支持内置类型。
三、类模板
类模板让你定义通用容器和工具类。STL 的 vector、map 全是类模板:
#include <iostream>
#include <vector>
using namespace std;
// 类模板:用类型参数 T 定义通用容器/类
template <typename T>
class Stack {
private:
vector<T> data;
public:
void push(const T& x) { data.push_back(x); }
void pop() { data.pop_back(); }
T top() const { return data.back(); }
bool empty() const { return data.empty(); }
size_t size() const { return data.size(); }
};
int main() {
Stack<int> si; // 显式指定 T=int
si.push(1);
si.push(2);
cout << si.top() << endl; // 2
Stack<string> ss; // T=string
ss.push("hello");
ss.push("world");
cout << ss.top() << endl; // world
// C++17 支持类模板参数推导(CTAD)
Stack sd = Stack<double>{}; // 之前必须写 <double>
return 0;
}注意:
- 类模板使用时必须显式指定类型:
Stack<int>、Stack<string>。函数模板可以推导,类模板一般不能(C++17 引入了 CTAD 类模板参数推导)。 - 类模板的成员函数也是模板,定义在类外时要写
template <...>前缀。 - 类模板常用于:容器(vector)、智能指针(unique_ptr)、包装器(function)、迭代器。
四、多个模板参数与非类型参数
#include <iostream>
using namespace std;
// 多个模板参数
template <typename K, typename V>
struct Pair {
K key;
V value;
};
// 模板参数可以有默认值
template <typename T, int N>
class Array {
T data[N];
public:
T& operator[](int i) { return data[i]; }
int size() { return N; }
};
int main() {
Pair<string, int> p{"age", 25};
cout << p.key << " = " << p.value << endl; // age = 25
// 非类型模板参数(必须是编译期常量)
Array<double, 5> arr;
for (int i = 0; i < arr.size(); i++) arr[i] = i * 0.5;
cout << arr[3] << endl; // 1.5
return 0;
}模板参数有两大类:
- 类型参数:
typename T、typename K, typename V。可以多个,可以有默认值(typename T = int)。 - 非类型参数:
int N、bool Flag。必须是编译期常量。用于数组大小、标志位等。C++20 后甚至可以用浮点和字符串。
五、模板参数推导与 auto 返回
#include <iostream>
using namespace std;
template <typename T>
void print(T x) {
cout << x << endl;
}
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // C++11 尾随返回类型
return a + b;
}
int main() {
print(42); // T=int(编译器自动推导)
print("hello"); // T=const char*
print(3.14); // T=double
cout << add(1, 2.5) << endl; // 3.5(double)
cout << add(string("a"), string("b")) << endl; // ab
// 显式指定模板参数(通常不必要,编译器能推导)
print<int>(100);
// C++20 引入 concepts 可以约束模板参数
return 0;
}- 编译器自动推导:调用
print(42)时 T 推导为 int。 - 多参数推导:每个参数独立推导,必须一致(否则编译错误或显式指定)。
- 尾随返回类型(C++11
-> decltype(...)):用于返回类型依赖参数的场景。 - C++14+:可以直接写
auto返回,编译器从 return 语句推导。
六、模板特化
有时候某个特定类型需要特殊处理。模板特化(specialization)让你为特定类型提供专门实现:
#include <iostream>
using namespace std;
template <typename T>
T maxx(const T& a, const T& b) {
return a > b ? a : b;
}
// 全特化:对某个特定类型提供专门实现
template <>
const char* maxx(const char* const& a, const char* const& b) {
return strcmp(a, b) > 0 ? a : b;
}
// 类模板特化示例
template <typename T>
class TypeName {
public:
static const char* get() { return "未知类型"; }
};
// 针对 int 的特化
template <>
class TypeName<int> {
public:
static const char* get() { return "int"; }
};
int main() {
cout << maxx(3, 5) << endl; // 5(用通用模板)
cout << maxx("apple", "banana") << endl; // banana(用特化版本)
cout << TypeName<double>::get() << endl; // 未知类型
cout << TypeName<int>::get() << endl; // int
return 0;
}- 全特化(full specialization):
template <>+ 针对特定类型的实现。 - 偏特化(partial specialization):只对类模板,针对"某一类类型"(如
template <typename T> class Foo<T*>处理所有指针类型)。函数模板不能偏特化,只能重载。 - 典型用途:针对
const char*的字符串处理、针对 vector<T> 的容器优化。
七、模板的编译期本质
理解模板的关键:模板是"代码生成"机制。模板本身不编译成代码,只有被"实例化"时编译器才生成具体代码:
// 模板是"编译期"机制:编译器根据调用生成具体代码
// 模板本身不生成代码,只有被"实例化"时才生成
template <typename T>
T add(T a, T b) { return a + b; }
// 调用 add(1, 2):编译器实例化出 int add(int, int)
// 调用 add(1.5, 2.5):编译器实例化出 double add(double, double)
// 这是"代码生成",编译后的二进制里有两份不同的 add
// 模板的代价:
// 1. 编译慢:每个用到的类型都要生成一份代码
// 2. 二进制大:模板代码膨胀(code bloat)
// 3. 错误信息难懂:编译器报错会带一长串模板实例化栈
// 模板的强大:
// 1. 性能:编译期生成,运行时零开销
// 2. 类型安全:每个实例都是强类型
// 3. 复用:写一份代码,适用所有类型(STL 就靠它)这种"编译期代码生成"带来:
- 零开销抽象:运行时和手写的特化代码一样快。
- 类型安全:每个实例都是强类型,编译期检查。
- 编译变慢:每次调用不同类型都要生成代码。
- 错误信息难读:模板错误会打印一大串实例化栈。C++20 的 concepts 改善了这点。
八、模板是 STL 的基石
STL(标准模板库)全部由模板构成:
#include <iostream>
#include <vector>
using namespace std;
// STL 全部由模板构成
// vector<int>、vector<string>、vector<vector<int>> ...
template <typename T>
void printAll(const vector<T>& v) {
for (const T& x : v) {
cout << x << " ";
}
cout << endl;
}
int main() {
vector<int> nums = {3, 1, 4, 1, 5};
printAll(nums); // 3 1 4 1 5
vector<string> names = {"Alice", "Bob", "Carol"};
printAll(names); // Alice Bob Carol
// 没有模板,你必须写 printAllInt、printAllString、printAllDouble ...
// 这就是模板的意义:消除重复
return 0;
}没有模板,STL 不可能存在——你不可能为每种类型都写一份 vector、list、map。模板 = 代码复用的终极武器。
九、变参模板(C++11)
这是 C++11 引入的高阶特性,接受任意数量、任意类型的参数:
#include <iostream>
using namespace std;
// 变参模板(C++11):接受任意数量、任意类型的参数
// 参数包(parameter pack):typename... Args
// 递归终止:无参数时调这个
void print() {
cout << endl;
}
// 递归展开:取第一个,剩下的递归处理
template <typename T, typename... Args>
void print(const T& first, const Args&... rest) {
cout << first << " ";
print(rest...); // 把剩下的传给自己
}
int main() {
print(1, 2.5, "hello", 'c', string("end"));
// 输出: 1 2.5 hello c end
// 这种"递归展开"是 C++11/14 的写法
// C++17 引入了"折叠表达式",更简洁
return 0;
}
// 变参模板是现代 C++ 最重要的特性之一
// std::make_unique、std::tuple、fmt::format 都靠它实现变参模板的实际用途:
- std::make_unique<T>(args...):完美转发构造函数参数。
- std::tuple<T1, T2, ...>:任意元素数量的元组。
- fmt::format("...", args...):类型安全的格式化。
- C++17 折叠表达式:简化变参模板的展开语法。
新手不用精通变参模板,知道它存在、能看懂 STL 实现就够了。
十、模板进阶(了解)
模板能做的事情远不止泛型。C++ 模板是图灵完备的,可以在编译期做任意计算:
- 模板元编程(TMP):用模板在编译期算斐波那契、阶乘等。运行时零开销,但代码极难读。
- SFINAE(替换失败不是错误):用模板条件性启用/禁用函数。复杂,现代 C++ 用 concepts 替代。
- concepts(C++20):约束模板参数(
template <std::integral T>要求 T 是整数)。大大改善错误信息。
这些属于高级主题,工程师日常用得很少,了解即可。
小结
这一章你认识了 C++ 模板:函数模板、类模板、多参数与默认值、参数推导、特化(全/偏)、变参模板。理解了模板是"编译期代码生成",是 STL 的基石。下一篇我们正式进入 STL——C++ 程序员的"瑞士军刀"。
← 上一篇 C++ 继承
下一篇 C++ STL →