第9章 库概览
第9章 库概览
• 简介
• 标准库组件
• 标准库组织
命名空间;
ranges 命名空间;
模块;
头文件
• 建议
9.1 简介
没有任何重要的程序是仅用裸编程语言编写的。首先,会开发一套库。这些库随后成为进一步工作的基础。大多数程序用裸语言编写都是繁琐的,而通过使用优秀的库,几乎任何任务都可以变得简单。
继第1至第8章之后,第9至第18章快速概述了关键标准库设施。我将简要介绍有用的标准库类型,如 string 、 ostream 、 variant 、 vector 、 map 、 path 、 unique_ptr 、 thread 、 regex 、 system_clock 、 time_zone 和 complex ,以及使用它们最常见的方法。
与第1至第8章一样,强烈建议您不要因对细节理解不全而分心或气馁。本章的目的是传达对最有用的库设施的基本理解。
标准库的规范占ISO C++标准的三分之二以上。探索它,并优先于自制的替代方案。在其设计上已经投入了大量的思考,其实施上更是如此,而且维护和扩展也将需要大量的努力。
本书中描述的标准库设施是每个完整的C++实现的一部分。除了标准库组件之外,大多数实现还提供“图形用户界面”系统(GUI)、Web接口、数据库接口等。类似地,大多数应用程序开发环境为公司或工业的“标准”开发和/或执行环境提供“基础库”。除此之外,还有数千个支持特定应用领域的库。在这里,我不会描述超出标准库之外的库、系统或环境。目的是提供一个由其标准[C++,2020]定义的C++自包含描述,并保持示例的可移植性。自然,鼓励程序员探索大多数系统上可用的更广泛设施。
9.2 设施
标准库提供的设施可以这样分类:
- 运行时语言支持(例如,用于分配、异常和运行时类型信息)。
- C标准库(进行了很小的修改以最小化类型系统违规)。
- 支持国际化字符集、本地化及子字符串的只读视图的字符串处理(§10.2)。
- 正则表达式匹配的支持(§10.4)。
- 输入输出流是一个可扩展框架,用户可以向其中添加自己的类型、流、缓冲策略、区域设置和字符集(第11章)。它还提供了灵活的输出格式化功能(§11.6.2)。
- 一个用于以可移植方式操作文件系统的库(§11.9)。
- 容器(如 vector 和 map ;第12章)和算法(如 find() 、 sort() 和 merge() ;第13章)的框架。这个传统上称为 STL 的框架[Stepanov,1994]是可扩展的,因此用户可以添加自己的容器和算法。
- 范围(§14.1),包括视图(§14.2)、生成器(§14.3)和管道(§14.4)。
- 基本类型和范围的概念(§14.5)。
- 对数值计算的支持,如标准数学函数、复数、具有算术运算的向量、数学常数和随机数生成器(§5.2.1和第16章)。
- 对并发编程的支持,包括 线程 和 锁 (第18章)。并发支持是基础性的,以便用户能够以库的形式添加对新并发模型的支持。
- 同步和异步协程(§18.6)。
- 大部分 STL 算法和一些数值算法(如 sort() (§13.6)和 reduce() (§17.3.1))的并行版本。
- 支持元编程(例如,类型函数;§16.4)、STL风格的泛型编程(例如, pair ;§15.3.3)和一般编程(例如, variant 和 optional ;§15.4.1, §15.4.2)的实用工具。
- 用于资源管理的“智能指针”(如 unique_ptr 和 shared_ptr ;§15.2.1)。
- 特殊用途容器,如 array (§15.3.1)、 bitset (§15.3.2)和 tuple (§15.3.3)。
- 支持绝对时间和持续时间,如 time_point 和 system_clock (§16.2.1)。
- 支持日历,如 month 和 time_zone (§16.2.2, §16.2.3)。
- 流行单位的后缀,如 ms 表示毫秒, i 表示虚部(§6.6)。
- 操纵元素序列的方法,如视图(§14.2)、 string_views (§10.3)和 spans (§15.2.2)。将一个类包含进库的主要标准是:
- 它几乎可以帮助每一位C++程序员(无论新手还是专家)。
- 它可以以一种通用的形式提供,相比同一设施的简化版本,不会增加显著的开销。
- 简单的使用应该易于学习(相对于他们任务固有的复杂度)。
实质上,C++标准库提供了最常见且基础的数据结构,以及在这些数据结构上使用的根本算法。
9.3 标准库组织结构
标准库的所有设施都被放置在名为 std 的命名空间中,并通过模块或头文件向用户开放。
9.3.1 命名空间
每个标准库设施都是通过某个标准头文件提供的。例如:
#include <string>
#include <list>
这使得标准的 string 和 list 可用。
标准库是在名为 std 的命名空间(§3.3)中定义的。要使用标准库设施,可以使用 std:: 前缀:
std::string sheep {"Four legs Good; two legs Baaad!"};
std::list<std::string> slogans {"War is Peace", "Freedom is Slavery", "Ignorance is Strength"};
为了简洁,在示例中我很少使用 std:: 前缀。我也不会明确地 #include 或 import 必要的头文件或模块。要编译和运行这里的程序片段,你必须使标准库的相关部分可用。例如:
#include <string> // 使标准字符串设施可访问
using namespace std; // 使std中的名字无需std::前缀即可使用
string s {"C++ is a general-purpose programming language"}; // 可行:string等同于std::string
通常情况下,将命名空间中的所有名字导入全局命名空间是不推荐的做法。然而,在本书中,我专一使用标准库,了解它提供的内容是有益的。
标准库向 std 提供了几个只能通过显式动作访问的子命名空间:
- std::chrono :chrono中的所有设施,包括 std::literals::chrono_literals (§16.2)。
- std::literals::chrono_literals :年份的 y 、天数的 d 、小时的 h 、分钟的 min 、毫秒的 ms 、纳秒的 ns 、秒的 s 以及微秒的 us 后缀(§16.2)。
- std::literals::complex_literals :虚部double的 i 、虚部float的 if 和虚部long double的 il 后缀(§6.6)。
- std::literals::string_literals :字符串的 s 后缀(§6.6, §10.2)。
- std::literals::string_view_literals :字符串视图的 sv 后缀(§10.3)。
- std::numbers :数学常数(§17.9)。
- std::pmr :多态内存资源(polymorphic memory resources)(§12.7)。
要使用子命名空间中的后缀,我们必须将其引入我们想要使用它的命名空间中。例如:
// 未提及complex_literals
auto z1 = 2+3i;// 错误:没有'i'后缀
using namespace literals::complex_literals; // 使复数字面量可见
auto z2 = 2+3i;// 正确:z2是一个complex<double>
对于什么应该放在子命名空间中,并没有一致的哲学。但是,后缀不能被明确限定,所以我们只能引入一组后缀到一个作用域中,以防引起歧义。因此,为打算与其他可能定义自己后缀的库一起工作的库设计的后缀,会被放置在子命名空间中。
9.3.2 ranges命名空间
标准库提供了两种版本的算法,比如 sort() 和 copy() :
- 传统的序列版本,接受一对迭代器;例如, sort(begin(v),v.end())
- 范围版本,接受单一范围;例如, sort(v)
理想情况下,这两个版本应能完美重载,无需特殊努力。然而,事实并非如此。例如:
using namespace std;
using namespace ranges;
void f(vector<int>& v) {
sort(v.begin(), v.end()); // 错误:二义性
sort(v); // 错误:二义性
}
为了避免在使用传统无约束模板时产生的二义性,标准要求我们显式地将标准库算法的范围版本引入作用域:
using namespace std;
void g(vector<int>& v) {
sort(v.begin(), v.end()); // OK
sort(v); // error: no matching function (in std)
ranges::sort(v); // OK
using ranges::sort; // 从这里开始,sort(v)就正确了
sort(v); // OK
}
通过这种方式,我们能够明确区分并正确调用传统迭代器版本和范围版本的算法。
9.3.3 模块
目前还没有标准库模块。C++23很可能会弥补这一遗漏(这是由于委员会时间不足造成的)。目前,我使用的模块名为 std ,这很可能会成为标准,它提供了 std 命名空间中的所有设施。详情参见附录A。
9.3.4 头文件
以下是一些标准库头文件的选摘,它们都在 std 命名空间中提供声明:
<algorithm> | copy(), find(), sort() | Chapter 13 |
<array> | array | §15.3.1 |
<chrono> | duration, time_point, month, time_zone | §16.2 |
<cmath> | sqrt(), pow() | §17.2 |
<complex> | complex, sqrt(), pow() | §17.4 |
<concepts> | floating_point, copyable, predicate, invocable | §14.5 |
<filesystem> | path | §11.9 |
<format> | format() | §11.6.2 |
<fstream> | fstream, ifstream, ofstream | §11.7.2 |
<functional> | function, greater_equal, hash, range_value_t | Chapter 16 |
<future> | future, promise | §18.5 |
<ios> | hex, dec, scientific, fixed, defaultfloat | §11.6.2 |
<iostream> | istream, ostream, cin, cout | Chapter 11 |
<map> | map, multimap | §12.6 |
<memory> | unique_ptr, shared_ptr, allocator | §15.2.1 |
<random> | default_random_engine, normal_distribution | §17.5 |
<ranges> | sized_range, subrange, take(), split(), iterator_t | §14.1 |
<regex> | regex, smatch | §10.4 |
<string> | string, basic_string | §10.2 |
<string_view> | string_view | §10.3 |
<set> | set, multiset | §12.8 |
<sstream> | istringstream, ostringstream | §11.7.3 |
<stdexcept> | length_error, out_of_range, runtime_error | §4.2 |
<tuple> | tuple, get<>(), tuple_size<> | §15.3.4 |
<thread> | thread | §18.2 |
<unordered_map> | unordered_map, unordered_multimap | §12.6 |
<utility> | move(), swap(), pair | Chapter 16 |
<variant> | variant | §15.4.1 |
<vector> | vector | §12.2 |
该列表远未完成。
C标准库中的头文件,例如 <stdlib.h> ,都会提供。对于每个这样的头文件,都存在一个版本,其名称以前缀 c 开头并去掉了 .h 。这样的版本,比如 <cstdlib> ,会将其声明放在 std 和全局命名空间中。
这些头文件反映了标准库开发的历史。因此,它们并不总是如我们所期望的那样逻辑清晰和易于记忆。这是使用模块(例如 std (§9.3.3))的一个原因。
9.4 建议
- 不要重复造轮子;使用库;§9.1;[CG: SL.1.]
- 当有选择时,优先选择标准库而非其他库;§9.1;[CG: SL.2]。
- 不要认为标准库对所有事情都是最理想的;§9.1。
- 如果不使用模块,请记住 #include 相应的头文件;§9.3.1。
- 记住,标准库设施定义在 std 命名空间中;§9.3.1;[CG: SL.3]。
- 使用 ranges 时,请记住显式指定算法名称;§9.3.2。
- 优先导入模块而不是 #include 头文件(§9.3.3)。