前言
在前面的博客中,我们系统学习了 类和对象(封装、继承、多态、构造/析构等),又深入探讨了 模板(函数模板、类模板、泛型编程基础)。从今天开始,我们将进入 C++ 标准库的核心领域——STL(Standard Template Library)。
1.STL
意义:可以说是C++的核心部分,它的存在让我们站在巨人的肩膀上,不必重复造轮子,你要的数组、链表、队列、排序、查找,标准库都帮你实现好了,性能顶尖,bug更少。用它,就是站在高手的肩膀上写代码。
2.编码表
意义:为下篇文章做准备,我们下篇将会探索第一个STL容器——string。在正式踏入string的世界之前,我们先聊一个看似基础却极其重要的话题——编码表(字符编码)。
3.C++11语法补充
现代C++能极大提高编程效率,这里仅先介绍两个现代C++的语法:auto关键字和范围for,介绍的意义和编码表一样,为下篇string的引入做铺垫。
目录
前言
(一)STL简介
1.什么是STL
2.STL的版本
原始版本
P. J. 版本
RW版本
SGI版本
3.STL的六大组件
1.容器(Containers)
2.迭代器(Iterators)
3.算法(Algorithms)
4.仿函数(Functors)
5.适配器(Adapters)
6.分配器(Allocators)
组件间协作关系
4.STL的重要性
(二)编码表
1.为什么需要编码表
2.编码与 std::string 的关系
3.常见的编码方案
常见乱码场景对比
(三)C++11语法补充
1.auto关键字
背景
使用说明
代码演示
2.范围for
背景
使用说明
代码演示
本篇小结
(一)STL简介
1.什么是STL
STL是C++标准库的重要组成部分,不仅是一个可以复用的组件库,还是一个包罗数据结构和算法的软件框架。
2.STL的版本
原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。
P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。
STL要阅读部分源代码,主要参考的就是这个版本。
小故事:
HP版本的开源声明极具前瞻性:允许任何人使用、修改、商用,唯一条件是衍生品也必须开源。这影响了后来的很多开源协议。
SGI版本之所以可读性高,是因为它采用了完整命名(如 `__STL::vector`)和详细注释
面试中偶尔会问“你是否读过 STL 源码?”——哪怕只读过 `vector` 的扩容机制或 `sort` 的内省排序实现,都会是加分项。
3.STL的六大组件
STL(Standard Template Library)是C++标准库的核心部分,提供了一系列通用的模板类和函数,主要包括以下六大组件:
1.容器(Containers)
容器就是数据结构,用于存储和管理数据的模板类,分为序列容器和关联容器:
序列容器:vector、list、string、array。
关联容器:set、multiset、map、multimap。
无序关联容器:unordered_set、unordered_map等(C++11引入)。
2.迭代器(Iterators)
提供访问容器元素的通用接口,分为五种类型:
输入迭代器(Input Iterator)
输出迭代器(Output Iterator)
前向迭代器(Forward Iterator)
双向迭代器(Bidirectional Iterator)
随机访问迭代器(Random Access Iterator)
3.算法(Algorithms)
封装了常用操作,如排序、查找、遍历等,通过迭代器与容器交互。例如:
sort():排序。
find():查找元素。
swap() / reverse().............
4.仿函数(Functors)
5.适配器(Adapters)
对容器或迭代器的接口进行转换,例如:
容器适配器:stack、queue、priority_queue。
迭代器适配器:反向迭代器reverse_iterator。
6.分配器(Allocators)
管理内存分配与释放的模板类,默认使用std::allocator。用户可自定义以实现特殊内存管理需求。
组件间协作关系
容器通过分配器管理内存。
算法通过迭代器操作容器元素。
仿函数和适配器扩展算法和容器的功能。
4.STL的重要性
1. 极大提升开发效率
开箱即用的数据结构与算法:无需重复实现链表、动态数组、栈、队列、哈希表、排序、查找等常用组件,代码量显著减少。
2. 保证性能与正确性
高度优化:STL 由顶尖 C++ 专家实现,性能通常优于自己编写的“轮子”。
经过严格测试:减少因边界条件、内存管理等问题引入的 bug。
3. 统一编程接口,降低学习成本
迭代器统一访问:算法与容器通过迭代器解耦,学会一套迭代器操作即可用于所有容器。
4. 是现代 C++ 编程的事实标准
任何 C++ 工程(游戏、后端、嵌入式、高频交易等)几乎都会使用 STL。
面试与笔试中,STL 使用熟练度是考察 C++ 基础能力的重要指标。
5. 奠定泛型编程与数据抽象的基石
STL 完美展示了“泛型编程”的威力:算法与容器分离,通过模板和迭代器连接。
未来去互联网公司找工作或找实习时,一般都需要一轮笔试多轮面试,笔试和面试中都极易考察STL,毕竟STL是C++的核心。网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。
(二)编码表
1.为什么需要编码表
计算机只认识二进制,而人类需要处理文字、符号。编码表就是一套“翻译规则”,把字符映射到数字,再转换为二进制存储或传输。
2.编码与 std::string 的关系
std::string 本身不关心编码——它只存储字节序列。这意味着你可以把任意编码的文本(如 UTF-8、GBK)放入 std::string,但如何解释这些字节(比如按字符遍历、获取字符数、大小写转换)需要你自己处理。
因此,s.size() 返回的是字节数,不是字符数;用下标修改字节可能破坏编码,造成乱码。
std::string 是 basic_string<char> 的别名。此外还有:
std::u16string(char16_t) → 通常用于 UTF-16 编码
std::u32string(char32_t) → 通常用于 UTF-32 编码
C++20 引入了 std::u8string(char8_t) → 专门用于 UTF-8 编码
但 std::string 始终不绑定任何编码,它只是一个字节容器。
类模板:basic_string
std::u16string和std::u32string分别是char16_t和char32_t的容器,通常用于存储UTF-16和UTF-32编码的文本。
3.常见的编码方案
ASCII:最基础的编码,用 7 位(后扩展为 8 位)表示英文字母、数字、标点。但它只能表示 128 个字符,连中文、日文等都无法处理。
GBK:中文环境下常用的多字节编码,用 1~2 个字节表示一个字符(英文 1 字节,中文 2 字节)。但不同国家和地区有各自的编码,容易产生乱码。
“GB”就是“国标”。
乱码:
void Test_String3() { string s1 = "引用"; s1[0]++; cout << s1 << endl; } int main() { Test_String3(); return 0; }在 UTF-8 环境下,“引”的第一个字节被 +1,破坏了 UTF-8 序列,输出乱码。即使是在 GBK 环境下,也会因破坏双字节结构而导致乱码。
常见乱码场景对比
| 存储编码 | 解释编码 | 结果 |
|---|---|---|
| GBK | UTF-8 | 乱码(如���) |
| UTF-8 | GBK | 乱码(如浣犲ソ) |
| UTF-8 | UTF-8 | 正常 |
Unicode:统一所有字符的编码标准,为全世界每个字符分配唯一的码点(如 U+4F60 表示汉字“你”)。Unicode 只定义码点,不规定存储方式。
UTF-8:Unicode 的一种变长存储实现,英文占 1 字节,中文占 3 字节,兼容 ASCII,是目前互联网最通用的编码。
(三)C++11语法补充
1.auto关键字
背景
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量。
C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
使用说明
1.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
3.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
4.auto不能直接用来声明数组
代码演示
int i = 0; int& r1 = i; // r2不是int&引用,是int auto r2 = r1; //r3是int& 引用 auto& r3 = r1;2.范围for
背景
对于一个又范围的集合而言,由程序员来说明循环的范围是很繁琐且多余的,有时候还会容易犯错。因此C++11引入了基于范围的for循环。
使用说明
1.for循环后的括号由冒号“:”分为两部分:冒号左边是范围内用于迭代的变量,冒号右边是被迭代的范围。 (自动取容器数据赋值,自动迭代++,自动判断结束)
2.支持迭代器的容器都可以使用范围for,且数组也支持(特殊处理)。
代码演示
//范围for 依次从容器里取值赋给ch,相当于拷贝一个临时变量 string str1("hello world"); for (char ch: str1) { cout << ch << " "; } cout << endl; //错误版本 string str1("hello world"); char ch; for (ch: str1) { cout << ch << " "; } cout << endl; //引用才能通过形参改变实参 for (auto& ch : str1) { ch--; } cout << str1 << endl;本篇小结
本篇作为 STL 系列的开篇,从宏观上介绍了 STL 的诞生背景、六大组件及其重要性,帮助你建立起对 C++ 标准模板库的整体认知。同时,为了后续深入 std::string 的使用,我们补充了编码表的基础知识,澄清了 std::string 只存储字节、不关心编码这一关键点,并列举了常见乱码场景及成因。最后,简单介绍了 auto 和范围 for 这两项现代 C++ 语法,它们将在后续操作字符串时频繁出现。
从下一篇开始,我们将正式进入 std::string 的成员函数详解,包括构造、容量、迭代器、元素访问、修改器以及字符串查找等操作。掌握 string,是学好其他 STL 容器的第一步。
创作不易,如果你觉得本文有帮助,欢迎点赞、收藏、评论,你的支持是我继续分享的最大动力~ 我们下期见!