没有任何一款具有实用价值的软件是用“裸语言”写成的。人们通常会先开发一系列库,作为后续编程工作的基础。仅使用“裸语言”编写程序,注定是极其繁琐且乏味的,而善加利用事先编写好的库,编程工作会变得简单而高效。
C++标准库提供了众多工具和方法,它们基于一系列自定义的数据类型,如:string、ostream、variant、vector、map、path、unique_ptr、thread、regex、system_clock、time_zone、complex,等等。
在全面掌握标准库的所有细节之前,最好能对其中最常用的工具有一个基本了解。
在ISO C++的标准文本中,标准库规范占了将近三分之二的篇幅。在解决实际问题的过程中,应尽可能使用标准库的工具和方法,而不是自己定义功能相近的替代品。在标准库的设计与实现中,凝聚了大量多年积累并经过充分检验的思想。未来,还会有更多的人力物力,被投入到标准库的维护和扩展中。
标准库的工具和方法,固然是任何一个C++实现的必备部分。此外,大多数C++实现还提供了诸如图形用户界面(GUI)、网络编程、数据库访问等扩展功能。类似地,大多数基于C++语言的应用开发环境,还附带一套基础库,以满足企业级和工业级应用程序开发的需要。除此以外,还有成千上万的库,用于特定的专业领域,如流媒体、科学计算、机器视觉、人工智能等。当然,这些标准库以外库、系统和环境并不是这里关注的重点。这里讨论的内容,仅限C++标准定义的自包含描述,并确保其可移植性。在此基础上,进一步探索在大多数系统上可用的工具和方法,也是值得鼓励的。
标准库提供的工具和方法包括:
工具和方法 | 说明 |
---|---|
运行时支持 | 动态内存管理、异常、运行时类型检查等 |
C标准库 | 略有改动,以规避与类型系统的冲突 |
字符串 | 国际字符集、本地化、子字符串只读视图等 |
正则表达式 | 基于正则表达式的文本匹配 |
I/O流 | 输入输出和格式化控制 |
文件 | 以可移植的方式处理与具体文件系统无关的文件操作 |
容器和算法 | vector、map等容器和sort、find等算法,亦称标准模板库(STL) |
范围 | 视图、发生器、管道等 |
概念 | 与基本数据类型和范围有关的各种概念 |
数值计算 | 标准数学函数、向量计算、数学常数、随机数、复数 |
并发 | thread和锁机制 |
协程 | 同步协程和异步协程 |
并行 | 一部分数学算法和大多数泛型算法的并行版本 |
泛型工具 | 模板元编程工具(如类型特性等)、泛型程序设计工具(如pair等)、 通用程序设计工具(如variant、optional等) |
智能指针 | unique_ptr、shared_ptr等 |
特殊容器 | array、bitset、tuple等 |
绝对时间与时间段 | time_point、system_clock等 |
日历 | month、time_zone等 |
单位后缀 | 表示毫秒的“ |
元素序列 | 视图、string_view、span等 |
什么样的工具和方法才有资格入选标准库?
对几乎所有C++程序员(无论是初学者还是专家)都有用
在不过分增加额外开销的前提下,尽可能地保持通用性
易学易用(相对于编程任务的内在复杂性而言)
从本质上讲,C++标准库提供了最常用的基本数据结构,及针对这些数据结构的基础算法。
标准库的所有设施都被置于std命名空间中。用户可以导入模块或包含头文件的方式,引用其中的组件。
标准库中的每个工具和方法都有对应的头文件,例如:
xxxxxxxxxx
21
2
包含这两个头文件后,在程序中就可以使用string和list了。
标准库组件位于std命名空间中。使用其中的工具和方法,需要加上“std::”前缀,例如:
xxxxxxxxxx
51std::string sheep{ "Four legs Good; two legs Baaad!" };
2std::list<std::string> slogans{
3 "War is Peace",
4 "Freedom is Slavery",
5 "Ignorance is Strength" };
如果觉得每次引用标准库组件都要使用“std::”前缀过于繁琐,也可以借助using指令,使位于std命名空间中的名字在当前作用域中可见,这时“std::”前缀可以省略不写。例如:
xxxxxxxxxx
51// 使标准库中的string功能可用
2using namespace std; // 使std命名空间中的名字在不加“std::”的情况下可用
3...
4string s{ "C++ is a general-purpose programming language" }; // 这里的string就是std::string
5...
一般而言,将命名空间中的所有名字,都导入全局作用域,并不是一个好的编程习惯。但如果只使用std一个命名空间中的名字,这样做也无妨。
std命名空间中还有几个子命名空间:
std命名空间的子命名空间 | 说明 |
---|---|
std::chrono | 时间库 |
std::literals::chrono_literals | 时间后缀:y表示年、d表示日、h表示时、min表示分、s表示秒、ms表示毫秒、us表示微秒、ns表示纳秒 |
std::literals::complex_literals | 虚数后缀:i表示双精度虚数、if表示单精度虚数、il表示long long类型的虚数 |
std::literals::string_literals | 字符串后缀:s表示string类型的字符串 |
std::literals::string_view_literals | 字符串视图后缀:sv表示string_view类型的字符串视图 |
std::numbers | 数学常数 |
std::pmr | 多态内存资源 |
要使用子命名空间中的名字,必须将它们引入当前作用域。例如:
xxxxxxxxxx
11auto c = 1.2 + 3.4i; // 编译错误,后缀i没有定义
正确的写法是:
xxxxxxxxxx
31using namespace std::literals::complex_literals;
2...
3auto c = 1.2 + 3.4i; // c是complex<double>类型的对象
对于子命名空间中应该包含的内容,目前还缺乏统一连贯的理论指导。因此,为了避免歧义,使用后缀时,最好将相应的命名空间引入当前作用域。一个库如果定义了自用的后缀,通常需要将其定义在独立的命名空间中。
标准库提供的算法,如sort、copy等,有两个版本:
传统的接受双迭代器的版本,如:sort(begin(v), v.end())
较新的接受一个范围的版本,如:sort(v)
理想情况下,这两个版本应该基于参数表的差别,借助重载加以区分,但实际上却不可行。例如:
xxxxxxxxxx
51using namespace std;
2using namespace std::ranges;
3...
4sort(begin(v), v.end()); // 编译错误,歧义
5sort(v); // 编译错误,歧义
标准规定必须在作用域内显式指明范围版本的命名空间。例如:
xxxxxxxxxx
61using namespace std;
2...
3sort(begin(v), v.end()); // 迭代器版本
4ranges::sort(v); // 范围版本
5using ranges::sort;
6sort(v); // 范围版本
截至目前为止,还没有任何标准库模块,主要是因为C++标准化委员会缺乏时间,C++23可能会弥补这个遗漏。Microsoft Visual C++ 2022提供了基于C++23预览版的标准库模块——std。
下表罗列了部分标准库头文件,其中的声明都位于std命名空间中:
头文件 | 主要内容 |
---|---|
<algorithm> | copy、find、sort |
<array> | array |
<chrono> | duration、time_point、month、time_zone |
<cmath> | sqrt、pow |
<complex> | complex、sqrt、pow |
<concepts> | floating_point、copyable、predicate、invocable |
<filesystem> | path |
<format> | format |
<fstream> | fstream、ifstream、ofstream |
<functional> | function、greater_equal、hash、range_value_t |
<future> | future、promise |
<ios> | hex、dec、scientific、fixed、defaultfloat |
<iostream> | istream、ostream、cin、cout |
<map> | map、multimap |
<memory> | unique_ptr、shared_ptr、allocator |
<random> | default_random_engine、normal_distribution |
<ranges> | sized_range、subrange、take、split、iterator_t |
<regex> | regex、smatch |
<string> | string、basic_string |
<string_view> | string_view |
<set> | set、multiset |
<sstream> | istringstream、ostringstream |
<stdexcept> | length_error、out_of_range、runtime_error |
<tuple> | tuple、get、tuple_size |
<thread> | thread |
<unordered_map> | unordered_map、unordered_multimap |
<utility> | move、swap、pair |
<variant> | variant |
<vector> | vector |
此表远未囊括所有的标准库头文件。
C++标准库还提供了源自C标准库的头文件,如<stdlib.h>等。这类头文件都有一个对应的类似<cstdlib>的版本,其中的声明位于std命名空间中。
这些头文件反映了标准库的开发历程,因而并不象多数人所期待的那么富有逻辑,也不太易于记忆。这也是为什么需要一个std模块的原因。
永远不要重新发明轮子,尽可能使用现成的库才是明智之选
只要条件允许,优先选择标准库,而不是其它第三方库
不要迷信标准库在任何情况下都是理想之选
如果不使用模块,那就必须通过“#include”包含所需要的头文件
标准库中的所有工具和方法,都被定义在名为“std”的命名空间中
使用std::ranges命名空间中的名字时,必须显式指明该命名空间,不要妄图借助“using namespace”省略之
只要有可能,尽量使用“import”导入模块,而不是通过“#include”包含头文件