文章目录
-
- 上期回顾
- 一、C++ 模板初阶
-
- 一、本节核心主线
- 二、泛型编程
-
- 1. 为什么需要泛型编程?
- 2. 泛型编程的思想
- 三、函数模板
-
- 1. 函数模板概念
- 2. 函数模板格式
- 3. 通用 Swap 函数模板
- 四、函数模板的原理⭐️⭐️⭐️
-
- 1. 函数模板不是函数
- 2. 模板把重复工作交给编译器
- 五、函数模板实例化⭐️
- 1. 隐式实例化(常用)
- 2. 隐式实例化的类型冲突
- 3. 解决方式一:强制类型转换
- 4. 解决方式二:显式实例化
- 六、模板参数匹配原则
-
- 1. 普通函数和函数模板可以同名共存
- 2. 普通函数和模板都匹配时,优先普通函数
- 3. 如果模板能生成更匹配的版本,会选择模板
- 4. 模板一般不主动做类型转换
- 七、类模板
- 1. 类模板定义格式
- 2. 类模板成员函数在类外定义
- 3. 模板不建议声明和定义分离到 `.h` 和 `.cpp`⭐️⭐️⭐️⭐️
- 八、类模板实例化
- 九、易错点汇总
-
- 易错点 1:把函数模板当成函数
- 易错点 2:模板参数中用 `struct`
- 易错点 3:同一个 `T` 被推导成不同类型
- 易错点 4:以为模板会自动类型转换
- 易错点 5:类模板实例化忘记写 `<类型>`
- 易错点 6:类模板成员函数类外定义忘记 `Stack<T>::`
- 十、本节总结
- 二、STL 简介复习总结
-
- 一、本讲核心主线
- 二、什么是 STL
- 三、STL 的版本
-
- 1. 原始版本 / HP 版本
- 2. P. J. 版本
- 3. RW 版本
- 4. SGI 版本
- 四、STL 的六大组件
-
- 1. 容器
- 2. 算法
- 3. 迭代器
- 4. 仿函数
- 5. 适配器
- 6. 空间配置器
- 五、本节总结⭐️⭐️
- 下期预告
- string
- 结语
◆ 博主: @此生决int
分享编程知识!深挖底层原理!持续原创更新!
热门专栏: 深入理解 C++系列|算法系列 快速复习系列 |Java 速通系列
📌 本系列知识点前后关联较强,建议按照专栏顺序阅读哦
本系列主要面向的阅读人群
1,学完 C 语言,准备系统学习 C++ 的同学
2,已经学习过 C++,希望重新梳理知识体系的同学
3,想深入理解 C++ 设计思想与底层原理,而不仅仅停留在语法层面的同学
不止教你会写代码,更带你理解代码为什么这样设计!
—
上期回顾
hello,大家好,这里是 此生决int,上一期呢我们学习了C++中数据的存储,新学习了new delete等等C++申请和释放内存的方式,那么今天我们再来学习一个C++面向对象的一个厉害的地方,模版和STL,这可以算的上是C++区别于C语言的最大的一个特点,甚至有人说,C语言+STL就是C++!!!那么今天我们就来学习学习这个模版和STL!
一、C++ 模板初阶
一、本节核心主线
模板初阶主要解决一个问题:
如何写一份与类型无关的通用代码,让编译器根据不同类型自动生成对应代码?
二、泛型编程
1. 为什么需要泛型编程?
如果要写交换函数,普通做法可能是函数重载:
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
问题:太麻烦了!
2. 泛型编程的思想
泛型编程:深刻理解与类型无关的通用代码!!!
编写与类型无关的通用代码,是代码复用的一种重要手段。
模板就是泛型编程的基础。可以把模板理解成一个“模具”:
模板 = 模具
类型 = 材料
实例化后的函数/类 = 具体成品
三、函数模板
1. 函数模板概念
函数模板代表一个函数家族。就是众多只是类型不同的函数里面的类型参数被抽象成了一个T!
2. 函数模板格式
template<class T>//class或者typename都可以,但是不能用 `struct`
template<typename T>
返回值类型 函数名(参数列表)
{
}
3. 通用 Swap 函数模板
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
调用:
int a = 1, b = 2;
Swap(a, b);
double x = 1.1, y = 2.2;
Swap(x, y);
编译器会根据实参类型生成:
void Swap(int& left, int& right);
void Swap(double& left, double& right);
注意哦,编译器会生成对应的函数,根据模版,但是是你要什么生成什么而不是全部生成!
四、函数模板的原理⭐️⭐️⭐️
1. 函数模板不是函数
函数模板本身不是函数,而是编译器生成具体函数的蓝图。编译期间,编译器会根据模板使用情况生成具体类型的函数。
例如:
Swap(a, b); // a、b 是 int
编译器推导出:
T = int
于是生成一份处理 int 的 Swap 函数。
2. 模板把重复工作交给编译器
原来需要程序员手写:
Swap(int&)
Swap(double&)
Swap(char&)
使用模板后:
程序员写一份模板,编译器按需生成多份具体函数。
五、函数模板实例化⭐️
用不同类型的参数使用函数模板,称为函数模板实例化。
分为:
1. 隐式实例化(常用)
让编译器根据实参类型推导模板参数。
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2); // T 推导为 int
Add(d1, d2); // T 推导为 double
2. 隐式实例化的类型冲突
Add(a1, d1);
如果模板是:
template<class T>
T Add(const T& left, const T& right)
编译器推导:
- 根据 a1 推导 T = int
- 根据 d1 推导 T = double
同一个 T 推导出两个类型,编译器无法决定,因此报错。
3. 解决方式一:强制类型转换
Add(a1, (int)d1);
此时两个实参都可以推导为 int。
4. 解决方式二:显式实例化
显式指定模板参数类型:
Add<int>(a1, d1);
表示强制实例化出 T = int 的版本。
如果类型不匹配,编译器会尝试隐式类型转换。
六、模板参数匹配原则
1. 普通函数和函数模板可以同名共存
int Add(int left, int right)
{
return left + right;
}
template<class T>
T Add(T left, T right)
{
return left + right;
}
2. 普通函数和模板都匹配时,优先普通函数
Add(1, 2);
如果普通函数完全匹配,编译器优先调用普通函数,而不是从模板生成一个实例。
3. 如果模板能生成更匹配的版本,会选择模板
例如:
int Add(int left, int right);
template<class T1, class T2>
T1 Add(T1 left, T2 right);
Add(1, 2.0);
普通函数需要把 2.0 转成 int。
模板可以生成:
Add(int, double)
更匹配,所以会选择模板。
4. 模板一般不主动做类型转换
重点:
在模板中,编译器一般不会进行类型转换操作,因为一旦转换出问题,编译器就需要背锅。
例如:
template<class T>
T Add(T left, T right);
Add(1, 2.0); // T 推导冲突,通常报错
除非你显式指定模板参数:
Add<int>(1, 2.0);
七、类模板
1. 类模板定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
示例:
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
2. 类模板成员函数在类外定义
类外定义成员函数时要写模板参数:
template<class T>
void Stack<T>::Push(const T& data)
{
_array[_size] = data;
++_size;
}
注意两点:
1. 前面要写:
template<class T>
2. 类名位置要写:
Stack<T>::
3. 模板不建议声明和定义分离到 .h 和 .cpp⭐️⭐️⭐️⭐️
在此强调:
模板不建议像普通函数那样声明放 .h、定义放 .cpp,否则容易出现链接错误。
当前阶段先记结论:
模板通常把声明和定义都放在头文件中。
后面深入模板编译原理时再理解原因。
八、类模板实例化
类模板实例化和函数模板不同。
函数模板可以根据实参自动推导:
Swap(a, b);
但类模板使用时通常需要显式写类型:
Stack<int> st1;
Stack<double> st2;
重点:
Stack 是类模板名,不是真正的类型;Stack<int> 才是真正的类型。
九、易错点汇总
易错点 1:把函数模板当成函数
函数模板不是函数,而是生成函数的模具。
易错点 2:模板参数中用 struct
正确:
template<typename T>
template<class T>
错误:
template<struct T>
易错点 3:同一个 T 被推导成不同类型
Add(1, 2.0);
如果模板只有一个 T,编译器无法同时让 T 是 int 和 double。
易错点 4:以为模板会自动类型转换
模板推导阶段通常不会主动做类型转换。
要么手动强转:
Add(1, (int)2.0);
要么显式实例化:
Add<int>(1, 2.0);
易错点 5:类模板实例化忘记写 <类型>
错误:
Stack st;
正确:
Stack<int> st;
易错点 6:类模板成员函数类外定义忘记 Stack<T>::
正确:
template<class T>
void Stack<T>::Push(const T& data)
{
}
十、本节总结
- 泛型编程:写与类型无关的通用代码。
- 模板是泛型编程的基础。
- 函数模板不是函数,是生成函数的蓝图。
- 模板实例化:编译器根据类型生成具体函数或类。
- 隐式实例化:编译器根据实参推导类型。
- 显式实例化:程序员用 <> 指定类型。
- 同一个模板参数不能同时推导成多个类型。
- 普通函数和模板都匹配时,普通函数优先。
- 模板能生成更匹配版本时,会选择模板。
- 模板一般不会在推导阶段主动做类型转换。
- Stack 是类模板名,Stack<int> 才是真正类型。
- 模板定义通常放头文件,不建议声明定义分离到 .h/.cpp。
二、STL 简介复习总结
本节内容偏了解,可直接跳转到总结部分!
一、本讲核心主线
这一讲主要是 STL 的入门介绍,重点不是具体使用细节,而是建立整体框架:
一句话概括:
STL 是 C++ 标准库的重要组成部分,它把常用数据结构和算法做成了可复用的软件框架,让我们不用重复造轮子。
二、什么是 STL
STL 全称:
Standard Template Library
标准模板库
它是 C++ 标准库的重要组成部分。
STL 不只是一个简单的组件库,而是:
包含数据结构与算法的软件框架。
它提供了大量通用组件,例如:
- 顺序表 / 动态数组:vector
- 链表:list
- 栈:stack
- 队列:queue
- 优先级队列:priority_queue
- 集合:set
- 映射:map
- 哈希表相关容器:unordered_map、unordered_set
- 通用算法:排序、查找、遍历等
这些内容也就是我们接下来要学习的内容!!
三、STL 的版本
本节我们介绍几个 STL 版本,复习时不需要死背细节,但要知道 SGI 版本比较重要。
1. 原始版本 / HP 版本
由:
- Alexander Stepanov
- Meng Lee
在惠普实验室完成。
特点:
- STL 的原始版本。
- 具有开源精神。
- 是所有 STL 实现版本的始祖。
2. P. J. 版本
由 P. J. Plauger 开发。
特点:
- 继承自 HP 版本。
- 被 Windows Visual C++ 采用。
- 不能公开或修改。
- 可读性较低,符号命名较怪。
3. RW 版本
由 Rouge Wage 公司开发。
特点:
- 继承自 HP 版本。
- 被 C++ Builder 采用。
- 不能公开或修改。
- 可读性一般。
4. SGI 版本
由 Silicon Graphics Computer Systems 公司开发。
特点:
- 继承自 HP 版本。
- 被 GCC / Linux 采用。
- 可移植性好。
- 可以公开、修改甚至贩卖。
- 命名风格和编程风格可读性较高。
重点:
后续学习 STL 源码时,主要参考 SGI 版本。
四、STL 的六大组件
STL 通常分为六大组件:目前只做了解,后面都会详细讲
| 容器 | 存储数据的数据结构 |
| 算法 | 操作数据的通用算法 |
| 迭代器 | 连接容器和算法,类似泛化指针 |
| 仿函数 | 像函数一样使用的对象 |
| 适配器 | 对已有组件进行包装转换 |
| 空间配置器 | 负责内存申请和释放 |
1. 容器
容器用于存储数据。
常见容器:
vector
list
deque
stack
queue
priority_queue
set
map
unordered_set
unordered_map
可以把容器理解为:
STL 中封装好的常用数据结构。
2. 算法
算法用于操作容器中的数据。例如:
sort
find
count
reverse
lower_bound
upper_bound
算法通常不直接依赖某一种容器,而是通过迭代器操作数据。
3. 迭代器
迭代器是 STL 中非常核心的概念。
作用:
连接容器和算法。
可以把迭代器先粗略理解为:
类似指针的东西,用来访问容器中的元素。
例如:
vector<int> v;
vector<int>::iterator it = v.begin();
后面学习 STL 时,迭代器是重点。
4. 仿函数
仿函数是像函数一样使用的对象。
本质通常是重载了 operator() 的类对象。
例如后续可能看到:
greater<int>()
less<int>()
当前阶段了解即可。
5. 适配器
适配器是对已有组件做包装,让它表现出另一种使用形式。
典型例子:
stack
queue
priority_queue
它们底层通常依赖已有容器实现,但对外提供特定接口。
例如:
- stack 提供栈接口。
- queue 提供队列接口。
- priority_queue 提供堆 / 优先级队列接口。
6. 空间配置器
空间配置器负责内存管理。
它是 STL 底层组件,主要负责:
- 空间申请
- 空间释放
- 对象构造
- 对象析构
当前阶段知道它是 STL 的底层内存管理组件即可。
五、本节总结⭐️⭐️
- STL = Standard Template Library,标准模板库。
- STL 是 C++ 标准库的重要组成部分。
- STL 是数据结构和算法的软件框架。
- STL 六大组件:容器、算法、迭代器、仿函数、适配器、空间配置器。
- 容器负责存数据。
- 算法负责处理数据。
- 迭代器负责连接容器和算法。
- 适配器是对已有组件的包装转换。
- 学习 STL 的三层境界:能用、明理、能扩展。
- 后续源码学习主要参考 SGI 版本。
- STL 不只是容器,还包括算法、迭代器、仿函数、适配器、空间配置器。
- stack、queue、priority_queue 更准确地说是容器适配器。
- 学 STL 不能只停留在会调接口,面试还要理解底层原理。
- 迭代器是连接容器和算法的核心,后续要重点掌握。
- SGI STL 版本可读性较高,后续源码学习主要参考它。
下期预告
string
结语
本文到此结束,感谢大家的阅读!如果觉得本文对你有所帮助,欢迎点赞、收藏、关注,也欢迎在评论区一起交流讨论。 也欢迎订阅我的 深入理解 C++系列:从语法入门到底层原理,系统掌握现代 C++ 算法系列:从入门到精通,蓝桥杯、ACM、LeetCode 与面试算法全路线 快速复习系列:知识梳理、查漏补缺,考前冲刺必备 Java 速通系列:已学 C 语言,快速上手 Java,轻松备战期末考试
愿每一次敲下键盘,都比昨天更进一步!
愿每一行代码落下,都让未来多一种可能!
网硕互联帮助中心





评论前必须登录!
注册