C++ 模板:泛型编程的核心利器

💡 学习目标:掌握模板的基本语法与分类,理解函数模板和类模板的实现原理,能够利用模板实现通用化、可复用的代码,提升开发效率。
💡 学习重点:函数模板的定义与实例化、类模板的使用方法、模板特化的应用场景、模板的编译机制与注意事项。
一、模板的概念与核心价值
✅ 结论:模板是 C++ 泛型编程的基础,通过参数化类型的方式,让函数或类能够独立于具体数据类型工作,实现代码的高度复用。
1.1 泛型编程的思想
泛型编程的核心是**“参数化类型”**,简单来说就是将数据类型作为参数传递给函数或类。
- 传统编程:一个函数只能处理一种数据类型,如
int类型的交换函数只能交换整数。 - 泛型编程:通过模板定义通用函数,可处理
int、float、string等任意类型的数据。
1.2 模板的核心优势
- 代码复用:一份模板代码可适配多种数据类型,避免重复编写相似功能的代码。
- 类型安全:编译阶段进行类型检查,避免隐式类型转换带来的错误。
- 高效灵活:模板实例化发生在编译期,不会带来运行时性能开销。
⚠️ 注意事项:模板不是函数或类,而是生成函数或类的蓝图,只有在被调用或实例化时,编译器才会生成具体类型的代码。
二、函数模板:通用函数的实现
💡 函数模板是能够处理多种数据类型的通用函数定义,使用关键字 template 声明,通过类型参数 typename 或 class 定义类型占位符。
2.1 函数模板的语法格式
template <typename T> // 模板参数列表,T 是类型参数
返回值类型 函数名(参数列表) {
// 函数体:使用 T 作为数据类型
}
template:声明模板的关键字,必须放在函数定义最前面。typename:用于定义类型参数的关键字,也可以用class替代,二者作用完全相同。T:类型参数的占位符,可以是任意合法标识符,通常用T、U、V等表示。
2.2 函数模板的基础案例:通用交换函数
传统写法需要为每种类型写一个交换函数,模板写法只需一份代码:
#include <iostream>
#include <string>
using namespace std;
// 函数模板:通用交换函数
template <typename T>
void swapData(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
// 实例化 int 类型的交换函数
int x = 10, y = 20;
swapData(x, y);
cout << "int 交换后:x = " << x << ", y = " << y << endl;
// 实例化 float 类型的交换函数
float f1 = 3.14f, f2 = 6.28f;
swapData(f1, f2);
cout << "float 交换后:f1 = " << f1 << ", f2 = " << f2 << endl;
// 实例化 string 类型的交换函数
string s1 = "Hello", s2 = "C++";
swapData(s1, s2);
cout << "string 交换后:s1 = " << s1 << ", s2 = " << s2 << endl;
return 0;
}
2.2.1 运行结果
int 交换后:x = 20, y = 10
float 交换后:f1 = 6.28, f2 = 3.14
string 交换后:s1 = C++, s2 = Hello
2.2.2 核心原理:模板实例化
当调用 swapData(x, y) 时,编译器会自动推导类型参数 T 为 int,并生成 int 版本的函数:
void swapData(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
这个过程称为模板的隐式实例化,编译器会根据传入的实参类型自动推导 T 的类型。
2.3 函数模板的显式实例化
当编译器无法自动推导类型参数时,需要手动指定类型,这就是显式实例化:
#include <iostream>
using namespace std;
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
// 隐式实例化:自动推导 T 为 int
cout << add(10, 20) << endl;
// 显式实例化:手动指定 T 为 double
cout << add<double>(10, 20.5) << endl;
return 0;
}
2.3.1 运行结果
30
30.5
2.4 函数模板的重载
函数模板可以和普通函数重载,也可以和其他模板重载,编译器会优先调用普通函数:
#include <iostream>
using namespace std;
// 函数模板
template <typename T>
T add(T a, T b) {
cout << "模板函数调用:";
return a + b;
}
// 普通函数
int add(int a, int b) {
cout << "普通函数调用:";
return a + b;
}
int main() {
// 调用普通函数
cout << add(10, 20) << endl;
// 调用模板函数(显式实例化)
cout << add<double>(10.5, 20.5) << endl;
return 0;
}
2.4.1 运行结果
普通函数调用:30
模板函数调用:31
三、类模板:通用类的实现
💡 类模板是能够处理多种数据类型的通用类定义,语法与函数模板类似,适用于实现容器类(如数组、栈、队列)等通用数据结构。
3.1 类模板的语法格式
template <typename T> // 模板参数列表
class 类名 {
public:
// 成员变量和成员函数,可使用 T 作为类型
T data;
void setData(T value);
T getData();
};
// 类模板的成员函数定义,必须在外部添加模板参数列表
template <typename T>
void 类名<T>::setData(T value) {
this->data = value;
}
template <typename T>
T 类名<T>::getData() {
return this->data;
}
⚠️ 注意事项:类模板的成员函数如果在类外定义,必须添加 template <typename T> 模板参数列表,并且类名要加上 <T>。
3.2 类模板的基础案例:通用栈
实现一个支持任意数据类型的栈结构:
#include <iostream>
#include <string>
using namespace std;
// 类模板:通用栈
template <typename T>
class Stack {
private:
T* arr; // 动态数组存储数据
int topIndex; // 栈顶索引
int capacity; // 栈的容量
public:
// 构造函数
Stack(int cap = 10) {
capacity = cap;
arr = new T[capacity];
topIndex = -1;
}
// 析构函数
~Stack() {
delete[] arr;
}
// 入栈操作
void push(T value) {
if (topIndex >= capacity - 1) {
cout << "⚠️ 栈满,无法入栈" << endl;
return;
}
arr[++topIndex] = value;
cout << "✅ 入栈成功:" << value << endl;
}
// 出栈操作
T pop() {
if (topIndex < 0) {
cout << "⚠️ 栈空,无法出栈" << endl;
return T(); // 返回 T 类型的默认值
}
return arr[topIndex--];
}
// 判断栈是否为空
bool isEmpty() {
return topIndex == -1;
}
};
int main() {
// 实例化 int 类型的栈
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
cout << "出栈:" << intStack.pop() << endl;
// 实例化 string 类型的栈
Stack<string> strStack;
strStack.push("Hello");
strStack.push("Template");
cout << "出栈:" << strStack.pop() << endl;
return 0;
}
3.2.1 运行结果
✅ 入栈成功:10
✅ 入栈成功:20
出栈:20
✅ 入栈成功:Hello
✅ 入栈成功:Template
出栈:Template
3.3 类模板的模板参数
类模板的模板参数可以是类型参数,也可以是非类型参数(如整数常量):
#include <iostream>
using namespace std;
// 非类型参数 N:栈的固定容量
template <typename T, int N>
class FixedStack {
private:
T arr[N];
int topIndex;
public:
FixedStack() : topIndex(-1) {}
void push(T value) {
if (topIndex >= N - 1) {
cout << "⚠️ 栈满" << endl;
return;
}
arr[++topIndex] = value;
}
T pop() {
if (topIndex < 0) {
cout << "⚠️ 栈空" << endl;
return T();
}
return arr[topIndex--];
}
};
int main() {
// 实例化:T 为 int,N 为 5
FixedStack<int, 5> stack;
stack.push(1);
stack.push(2);
cout << stack.pop() << endl;
return 0;
}
3.3.1 运行结果
2
⚠️ 注意事项:类模板的非类型参数必须是编译期常量,不能是变量。
四、模板特化:处理特殊类型的需求
💡 模板特化是指为特定类型提供定制化的模板实现,分为全特化和偏特化两种,用于解决通用模板无法处理的特殊场景。
4.1 函数模板的全特化
当通用模板对某个类型不适用时,为该类型单独编写特化版本:
#include <iostream>
#include <cstring>
using namespace std;
// 通用函数模板
template <typename T>
int compare(T a, T b) {
if (a > b) return 1;
else if (a < b) return -1;
else return 0;
}
// 函数模板全特化:处理 char* 类型的字符串比较
template <>
int compare<char*>(char* a, char* b) {
return strcmp(a, b);
}
int main() {
// 通用模板:int 类型比较
cout << compare(10, 20) << endl;
// 特化模板:char* 类型字符串比较
char str1[] = "Hello";
char str2[] = "World";
cout << compare(str1, str2) << endl;
return 0;
}
4.1.1 运行结果
-1
-15
4.2 类模板的全特化
为类模板的特定类型提供定制化实现:
#include <iostream>
using namespace std;
// 通用类模板
template <typename T>
class MyClass {
public:
MyClass() {
cout << "通用模板:T 为 " << typeid(T).name() << endl;
}
};
// 类模板全特化:T 为 int 类型
template <>
class MyClass<int> {
public:
MyClass() {
cout << "全特化模板:T 为 int" << endl;
}
};
int main() {
MyClass<double> obj1; // 通用模板
MyClass<int> obj2; // 全特化模板
return 0;
}
4.2.1 运行结果
通用模板:T 为 double
全特化模板:T 为 int
4.3 类模板的偏特化
偏特化是指对模板参数的部分特化,适用于模板有多个参数的场景:
#include <iostream>
using namespace std;
// 通用类模板:两个类型参数
template <typename T, typename U>
class MyPair {
public:
MyPair() {
cout << "通用模板:T = " << typeid(T).name() << ", U = " << typeid(U).name() << endl;
}
};
// 偏特化1:第二个参数为 int
template <typename T>
class MyPair<T, int> {
public:
MyPair() {
cout << "偏特化模板1:T = " << typeid(T).name() << ", U = int" << endl;
}
};
// 偏特化2:两个参数都是指针类型
template <typename T, typename U>
class MyPair<T*, U*> {
public:
MyPair() {
cout << "偏特化模板2:T* 和 U*" << endl;
}
};
int main() {
MyPair<double, string> p1; // 通用模板
MyPair<double, int> p2; // 偏特化1
MyPair<int*, char*> p3; // 偏特化2
return 0;
}
4.3.1 运行结果
通用模板:T = double, U = string
偏特化模板1:T = double, U = int
偏特化模板2:T* 和 U*
五、模板的编译机制与注意事项
5.1 模板的编译机制
模板采用分离编译模式,编译过程分为两个阶段:
- 模板定义阶段:编译器只检查模板代码的语法错误,不检查类型相关的错误。
- 模板实例化阶段:编译器根据传入的类型参数,生成具体类型的代码,并检查类型相关的错误。
⚠️ 核心问题:模板的声明和实现通常需要放在同一个头文件中,否则会导致链接错误。
- 原因:模板实例化需要看到完整的实现代码,若实现放在
.cpp文件中,编译器在实例化时无法找到代码。 - 解决方案:将类模板的声明和实现都写在
.h头文件中。
5.2 模板的使用禁忌
- 避免滥用模板:模板会增加编译时间和可执行文件体积,简单功能无需使用模板。
- 模板参数不能是局部类型:不能用函数内的局部类作为模板参数。
- 特化模板必须与原模板参数匹配:特化版本的模板参数数量和类型必须与原模板一致。
六、实战案例:模板实现通用数组排序
💡 需求:设计一个通用的排序函数,利用模板实现对任意类型数组的排序,支持整数、浮点数、字符串等类型。
6.1 需求分析
- 使用函数模板定义排序函数,支持任意数据类型。
- 采用冒泡排序算法,核心逻辑与数据类型无关。
- 支持自定义比较规则,实现升序和降序排序。
6.2 完整代码实现
#include <iostream>
#include <string>
using namespace std;
// 通用冒泡排序模板
template <typename T>
void bubbleSort(T arr[], int size, bool ascending = true) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
bool swapFlag = ascending ? (arr[j] > arr[j+1]) : (arr[j] < arr[j+1]);
if (swapFlag) {
T temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
// 打印数组模板
template <typename T>
void printArray(T arr[], int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
// 测试 int 数组排序
int intArr[] = {3, 1, 4, 1, 5, 9};
int intSize = sizeof(intArr) / sizeof(int);
cout << "int 数组排序前:";
printArray(intArr, intSize);
bubbleSort(intArr, intSize);
cout << "int 数组升序排序后:";
printArray(intArr, intSize);
// 测试 string 数组排序
string strArr[] = {"Banana", "Apple", "Cherry", "Date"};
int strSize = sizeof(strArr) / sizeof(string);
cout << "string 数组排序前:";
printArray(strArr, strSize);
bubbleSort(strArr, strSize, false);
cout << "string 数组降序排序后:";
printArray(strArr, strSize);
return 0;
}
6.3 运行结果
int 数组排序前:3 1 4 1 5 9
int 数组升序排序后:1 1 3 4 5 9
string 数组排序前:Banana Apple Cherry Date
string 数组降序排序后:Date Cherry Banana Apple
七、本章总结
✅ 模板是 C++ 泛型编程的核心,通过参数化类型实现代码复用,分为函数模板和类模板两大类。
✅ 函数模板通过隐式或显式实例化生成具体类型的函数,支持重载和特化。
✅ 类模板适用于实现通用容器类,支持类型参数和非类型参数,可通过特化处理特殊类型。
✅ 模板特化分为全特化和偏特化,用于为特定类型提供定制化实现。
✅ 模板的声明和实现通常需要放在头文件中,避免链接错误。

581

被折叠的 条评论
为什么被折叠?



