C++模板
我们来详细讲解 C++ 中的模板(Templates)。
1. 模板的概念
模板是 C++ 中的一种特性,它允许你编写通用代码,这种代码可以与多种不同的数据类型一起工作,而无需为每种数据类型分别编写。
你可以把模板想象成一个代码生成器。你定义一个模板,它包含了通用的逻辑,然后当你用一个具体的数据类型(比如 int、double 或一个自定义的 Person 结构体)去“实例化”这个模板时,编译器会自动为你生成一份针对该具体类型的代码。
2. 函数模板
函数模板是最常见的模板类型。它允许你创建一个通用的函数,用于处理各种类型的数据。
2.1 函数模板的定义
template <typename T> |
template <typename T>:这是模板声明。template关键字告诉编译器接下来是一个模板定义。<typename T>是模板参数列表。typename是一个关键字,用于引入一个模板类型参数。T是类型参数的名称,你可以把它看作一个占位符,它将在模板被实例化时被一个具体的类型(如int、double)所替代。你也可以使用class关键字代替typename,它们在大多数情况下是等价的。
T add(T a, T b):这是函数定义。- 返回类型是
T。 - 参数
a和b的类型也都是T。
- 返回类型是
2.2 函数模板的使用(实例化)
当你调用一个函数模板时,编译器会根据你传入的参数类型来推断模板参数 T 的具体类型,并生成相应的函数代码。这个过程称为模板实例化。
|
输出:Template add called for type: int
Sum (int): 15
Template add called for type: double
Sum (double): 5.85
Template add called for type: std::basic_string<char, std::char_traits<char>, std::allocator<char> >
Sum (string): Hello, World!
(注意:typeid(T).name() 的输出格式在不同编译器上可能略有不同)
在这个例子中,编译器实际上为我们生成了三个不同的 add 函数:
int add(int a, int b)double add(double a, double b)string add(string a, string b)
2.3 模板的显式实例化
有时,你可能希望在调用函数时明确指定模板参数的类型,而不是让编译器去推断。
int main() { |
在这个例子中,x (int类型) 会被隐式转换为 double 类型。
3. 类模板
类模板允许你创建一个通用的类,其成员变量和成员函数的类型可以是模板参数。
3.1 类模板的定义
template <typename T> |
template <typename T>:同样是模板声明。class MyArray:类定义。类中的T就是模板参数。
3.2 类模板的使用(实例化)
使用类模板时,必须显式指定模板参数。
|
输出:Integer Array: 0 10 20 30 40
String Array: Apple Banana Cherry
编译器会为 MyArray<int> 和 MyArray<string> 生成两个完全独立的类代码。
4. 模板参数
模板可以有多种类型的参数:
类型参数 (Type Parameters):
- 这是最常见的,用
typename或class声明。 - 例子:
template <typename T, class U>
- 这是最常见的,用
非类型参数 (Non-type Parameters):
- 它们是编译期常量,必须是整数类型(
int,long,size_t等)、枚举或指针/引用(指向具有外部链接的对象或函数)。 - 例子:
template <typename T, int Size> // Size 是一个非类型参数
class FixedSizeArray {
private:
T data[Size];
public:
// ...
};
FixedSizeArray<double, 10> arr; // 一个大小为 10 的 double 数组
- 它们是编译期常量,必须是整数类型(
5. 模板的特化 (Specialization)
有时,对于某个特定的类型,你可能希望模板有不同的行为,而不是使用通用的模板代码。这就是模板特化的用途。
5.1 函数模板特化
|
输出:Generic print: 42
Generic print: 3.14
Specialized print for const char*: Hello, World!
Generic print: Hi there
5.2 类模板特化
|
输出:Generic MyContainer constructed with value: 100
Generic MyContainer constructed with value: 3.14
Specialized MyContainer<bool> constructed with flag: true
6. 模板的优点
- 代码复用:编写一份通用代码,可以用于多种数据类型,极大地减少了代码冗余。
- 类型安全:模板在编译时进行类型检查,比使用
void*等方式更安全。 - 效率:模板实例化是在编译期完成的,生成的代码是针对具体类型优化的,没有运行时开销。
- 灵活性和可扩展性:可以轻松地为新的数据类型扩展现有模板的功能。C++ 标准库中的许多组件(如
std::vector,std::map,std::sort)都是基于模板实现的。
7. 模板的缺点/注意事项
- 编译时间增加:模板实例化会增加编译时间,因为编译器需要为每个使用的类型生成新的代码。
- 代码膨胀:如果一个模板被多种类型实例化,生成的目标文件会包含多份相似的代码,可能导致可执行文件变大。现代编译器和链接器会进行优化(如 COMDAT folding)来缓解这个问题。
- 错误信息复杂:当模板代码出现错误时,编译器给出的错误信息通常非常冗长和难以理解,因为它涉及到模板实例化的过程。
- 调试困难:调试模板代码也比调试普通代码更具挑战性。
8.多个类型参数
1. 模板的多个类型参数
定义格式:template <typename T, typename D>
返回类型 函数名(参数列表) {
// 函数体
}
T和D都是独立的类型参数,可在函数中作为类型使用。- 调用时,编译器会根据传入的参数自动推断
T和D的具体类型,也可以显式指定。
2. 示例:多个类型参数的函数模板
|
输出:(10, 3.14)
(Hello, 5)
(Alice, 1)
3. 类模板的多个类型参数
类模板也支持多个类型参数,例如:template <typename Key, typename Value>
class Pair {
private:
Key key;
Value value;
public:
Pair(Key k, Value v) : key(k), value(v) {}
Key getKey() const { return key; }
Value getValue() const { return value; }
};
int main() {
Pair<int, string> p1(1, "Apple");
Pair<string, double> p2("PI", 3.1415);
cout << p1.getKey() << ": " << p1.getValue() << endl;
cout << p2.getKey() << ": " << p2.getValue() << endl;
return 0;
}
输出:1: Apple
PI: 3.1415
4. 多个类型参数 + 非类型参数
模板还可以同时包含类型参数和非类型参数:
template <typename T, typename D, int N> |
输出:1 -> A
2 -> B
3 -> C
5. 显式指定模板参数
调用时,可以显式指定部分或全部类型参数:
printPair<int, double>(10, 3.14); // 显式指定 T=int, D=double |