C++ 中的模板(Template)是泛型编程的基石。它允许我们编写与类型无关的代码,即编写一个函数或一个类,让它们能够处理任何数据类型,而无需为每一种类型都重写一次代码。这大大提高了代码的可重用性灵活性

1. 什么是 template <typename T>

这是一个模板声明,它告诉编译器接下来的代码(无论是函数还是类)是一个“蓝图”或“模板”,而不是一个具体的实现。

  • template: C++ 的关键字,表示这是一个模板。
  • <...>: 尖括号内是模板参数列表。
  • typename: 关键字,用来声明后面的标识符是一个类型参数。在模板参数列表中,typenameclass 关键字是等价的,可以互换使用。但通常推荐使用 typename,因为它更明确地表示 T 是一个类型名称。
  • T: 这是一个模板参数,它是一个占位符,代表一个尚未确定的数据类型。你可以使用任何合法的标识符来代替 T(例如 U, MyType 等),但 T 是最常用的惯例。

当你在代码中使用这个模板时(比如 max<int>(a, b)),编译器会根据你提供的具体类型(这里是 int),自动生成一个该类型的特定版本。这个过程被称为模板实例化(Template Instantiation)。


2. 函数模板 (Function Templates)

函数模板允许你定义一个通用的函数,它可以接受任何类型的参数。

场景与示例

假设你需要一个函数来比较两个数的大小。使用函数模板,只需要一份代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <string>

// 定义一个通用的 max 函数模板
template <typename T>
T my_max(T a, T b) {
// 要求类型 T 必须支持 > 操作符
return a > b ? a : b;
}

int main() {
// 1. 编译器自动类型推导
std::cout << "Max of 3 and 7 is: " << my_max(3, 7) << std::endl; // T 被推导为 int
std::cout << "Max of 3.5 and 1.2 is: " << my_max(3.5, 1.2) << std::endl; // T 被推导为 double

std::string s1 = "hello";
std::string s2 = "world";
std::cout << "Max of strings is: " << my_max(s1, s2) << std::endl; // T 被推导为 std::string

// 2. 显式指定类型
// 当类型推导可能产生歧义时(例如 my_max(3, 5.5)),可以显式指定
std::cout << "Max of 3 and 5.5 (as double): " << my_max<double>(3, 5.5) << std::endl;

return 0;
}

工作原理:编译器在编译时看到 my_max(3, 7),它知道参数是 int 类型,于是就会自动生成一个 int 版本的 my_max 函数。


3. 类模板 (Class Templates)

类模板允许你定义一个通用的类,其成员变量和成员函数可以处理任何数据类型。标准模板库(STL)中的容器,如 std::vector, std::list, std::map 等,都是类模板的经典例子。

场景与示例

假设你想创建一个可以存储任意类型数据的栈(Stack)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <vector>
#include <stdexcept>
#include <string>

template <typename T>
class MyStack {
private:
std::vector<T> elements;

public:
// 入栈
void push(const T& element) {
elements.push_back(element);
}

// 出栈
void pop() {
if (elements.empty()) {
throw std::out_of_range("Stack<T>::pop(): empty stack");
}
elements.pop_back();
}

// 查看栈顶元素
T top() const {
if (elements.empty()) {
throw std::out_of_range("Stack<T>::top(): empty stack");
}
return elements.back();
}

// 判断是否为空
bool isEmpty() const {
return elements.empty();
}
};

int main() {
// 使用类模板时,必须显式指定类型

// 创建一个存储 int 类型的栈
MyStack<int> intStack;
intStack.push(10);
intStack.push(20);
std::cout << "Top of intStack: " << intStack.top() << std::endl; // 输出 20
intStack.pop();
std::cout << "Top of intStack after pop: " << intStack.top() << std::endl; // 输出 10

std::cout << "--------------------" << std::endl;

// 创建一个存储 std::string 类型的栈
MyStack<std::string> stringStack;
stringStack.push("Alice");
stringStack.push("Bob");
std::cout << "Top of stringStack: " << stringStack.top() << std::endl; // 输出 "Bob"

return 0;
}

4. 模板的更多特性

多个模板参数

模板可以有多个类型或非类型参数。

1
2
3
4
5
6
7
8
9
10
11
template <typename T1, typename T2>
class KeyValuePair {
public:
T1 key;
T2 value;
};

// 使用
KeyValuePair<int, std::string> pair;
pair.key = 1;
pair.value = "Apple";

非类型模板参数

模板参数不仅可以是类型,还可以是整数、指针等编译期常量值。

1
2
3
4
5
6
7
8
9
10
template <typename T, int SIZE>
class FixedArray {
private:
T arr[SIZE];
public:
int getSize() const { return SIZE; }
};

// 使用
FixedArray<int, 100> my_array; // 创建一个大小为 100 的 int 数组

模板特化 (Template Specialization)

当通用模板的实现对某个特定类型不适用或效率不高时,可以为该特定类型提供一个“特供版”的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 通用模板
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};

// 针对 const char* 的特化版本
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: \"" << value << "\"" << std::endl;
}
};

// 使用
Printer<int> p1;
p1.print(123); // 使用通用模板,输出: 123

Printer<const char*> p2;
p2.print("hello"); // 使用特化版本,输出: String: "hello"

总结

特性 描述
核心思想 代码复用泛型编程。编写与类型无关的代码。
工作原理 编译器根据你提供的具体类型,在编译时自动生成相应版本的代码(模板实例化)。
template <typename T> 声明一个模板,T 是一个代表任意类型的占位符。
函数模板 创建通用函数。编译器通常能根据参数自动推导类型。
类模板 创建通用类(如容器)。在使用时必须显式指定类型。
优势 1. 代码简洁:避免为不同类型编写重复代码。
2. 类型安全:错误在编译期就能被发现,比使用 void* 等方式更安全。
3. 高性能:因为实例化在编译期完成,没有运行时的额外开销。