一、泛型编程:代码复用的革命
核心思想:编写与类型无关的通用代码,通过编译器生成具体类型版本。
传统痛点:
// 需为每个类型写重复代码
void Swap(int& a, int& b) { /*...*/ }
void Swap(double& a, double& b) { /*...*/ }
模板优势:只需一套模板代码,编译器自动生成所需版本,彻底解决代码冗余问题。
二、函数模板:通用函数的实现
1. 基础语法
template<typename T>
void Swap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
typename 可替换为 class,二者等价(但不能用 struct)。
2. 实例化机制
- 隐式实例化:编译器自动推导类型
-
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2); Add(d1, d2); /* 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有 一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错 2. 显式实例化:在函数名后的<>中指定模板参数的实际类型 如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。 2.5 模板参数的匹配原则 1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这 个非模板函数 2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模 板 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要 背黑锅 Add(a1, d1); */ // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化 Add(a, (int)d); return 0; } - 显式实例化:手动指定类型
-
int main(void) { int a = 10; double b = 20.0; // 显式实例化 Add<int>(a, b); return 0; }
3. 匹配优先级int Add(int a, int b); // 普通函数 template<class T> T Add(T a, T b); // 模板函数 Add(3, 5); // 优先调用普通函数 Add(3.1,5.2);// 调用模板生成的double版本三、类模板:通用数据结构的利器
1. 基本结构
-
template<class T> class Stack { public: Stack(size_t capacity = 4); void Push(const T& data); private: T* _array; size_t _size; };2. 关键特性
- 类名
Stack≠ 类型,Stack<int>才是具体类型 -
// Stack是类名,Stack<int>才是类型 Stack<int> st1; // int Stack<double> st2; // double - 成员函数在类外定义时需加模板前缀:
template<class T> void Stack<T>::Push(const T& data) { /*...*/ }四、非类型模板参数:编译期常量
1. 核心概念
-
template<class T, size_t N = 10> // N是非类型参数 class Array { private: T _array[N]; };2. 重要限制
- 仅支持整型、指针等类型
- 值必须在编译期确定
- 禁止使用浮点数、类对象、字符串
五、模板特化:特殊类型的定制处理
1. 函数模板特化(不推荐)
template<>
bool Less<Date*>(Date* a, Date* b) {
return *a < *b;
}
为什么不推荐?
直接重载普通函数更直观:bool Less(Date* a, Date* b) { ... }
2. 类模板特化
全特化:所有模板参数明确指定
template<>
class Data<int, char> { ... };
偏特化:
a. 部分参数特化
template<class T1>
class Data<T1, int> { ... };
b. 参数类型限制
template<class T1, class T2>
class Data<T1*, T2*> { ... }; // 指针版本
3. 经典应用场景
比较指针指向的对象,有如下专门用来按照小于比较的类模板Less:
#include<vector>
#include<algorithm>
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
int main()
{
Date d1(2022, 7, 7);
Date d2(2022, 7, 6);
Date d3(2022, 7, 8);
vector<Date> v1;
v1.push_back(d1);
v1.push_back(d2);
v1.push_back(d3);
// 可以直接排序,结果是日期升序
sort(v1.begin(), v1.end(), Less<Date>());
vector<Date*> v2;
v2.push_back(&d1);
v2.push_back(&d2);
v2.push_back(&d3);
// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
sort(v2.begin(), v2.end(), Less<Date*>());
return 0;
}
通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排
序元素是指针,结果就不一定正确。因为:sort最终按照Less模板中方式比较,所以只会比较指
针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题:
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
六、模板分离编译难题
1. 问题现象
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
2. 根本原因
模板代码需在编译时看到完整定义,分离编译导致编译器无法生成具体实例。
3. 解决方案
- 将声明和实现都放在.hpp文件
- 显式实例化(不推荐):
template int Add<int>(int, int);
七、模板的优缺点总结
优势
- 代码复用率提升90%+
- 类型安全强于宏定义
- STL等基础库的构建基石
缺陷
- 代码膨胀(多个实例化版本)
- 编译时间增加约30%-50%
- 错误信息晦涩难懂(例如50行错误提示)
八、最佳实践指南
- 优先使用函数重载而非函数特化
- 类模板的定义统一放在头文件
- 特化前先验证通用模板行为
- 对指针/引用类型保持高度警惕


1053

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



