- 模板(Templates)概述
- 在 C++ 中,类型参数化主要通过模板来实现。模板是一种泛型编程的工具,它允许程序员编写能够处理多种数据类型的代码,而不是为每种数据类型都编写重复的代码。模板可以分为函数模板和类模板。
- 例如,考虑一个简单的交换函数。如果不使用模板,我们可能需要为不同的数据类型(如
int
、double
、char
等)分别编写交换函数:
// 交换两个整数的函数 void swapInt(int& a, int& b) { int temp = a; a = b; b = temp; } // 交换两个双精度浮点数的函数 void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; }
- 这样的代码存在大量重复。使用模板可以简化这个过程。
- 函数模板(Function Templates)
- 定义和使用
- 函数模板的定义以关键字
template
开始,后面跟着模板参数列表,参数列表通常是一个或多个类型参数。例如,上面的交换函数可以写成函数模板的形式:
template<typename T> void swap(T& a, T& b) { T temp = a; a = b; b = temp; }
- 这里
typename T
声明了一个类型参数T
,它可以代表任何数据类型。在函数体中,T
被当作一种具体的数据类型来使用。使用这个函数模板时,编译器会根据传入的实际参数类型自动推断T
的具体类型,并生成相应的函数代码。例如:
int main() { int a = 1, b = 2; swap(a, b); // 编译器会自动生成交换两个整数的函数代码 double c = 1.2, d = 2.3; swap(c, d); // 编译器会自动生成交换两个双精度浮点数的函数代码 return 0; }
- 模板参数的多种形式
- 除了类型参数(
typename
或class
关键字声明),函数模板还可以有非类型参数。非类型参数通常是整数类型(如int
、long
等)或指针类型。例如,一个函数模板可以接受一个整数参数来指定数组的大小:
template<typename T, int N> void printArray(T (&arr)[N]) { for (int i = 0; i < N; ++i) { std::cout << arr[i] << " "; } std::cout << std::endl; }
- 这里
N
是一个非类型模板参数,它在函数调用时必须是一个常量表达式。使用这个函数模板可以像这样:
int main() { int arr1[] = {1, 2, 3}; printArray(arr1); double arr2[] = {1.1, 2.2, 3.3}; printArray(arr2); return 0; }
- 类模板(Class Templates)
- 定义和使用
- 类模板的定义和函数模板类似,也是以
template
关键字开头,后面跟着模板参数列表。例如,定义一个简单的动态数组类模板:
template<typename T> class DynamicArray { public: DynamicArray() : size(0), capacity(1), data(new T[capacity]) {} ~DynamicArray() { delete[] data; } void push_back(T element) { if (size == capacity) { // 数组扩容 T* newData = new T[capacity * 2]; for (int i = 0; i < size; ++i) { newData[i] = data[i]; } delete[] data; data = newData; capacity *= 2; } data[size++] = element; } T& operator[](int index) { return data[index]; } private: int size; int capacity; T* data; };
- 这个类模板
DynamicArray
可以用来创建存储不同类型元素的动态数组。使用类模板时,需要指定模板参数的具体类型,例如:
int main() { DynamicArray<int> intArray; intArray.push_back(1); intArray.push_back(2); std::cout << intArray[0] << " " << intArray[1] << std::endl; DynamicArray<double> doubleArray; doubleArray.push_back(1.1); doubleArray.push_back(2.2); std::cout << doubleArray[0] << " " << doubleArray[1] << std::endl; return 0; }
- 模板的特化(Template Specialization)
- 有时候,对于某些特定的类型,通用的模板定义可能不适用或者效率不高,这时可以使用模板特化。模板特化是指为特定的类型提供专门的模板实现。例如,对于
const char*
类型的DynamicArray
,我们可能希望有不同的比较行为。可以这样进行类模板特化:
// 全特化(Full Specialization) template<> class DynamicArray<const char*> { public: DynamicArray() : size(0), capacity(1), data(new const char*[capacity]) {} ~DynamicArray() { for (int i = 0; i < size; ++i) { delete[] data[i]; } delete[] data; } void push_back(const char* element) { //... 不同的实现逻辑 } const char*& operator[](int index) { return data[index]; } private: int size; int capacity; const char** data; };
- 这里
template<>
表示这是一个全特化的类模板,它只适用于const char*
类型。
- 模板的编译模型
- C++ 模板是一种编译时的机制。当编译器遇到模板定义时,它不会立即生成代码,而是在模板被实例化(即使用具体的参数类型)时才会生成相应的代码。这意味着模板的定义通常需要放在头文件中,以便编译器在需要时能够看到完整的模板定义并进行实例化。
- 例如,如果将函数模板或类模板的定义放在
.cpp
文件中,而在另一个文件中使用这个模板,编译器在编译使用模板的文件时可能无法找到模板的定义,从而导致链接错误。为了解决这个问题,通常会将模板的定义和声明放在头文件中,或者使用一些特殊的编译技巧,如显式实例化等。