C++20开发环境与核心特性
C++20 Recipes
一种问题‐解决方案的方法 第二版
J. 伯顿·布朗宁 布鲁斯·萨瑟兰

第 1 章: C语言入门++
配方1‐1. 寻找文本编辑器
问题
您需要一个文本编辑器来编写 C++ 源代码。
解决方案
有许多优秀的文本编辑器可供选择,例如 Sublime Text、Visual Studio Code 或 Notepad++。
工作原理
文本编辑器是编写代码的基础工具。一个好的编辑器应具备语法高亮、自动补全等功能,以提高开发效率。图1-1 展示了 Sublime Text 编辑器的界面,其中 include 、 int 和 return 等关键字被高亮显示,有助于快速识别代码结构。
注意:不必急于立即抓起一个文本编辑器。本章节后面的某些食谱涵盖了集成开发环境(IDEs),其中包含了编写、构建和调试 C++ 应用程序所需的所有软件。究竟哪一种最适合你,这完全取决于用户偏好和平台。可以尝试使用文本编辑器以及一个(或两个!)功能全面的 IDE,看看哪种更符合你的工作流程。最终你可能会同时使用多个工具。
配方 1‐2. 在Ubuntu上安装Clang
问题
您希望在运行 Ubuntu 的计算机系统上构建支持最新 C++ 语言特性的程序。
解决方案
Clang编译器支持所有最新的C++20语言特性,而libstdc++库支持所有C++20的 STL特性。
工作原理
Ubuntu操作系统已配置好软件包仓库,使您能够轻松安装Clang。您可以在终端窗口中使用 apt-get命令来实现此操作。图 1-2 显示了您应输入的用于安装Clang的命令。
要安装Clang,您可以在命令行中输入以下命令: sudo apt-get install clang 。运行此命令将使Ubuntu查询其软件仓库,并计算出安装Clang所需的所有依赖项。此过程完成后,系统将提示您确认是否希望安装 Clang及其依赖项。您可以在图 1-3 中看到此提示。


此时,您可以按回车键继续,因为默认选项为“是”。Ubuntu 将下载并安装在您的计算机上安装 Clang 所需的所有软件。您可以通过运行 clang命令来确认此操作是否成功。图 1-4 展示了如果一切顺利,结果应呈现的样子。

配方 1‐3. 在 Windows 上安装 Clang
问题
您希望在Windows操作系统上构建基于C++20的程序。
解决方案
你可以使用适用于 Windows 的 Cygwin 来安装 Clang 并构建应用程序。
工作原理
Cygwin 为 Windows 计算机提供了类 Unix 命令行环境。这对于使用 Clang 构建程序非常理想,因为安装的 Cygwin 已预先配置了包含所有必要内容的软件包仓库,以便在 Windows 计算机上安装和使用 Clang。
您可以从Cygwin网站www.cygwin.com获取Cygwin安装程序可执行文件。请务必下载32位版本的Cygwin安装程序,因为Cygwin目前提供的默认包仅适用于32位环境。
下载安装程序后,应运行它并单击下一步,直到出现要安装的包列表。此时,你需要选择Clang、make和libstdc++包。图 1-5 显示了已选中Clang包的Cygwin安装程序。
在安装程序中,可以通过单击包所在行的“跳过”区域来标记要安装的包。单击一次“跳过”会将包版本更改为最新。您应为 Clang、make 和 libstdc++ 选择最新的包。在选中这三个包后,可以单击“下一步”,进入一个窗口,提示确认安装这三个包所需的依赖项。
成功下载并安装所有需要的包以运行Clang后,可以通过打开Cygwin终端并输入clang命令来检查是否成功。您可以在图1-6中看到此输出的结果。


配方 1‐4. 在macOS上安装Clang
问题
你希望在运行 macOS 的计算机上构建基于 C++20 的程序。
解决方案
Apple 的 Xcode 集成开发环境默认使用 Clang 作为其编译器。从 macOS 的 App Store 安装 Xcode 时,也会同时安装 Clang。但请注意,您的 macOS 需要保持最新版本才能成功安装 Xcode。在安装最新更新之前,请确保您的计算机拥有足够的内存!
工作原理
在您的 macOS 计算机上的 App Store 中安装最新版本的 Xcode。安装 Xcode 后,您可以使用 Spotlight 打开一个终端窗口,并输入 clang 以确认编译器已安装。图 1-7 显示了应有的样子。

配方1‐5. 构建你的第一个 C++程序
问题
您希望使用计算机将编写的C++源代码生成可执行应用程序。
解决方案
从C++源文件生成可执行文件包括两个步骤:编译和链接。按照配方 1‐2、配方 1‐3 或 配方 1‐4 中的步骤操作(取决于你的操作系统),你将获得从C++20源文件构建应用程序所需的所有软件。现在,你已准备好构建你的第一个C++20程序。创建一个文件夹来存放你的项目,并添加一个名为 HelloWorld.cpp 的文本文件。将 清单 1-1 中的代码输入该文件并保存。
清单 1-1. 您的第一个 C++20程序
#include<iostream>
#include<string>
int main(void)
{
using namespace std::string_literals;
auto output="Hello World!"s;
std::cout<< output<< std::endl;
return 0;
}
列表 1-1 中的代码是一个 C++程序,仅当使用支持 C++14 或更高版本的编译器时才能编译。本章中的配方 1‐2 到 1‐4 包含了如何获取可在 Windows、Ubuntu 和 macOS 上用于编译大多数(截至 2019年)提议的 C++20代码特性的编译器的说明。
在创建包含列表 1-1 代码的文件夹和源文件后,你可以通过 Makefile 构建一个可运行的应用程序。请在与 HelloWorld.cpp 文件相同的文件夹中创建一个名为 makefile 的文件。该 makefile 不应带有文件扩展名,这对于习惯于 Windows操作系统的开发者来说可能有些奇怪,但在 Linux 和 macOS 等基于 Unix 的操作系统上这是完全正常的。将列表 1-2 中的代码输入到你的 makefile 中。
清单 1-2。构建 清单1-1 中的 代码 所需的 Makefile
HelloWorld: HelloWorld.cpp
clang++ -g -std=c++1y HelloWorld.cpp -o HelloWorld
注意:命令前的空白字符为制表符。你不能用空格替换该制表符,否则 clang++命令在清单 1-2 中将无法构建。请确保你的 Makefile 中的 recipes 始终以制表符开头。
列表 1-2 中的文本包含从 HelloWorld.cpp 源文件构建应用程序所需的说明。
第一行的第一个词是 Makefile 的目标名称。这是在构建过程完成之后可执行文件将被赋予的名称。在本例中,我们将构建一个名为 HelloWorld 的可执行文件。其后是构建程序所需的前提条件。此处您列出了 HelloWorld.cpp 作为唯一先决条件,因为它是用于构建可执行文件的唯一源文件。
目标和先决条件之后,跟着一个按顺序执行的配方列表,用于构建你的应用程序。在这个小示例中,你有一行代码调用Clang++编译器,从 HelloWorld.cpp生成可执行代码文件。传递给 clang++ 的参数使用 –std=c++1y 要求 Clang 使用 C++14 语言标准进行构建,而 –o 开关则指定编译过程生成的对象输出文件的名称。
使用命令行外壳(如Windows上的cmd或Linux/macOS上的终端),浏览到你创建的用于存储源文件和Makefile的文件夹,然后输入make。这将调用GNU make程序,并自动读取并执行你的Makefile。这会在同一文件夹中生成一个可执行文件,你可以从命令行运行它。你现在就可以执行此操作,并看到命令行输出文本 Hello World。图1-8展示了在Ubuntu终端窗口中的效果。

配方 1‐6. 在 Cygwin 或 Linux 中使用 GDB 调试 C++程序
问题
你正在编写一个C++20程序,并希望可以从命令行调试该应用程序。
解决方案
Windows 的 Cygwin 和基于 Linux 的操作系统(如 Ubuntu)都可以为 C++ 应用程序安装并使用 GDB 命令行调试器。
工作原理
您可以使用 Windows 的 Cygwin 安装程序或您喜欢的 Linux 发行版附带的包管理器来安装 GDB 调试器。这将为您提供一个命令行 C++调试器,可用于检查您的 C++程序的功能。您可以使用配方1‐5中生成的源文件、Makefile 和应用程序来练习此操作。为了为您的程序生成调试信息,您应更新 Makefile 以包含清单 1-3 中的内容,并运行 make 命令以生成可调试的可执行文件。
清单1-3. 一个用于生成可调试程序的Makefile
HelloWorld: HelloWorld.cpp
clang++ -g -std=c++1y HelloWorld.cpp -o HelloWorld
按照配方1‐5操作后,更新Makefile以包含清单1-3中的内容,并生成可执行文件后,你可以通过命令行浏览到该文件夹并输入gdb HelloWorld来在应用程序上运行GDB。在清单1-3的Makefile中传递给Clang的新–g选项会要求编译器在应用程序中生成额外的信息,以帮助调试器在程序于调试器中执行时为你提供准确的程序信息。
注意:如果之前已构建过,您可能会收到通知,提示您的程序已是最新版本。此时,只需删除现有的可执行文件即可。
在HelloWorld中运行GDB命令应导致命令行运行GDB并提供如图1-9所示的输出。

现在,您已拥有一个正在运行的调试器,可用于在程序执行期间检查程序。
GDB刚启动时,程序尚未开始运行。这允许您在开始之前配置一些断点。要设置断点,可以使用 break命令或该命令的 b简写形式。在GDB命令提示符中输入 break main并按回车键。GDB应会将该命令回显给您,并显示断点设置的程序地址以及检测到的函数对应的文件名和行号。现在,您可以在窗口中输入run命令以执行程序,GDB将在您的断点处暂停。输出结果应如图 1-10所示。

此时,您有几种选择可以继续执行程序。您可以在以下内容中看到最常用命令的列表:
- step : step命令用于步入在当前行将要调用的函数。
- next : next命令用于跳过当前行并停止在同一函数的下一行。
- finish : finish命令用于执行当前函数中剩余的所有代码,并在调用当前函数的函数的下一行停止。
- print : print命令后跟变量名称,可用于打印程序中变量的值。
- break : break命令可以与行号、函数名或源文件和行号一起使用,以在程序的源代码中设置断点。
- continue : continue命令用于在代码执行因断点而暂停后恢复执行。
- until : until命令可以从循环继续执行,并在循环执行结束后立即停在下一行。
- info : info命令可以与locals命令或stack命令一起使用,以显示有关程序中当前局部变量或堆栈状态的信息。
- help : 你可以输入help ,后跟任何命令,让GDB提供有关该命令各种用法的信息。
GDB调试器也可以通过 –tui 命令运行。这将在窗口顶部显示你当前正在调试的源文件。你可以参见图 1-11 的效果。

配方 1‐7. 在 macOS 上调试您的 C++ 程序
问题
macOS 操作系统没有提供任何简便的方法来安装和使用 GDB。
解决方案
Xcode附带了LLDB调试器,可在命令行中替代GDB使用。
工作原理
LLDB调试器本质上与配方 1‐6 中使用的GDB调试器非常相似。在GDB和LLDB之间切换,只需学会如何使用各自提供的命令来完成相同的简单任务即可。
您可以通过在终端中浏览到包含HelloWorld的目录并键入lldb HelloWorld来在HelloWorld可执行文件上运行LLDB。这将给出类似于图1-12的输出。
注意:您需要使用 –g开关来编译您的程序。如果您不确定 this 的位置, 请查看 1-3 列表。
一旦你启动了LLDB,如图 1-12所示,你可以通过输入 breakpoint set –f HelloWorld.cpp –l 8或使用其简写形式 b main在 main 函数的第一行设置断点。你可以使用 run命令开始执行程序,并使其在你刚刚设置的断点处暂停。当程序停止时,你可以使用 next命令单步执行当前行,并在下一行暂停。你也可以使用 step命令进入当前行调用的函数,并在该函数的第一行暂停。而 finish 命令将从当前函数中跳出。
您可以通过输入 q 并按回车来退出 LLDB。重启 LLDB 并输入 breakpoint set –f HelloWorld.cpp –l 9。接着使用 run命令,LLDB 将会打印应用程序停止位置附近的源代码。

现在您可以输入 print output 来查看 output 变量存储的值。您还可以使用 frame variable 命令查看当前栈帧中的所有局部变量。
这些简单的命令将使您能够充分使用 LLDB 调试器来学习本书附带的示例。以下列表可作为使用 LLDB 时的便捷速查表:
- step : 使用 step 命令可以单步进入当前行将要调用的函数。
- next : 使用 next 命令可以跳过当前行,停在该函数的下一行代码上。
- finish : 使用 finish 命令可以执行当前函数中剩余的所有代码,并停在调用该函数的函数的下一行。
- print : 使用 print 命令后跟变量名称,可以打印程序中变量的值。
- breakpoint set –-name breakpoint set –file –line : 使用 breakpoint 命令并指定行号、函数名或源文件及行号,可以在程序的源代码中设置断点。
- help : 你可以输入 help ,后跟任意命令,让 GDB 向你提供有关该命令所有不同用法的信息。
配方 1‐8. 切换 C++编译模式
问题
你可能希望在编译程序之前能够在不同的 C++标准之间进行切换。
解决方案
std switch 由 Clang 提供,以便您在编译时指定要使用的 C++标准。
工作原理
Clang 默认使用 C++98标准进行构建。你可以使用 std 参数与 clang++配合,告诉编译器使用非默认的其他标准。清单 1-4 展示了一个配置为使用 C++17标准构建程序的 Makefile。Clang 5 默认支持 C++17。对于 C++版本20,请使用 2a 模式。
清单 1-4. 使用 C++17 构建
HelloWorld: HelloWorld.cpp
clang++ -std=c++17 HelloWorld.cpp -o HelloWorld
示例 1-4 中的 Makefile 展示了如何指定 Clang 使用 C++17 来构建你的源文件。本示例使用 Clang 5 编写,该版本使用 c++17 命令表示 C++17。对于 Clang 5 中 C++20(2a) 的实验性模式支持,请使用选项 -std=c++2a.
列表 1-5 展示了如何使用 C++11 构建一个程序。
清单 1-5. 使用 C++11 构建
HelloWorld: HelloWorld.cpp
clang++ -std=c++11 HelloWorld.cpp -o HelloWorld
在列表 1-5中,您需要将 c++11选项与 std开关结合使用以用 C++11进行构建。
最后,列表 1-6展示了如何配置 Clang 以显式使用 C++98进行构建。
清单 1-6. 使用 C++98 构建
HelloWorld: HelloWorld.cpp
clang++ -std=c++98 HelloWorld.cpp -o HelloWorld
清单 1-6 中的 Makefile 可用于显式使用 C++98 进行构建。你也可以完全省略 std 命令,Clang 将默认使用 C++98 进行构建。
注意:不能保证每个编译器都会默认使用 C++98 。如果不确定默认标准是哪个,请查阅编译器的文档。您也可以使用 Clang 来支持许多 C++20 特性,并通过前述文本中提到的命令启用其实验性的 C++20(2a) 支持。
配方 1‐9. 使用 Boost 库进行构建
问题
你想使用Boost库编写一个程序。
解决方案
Boost 以源代码形式提供,可包含在您的应用程序中并进行编译。
工作原理
Boost是一个大型C++库,包含各种出色的功能。本书不涵盖整个库的内容;但是,将使用字符串格式化库。您可以从以下位置的Boost网站获取Boost库: www.boost.org/。
你可以从Boost网站下载包含最新版本Boost库的压缩文件夹。为了包含基本的 Boost功能,你唯一必需的文件夹就是boost文件夹本身。下载当前版本的Boost,即 Boost 1.71.0,并在项目文件夹内创建一个名为 boost_1_xxx_x(其中x为版本号) 的文件夹,然后将下载版本中的boost文件夹拷贝到该位置。
第2章 现代 C++
C++编程语言的开发始于1979年的带类的C语言。C++这一名称于1983年被正式采用,在整个80年代和90年代,该语言的开发持续进行,但一直没有正式的语言标准。这种情况在1998年发生了改变,当时C++编程语言的第一个ISO标准被采纳。自那时以来,该标准已发布过多个更新版本,分别在2003年、2011年和2014年;跳过到最新版本,截至2019年,C++20正迅速接近最终定型和采用。
注意:2003年发布的标准是对1998年标准的一次小幅更新,几乎没有引入新的特性。但会提及C++17的一些特性以及C++20的部分特性。
本书将主要关注最新的C++编程标准C++20。每当提到C++编程语言时,你可以确信我们讨论的是当前ISO标准所描述的语言。如果讨论的是2011年引入的特性,则会显式地称为C++11版本;而对于2011年之前引入的任何特性,我将使用名称C++98;以此类推。
本章节将探讨在最新标准以及C++20中为该语言添加的编程特性。C++的许多现代特性是在C++11和C++17标准中引入的,并随着C++20提出的新增内容而进一步扩展。鉴于这一点,在使用尚未完全支持某一标准的编译器时,能够识别其差异显得尤为重要。事实上,这正是为什么截至2019年底,VS 2019及后续版本被特别注明的原因——它们尚未100%支持所有C++ 20提议的更新。
配方 2‐1. 初始化变量
问题
您希望能够以标准方式初始化所有变量。
解决方案
统一初始化是在 C++11 中引入的,可用于初始化任何类型的变量。
工作原理
有必要了解C语言中变量初始化的缺陷++98 ,以便理解为什么统一初始化是 C++语言中的一个重要特性++11。清单 2-1 展示了一个包含单个类的程序, MyClass。
清单 2-1. C++最令人困扰的解析问题
class MyClass {
private:
int m_Member;
public:
MyClass()= default;
MyClass(const MyClass& rhs)= default;
};
int main()
{
MyClass objectA;
MyClass objectB(MyClass());
return 0;
}
代码清单 2-1 中的代码会在 C++程序中产生编译错误。问题出在对 objectB 的定义上。C++编译器不会将此行视为定义了一个名为 objectB 、类型为 MyClass 并调用构造函数来构造一个由 MyClass 构造函数创建的对象的变量。你可能期望编译器会这样理解,但实际上它将其视为一个函数声明。编译器认为这一行是在声明一个名为 objectB 、返回 MyClass 对象的函数,并且该函数只有一个参数,即一个指向无参数且返回 MyClass 对象的未命名函数指针。
编译2-1 中所示的程序会导致 Clang 和 Visual Studio 生成一个警告。
Clang 的警告如下,Visual Studio 类似:
main.cpp:14:20: warning: parentheses were disambiguated as a function declaration[-Wvexing-parse] MyClass objectB(MyClass()); ^~~~~~~~~~~ main.cpp:14:21: note: add a pair of parentheses to declare a variable MyClass objectB(MyClass()); ^ ( )
Clang编译器已正确识别出2-1列表中输入的代码存在令人困扰的解析问题,甚至 helpful 地建议将作为参数传递的MyClass构造函数用另一对括号包裹起来以解决该问题。C++11通过统一初始化提供了另一种解决方案。你可以在2-2列表中看到这一点。
清单 2-2. 使用统一初始化解决令人困扰的解析问题
class MyClass {
private:
int m_Member;
public:
MyClass()= default;
MyClass(const MyClass& rhs)= default;
};
int main()
{
MyClass objectA;
MyClass objectB{MyClass{}};
return 0;
}
你可以在清单 2-2 中看到,统一初始化使用花括号替换了圆括号。这种语法变化会通知编译器,你希望使用统一初始化来初始化变量。统一初始化可用于初始化几乎所有类型的变量。
注意:前一段落提到,统一初始化可用于初始化 几乎 所有变量。在初始化聚合类型或普通旧数据类型时可能会出现问题;不过,目前你无需担心这些情况。
能够防止窄化转换是使用统一初始化的另一个优点。在使用统一初始化时,清单 2‐3中的代码将无法编译。
列表 2-3. 使用统一初始化防止窄化转换 s
int main()
{
int number{ 0};
char another{ 512};
double bigNumber{ 1.0};
float littleNumber{ bigNumber};
return 0;
}
Clang编译器以及VS编译器在编译列表2-3中的代码时都会报错,因为源代码中存在两个窄化转换。第一个窄化转换发生在尝试使用字面值 512 定义 char 变量时。char 类型最多只能存储最大值 255;因此,值 512 将会被窄化为该数据类型。
C++20 Recipes
一种问题‐解决方案的方法 第二版
J. 伯顿·布朗宁 布鲁斯·萨瑟兰
配方 2‐2. 使用初始化列表初始化对象
问题
您希望使用初始化列表来构造对象。
解决方案
C++11 引入了统一初始化语法,允许使用大括号 {} 进行构造和赋值。
工作原理
初始化列表提供了一种清晰、一致的方式来初始化对象,尤其是在处理聚合类型或类成员时。例如:
struct Point {
int x, y;
};
Point p1{10, 20}; // 使用初始化列表
这种方法不仅适用于基本数据类型,也适用于复杂对象和容器。
配方 2‐3. 使用类型推导
问题
您希望让编译器自动推导变量的类型,以简化代码并提高可读性。
解决方案
使用 auto 关键字可以让编译器在编译时推导出变量的实际类型。
工作原理
auto 是 C++11 中引入的关键字,它基于初始化表达式自动确定变量类型。例如:
auto i = 42; // i 被推导为 int
auto d = 3.14; // d 被推导为 double
auto str = "hello"; // str 被推导为 const char*
这在迭代器和模板编程中特别有用,可以显著减少冗长的类型声明。
配方 2‐4. 在函数中使用 auto
问题
您希望在函数返回类型中使用类型推导,特别是在返回复杂类型时。
解决方案
C++14 支持使用 auto 作为函数返回类型,并由编译器推导实际返回类型。
工作原理
从 C++14 开始,你可以这样写函数:
auto add(int a, int b) {
return a + b;
}
编译器会根据 return 语句推导出返回类型为 int 。对于更复杂的表达式(如 lambda 或模板),这种机制尤为强大。
配方 2‐5. 处理编译时常量
问题
您希望定义在编译期间就能确定值的常量,以便进行优化和元编程。
解决方案
使用 constexpr 关键字声明可在编译期求值的变量或函数。
工作原理
constexpr 变量必须在编译时初始化,且其值可用于数组大小、模板参数等场景:
constexpr int square(int x) {
return x * x;
}
constexpr int val = square(5); // 编译期计算,val == 25
int arr[val]; // 合法:val 是编译期常量
这比 const 更严格,确保了真正的编译期求值。
配方 2‐6. 使用 Lambda 表达式
问题
您希望在代码中内联定义小型匿名函数,以简化算法调用(如 std::sort )。
解决方案
使用 C++11 引入的 lambda 表达式语法直接在作用域内创建可调用对象。
工作原理
Lambda 表达式的语法如下:
[capture](parameters) -> return_type { body }
示例:
#include <algorithm>
#include <vector>
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 降序排序
});
捕获列表 [=] 表示按值捕获外部变量, [&] 表示按引用捕获。
配方 2‐7. 处理时间
问题
您需要测量程序执行时间或处理日期/时间逻辑。
解决方案
使用 <chrono> 库提供的高精度时钟和时间点功能。
工作原理
C++11 引入了 <chrono> ,支持多种时钟类型(如 steady_clock , high_resolution_clock ):
#include <chrono>
#include <thread>
auto start = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "耗时: " << duration.count() << " 微秒\n";
该库提供了强大的时间单位转换和精度控制能力。
配方 2‐8. 理解左值和右值引用
问题
您希望理解 C++11 中的右值引用,以实现移动语义和完美转发。
解决方案
使用 T&& 表示右值引用,结合 std::move 实现资源的高效转移。
工作原理
- 左值(lvalue) :有名字、可取地址的对象。
- 右值(rvalue) :临时对象或字面量。
右值引用允许绑定到临时对象,从而避免不必要的拷贝:
std::string createString() {
return "temporary"; // 返回临时对象
}
void processString(std::string&& s) {
// s 是右值引用,可以直接“移动”资源
}
processString(createString()); // OK: 绑定到右值
std::move 将左值强制转为右值引用,触发移动构造:
std::string a = "hello";
std::string b = std::move(a); // a 的内容被“移动”给 b,a 处于有效但未定义状态
配方 2‐9. 使用托管指针
问题
您希望避免手动管理动态内存,防止内存泄漏。
解决方案
使用智能指针 std::unique_ptr 、 std::shared_ptr 和 std::weak_ptr 自动管理生命周期。
工作原理
-
std::unique_ptr<T>:独占所有权,离开作用域时自动释放。 -
std::shared_ptr<T>:共享所有权,引用计数为零时释放。 -
std::weak_ptr<T>:弱引用,不增加引用计数,用于打破循环引用。
示例:
#include <memory>
auto ptr1 = std::make_unique<int>(42); // 唯一拥有
std::shared_ptr<int> sptr = std::move(ptr1); // 转为共享
{
auto copy = sptr; // 引用计数 +1
} // copy 离开作用域,引用计数 -1
// sptr 仍有效
std::weak_ptr<int> wptr = sptr;
if (auto locked = wptr.lock()) {
std::cout << *locked << "\n";
} else {
std::cout << "对象已被释放\n";
}
第3章 处理文本
配方 3‐1. 使用字面量在代码中表示字符串
问题
调试程序时,输出文本通常很有用。为此,C++允许你将字符串直接嵌入到代码中。
解决方案
C++程序具有一个称为字符串表的概念,程序中的所有字符串字面量都包含在程序的可执行文件中。
工作原理
一个标准的C++字符串字面量很容易使用。下面的列表 3-1 展示了创建字符串字面量的代码。
清单 3-1. 一个字符串字面量
#include<iostream>
#include<string>
using namespace std;
namespace {
const string STRING{"This is a string"s};
}
int main()
{
cout << STRING << endl;
return 0;
}
本示例中的字符串字面量是包含在引号内并后跟字母 s 的句子。编译器将在编译期间创建一个字符串表,并将它们全部放在一起。

配方 3‐2. 本地化面向用户的文本
问题
您的程序需要支持多语言用户界面。
解决方案
使用标准库或第三方库(如 gettext)实现文本本地化。
工作原理
通过将用户可见文本分离到资源文件中,程序可根据系统区域设置加载对应语言版本。虽然 C++ 标准库对此支持有限,但可通过 std::locale 和 facet 进行扩展。
配方 3‐3. 从文件读取字符串
问题
您需要从磁盘文件中读取文本内容。
解决方案
使用 std::ifstream 打开文件,并结合 std::getline 或流操作符读取内容。
工作原理
#include <fstream>
#include <string>
std::ifstream file("data.txt");
std::string line;
while (std::getline(file, line)) {
std::cout << line << "\n";
}
确保检查文件是否成功打开,并处理编码问题(如 UTF-8)。
配方 3‐4. 从 XML 文件读取数据
问题
您希望解析 XML 格式配置文件。
解决方案
使用轻量级 XML 解析库(如 pugixml 或 TinyXML-2)。
工作原理
以 pugixml 为例:
#include "pugixml.hpp"
#include <iostream>
pugi::xml_document doc;
doc.load_file("config.xml");
for (auto node : doc.child("root").children("item")) {
std::cout << node.attribute("name").value() << "\n";
}
这类库提供 DOM 接口,便于导航和提取数据。
配方 3‐5. 将运行时数据插入字符串
问题
您需要将变量值格式化到字符串中(如日志消息)。
解决方案
使用 std::ostringstream 或 C++20 的 std::format 。
工作原理
使用 ostringstream :
#include <sstream>
std::ostringstream oss;
oss << "User " << name << " logged in at " << time;
std::string msg = oss.str();
C++20 推荐使用 std::format :
#include <format>
std::string msg = std::format("User {} logged in at {}", name, time);
更安全、更高效。
第4章: 处理数字
配方 4‐1. 使用 C++ 中的整数类型
问题
您希望正确选择适合的整数类型以保证跨平台兼容性和性能。
解决方案
使用 <cstdint> 中定义的固定宽度类型(如 int32_t , uint64_t )。
工作原理
避免使用 int 、 long 等平台相关类型。推荐:
#include <cstdint>
int32_t id = 1000;
uint64_t timestamp = 1622505600ULL;
这些类型明确指定比特数,确保可移植性。
配方 4‐6. C++20 “太空船”或三向比较操作符
问题
您希望在代码中使用太空船操作符,以利用C++版本20中的改进。
解决方案
你可以使用太空船操作符来让编译器自动生成比较操作符,而无需手动编写。
工作原理
截至2019年,C++20 实现了赫布·萨特关于“太空船操作符”或三向比较操作符的提案:
- 需要“compare”头信息。
- 截至2019年末,并非所有编译器都支持版本20 C++标准的所有特性。
- 使用此操作符可以优化你的代码。
三向比较操作符,也称为“太空船”操作符,是 C++20 中的新特性,它提供了一种简化比较操作的方法。要使用该功能,需要包含头信息 <compare> ,并使用符合 C++20 标准的编译器,例如 MS Visual Studio 19。
操作符(<=>)被称为太空船操作符,因为它看起来像早期计算机游戏中各种老式ASCII飞船。它旨在用于类、结构体或函数中,而不是像之前看到的比较操作符那样作为独立操作符使用。事实上,在背后,编译器会自动生成你刚刚使用的所有比较操作符,因此只要符合你的需求,代码将更加简洁。
实现时,您可以选择多种比较选项:强序、弱序、偏序、强相等 和 弱相等。
前两者均包含全部六个关系运算符(==、!=、<、>、<=、>=):
- 强序 – 不允许不可比较的值;意味着可互换性;等价的值是无法区分的;less、equivalent、equal 和 greater 是成员常量
- 弱序 – 不允许不可比较的值;不意味着可互换性;等价的值可能无法区分;less、equivalent 和 greater 是成员常量
示例:
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};
Point a{1, 2}, b{3, 4};
if (a < b) { /* 成立 */ }
编译器自动生成 < , <= , > , >= , == , != 操作符。
更多推荐



所有评论(0)