控制流
1. 控制流简介
当程序运行时,CPU 从 main()
的顶部开始执行,执行一定数量的语句(默认情况下按顺序),然后程序在 main()
的末尾终止。CPU 执行的特定语句序列称为程序的执行路径(execution path)(或简称 path)。C++ 提供了许多不同的控制流语句(也称为流控制语句),这些语句允许程序员通过程序更改正常的执行路径。
当控制流语句导致执行点更改为非顺序语句时,这称为分支。
类别 | 含义 | 在C++中的实现方式 |
---|---|---|
条件语句 | 如果满足某个条件,则执行一段代码序列。 | if, else, switch |
跳转 | 告诉CPU从其他位置开始执行语句。 | goto, break, continue |
函数调用 | 跳转到其他位置并返回。 | 函数调用, return |
循环 | 根据条件重复执行一段代码序列,次数可以为零或多次。 | while, do-while, for, 范围for |
终止程序 | 终止程序运行。 | std::exit(), std::abort() |
异常 | 一种用于错误处理的特殊流程控制结构。 | try, throw, catch |
2. If 语句和块
C++ 支持两种基本类型的条件语句:if 语句和 switch 语句。
if 语句采用以下格式:
1 | if (condition) |
else是可选的,if-else 语句包含多个语句时应该用块,如果是单个语句,最好也放在块中
如果程序员没有在 if 语句或 else 语句的 statement 部分声明块,编译器将隐式声明一个块。因此上面的格式等价于:
1 | if (condition) |
在if语句中定义的变量无法被外部访问
什么时候应该使用 if-else(if 后跟一个或多个 else 语句)或 if-if(if 后跟一个或多个额外的 if 语句)。
-
当您只想在第一个
true
条件之后执行代码时,请使用 if-else。 -
如果要在所有
true
条件之后执行代码,请使用 if-if。
示例:
1 |
|
3. 常见的 if 语句问题
可以将 if 语句嵌套在其他 if 语句中:
1 |
|
现在考虑以下程序:
1 |
|
上述程序引入了一个潜在的歧义来源,称为悬挂else问题。在上述程序中,else语句是与外层if语句还是内层if语句匹配的?答案是 else 语句与同一块中最后一个不匹配的 if 语句配对。因此,在上面的程序中,else 语句与内部 if 语句匹配,就好像程序是这样编写的:
1 |
|
为了在嵌套 if 语句时避免这种歧义,最好将内部 if 语句显式地括在一个块中。
嵌套的 if 语句通常可以通过重构逻辑或使用逻辑运算符来展平,嵌套较少的代码不太容易出错。上面的示例可以按如下方式展平:
1 |
|
null 语句是仅包含分号的语句:
1 | if (x > 10) |
Null 语句不执行任何作。当语言需要存在一个语句,但程序员不需要时,通常会使用它们。为了提高可读性,null 语句通常放在它们自己的行上。Null 语句很少有意与 if 语句一起使用。但是,它们可能会无意中给我们带来问题。请考虑以下代码段:
1 | if (nuclearCodesActivated()); // note the semicolon at the end of this line |
在上面的代码段中,程序员不小心在 if 语句的末尾放了一个分号(这是一个常见的错误,因为分号结束了许多语句)。这个不起眼的错误编译正常,并导致代码段执行,就像它是这样编写的一样:
1 | if (nuclearCodesActivated()) |
小心不要用分号“终止”你的 if 语句,否则你想要有条件执行的语句将无条件执行(即使它们在一个块内)
在 Python 中,pass
关键字用作 null 语句。它通常用作稍后将实现的代码的占位符。因为它是一个单词而不是一个符号,所以 pass
不容易被无意中误用,并且更易于搜索(允许您稍后轻松找到这些占位符)。
1 | for x in [0, 1, 2]: |
在 C++ 中,我们可以使用预处理器来模拟 pass
:
1 |
|
在条件语句中,在测试相等性时,你应该使用 operator==
,而不是 operator=
(即赋值)
4. constexpr if 语句
通常,if 语句的条件是在运行时计算的。但是,请考虑条件是常量表达式的情况,如以下示例所示:
1 |
|
因为 gravity
是 constexpr 并使用值 9.8
初始化,所以条件 gravity == 9.8
的计算结果必须为 true
。因此,将永远不会执行 else 语句。在运行时评估 constexpr 条件是浪费的(因为结果永远不会改变)。将代码编译成永远无法执行的可执行文件也是浪费的。
C++17 引入了 constexpr if 语句,该语句要求条件为常量表达式。constexpr-if 语句的条件将在编译时进行求值。如果常量条件的计算结果为 true
,则整个 if-else 将被 true 语句替换。如果常量条件的计算结果为 false
,则整个 if-else 将被 false 语句(如果存在)或什么都没有(如果没有 else)替换。
要使用 constexpr-if 语句,我们在 if
后添加 constexpr
关键字:
1 |
|
它将编译以下内容:
1 | int main() |
出于优化目的,现代编译器通常会将具有 constexpr 条件的非 constexpr if 语句视为 constexpr-if 语句。但是,他们不需要这样做。编译器遇到带有 constexpr 条件的非 constexpr if 语句时,可能会发出警告,建议你改用 if constexpr
。这将确保进行编译时评估(即使禁用了优化)。
5. switch 语句基础知识
由于针对一组不同的值测试变量或表达式是否相等是很常见的,因此 C++ 提供了一个专门用于此目的的替代条件语句,称为 switch 语句。示例:
1 |
|
switch 语句背后的想法很简单:对表达式(有时称为条件)进行求值以生成值。然后发生以下情况之一:
-
如果表达式的值等于任何 case-labels 之后的值,则执行匹配的 case-label 之后的语句。
-
如果找不到匹配的值,并且存在 default 标签,则执行 default 标签后面的语句。
-
如果找不到匹配的值,并且没有默认标签,则跳过整个 switch 语句。
switch 中的条件必须计算为整型。为什么 switch 类型只允许整型 (或枚举) 类型?答案是因为 switch 语句被设计为高度优化。从历史上看,编译器实现 switch 语句的最常见方法是通过 Jump tables – 而 Jump Table 仅适用于整数值。
switch 语句使用了两种标签。第一种标签是 case 标签,它使用 case
关键字声明,后跟一个常量表达式。常量表达式必须与条件的类型匹配,或者必须可转换为该类型。如果条件表达式的值等于 case
标签后面的表达式,则执行从该 case
标签后面的第一个语句开始,然后按顺序继续执行。第二种标签是 default 标签(通常称为 default case),它是使用 default
关键字声明的。如果条件表达式与任何 case 标签都不匹配,并且存在 default 标签,则从 default 标签后的第一个语句开始执行。
将 default case 放在 switch 块的最后。
break 语句(使用 break
关键字声明)告诉编译器我们已经完成了 switch 中的语句执行,并且应该在 switch 块结束后继续执行该语句。这允许我们退出 switch 语句而不退出整个函数。下面是一个略微修改的示例,使用 break
而不是 return
重写:
1 |
|
标签下的每组语句都应以 break-statement 或 return-statement 结尾。这包括 switch 中最后一个标签下的语句。
传统上,标签通常不缩进:
1 | // Preferred version |
这样可以轻松识别每个标签。而且,由于语句仅从 switch 块缩进一级,因此它正确地暗示了这些语句都是 switch 块范围的一部分。
6. switch 穿透和范围界定
6.1 switch 穿透
当 switch 表达式与 case 标签或可选的 default 标签匹配时,执行从匹配标签后面的第一个语句开始。然后,执行将按顺序继续,直到发生以下终止条件之一:
-
到达 switch 块的末尾。
-
另一个控制流语句(通常是
break
或return
)会导致 switch 块或函数退出。 -
其他事情打断了程序的正常流程(例如,作系统关闭了程序,宇宙内爆,等等…)
请注意,存在另一个 case 标签不是这些终止条件之一。因此,如果没有 break
或 return
,执行将溢出到后续 case 中。示例:
1 |
|
输出:
1 | 2 |
这可能不是我们想要的!当执行从标签下的语句流向后续标签下的语句时,这称为 穿透(fallthrough)。
一旦 case 或 default 标签下的语句开始执行,它们就会溢出 (穿透) 到后续的 case 中。break 或 return 语句通常用于防止这种情况。
注释有意的穿透是一种常见的约定,用于告诉其他开发人员穿透是有意的。虽然这适用于其他开发人员,但编译器和代码分析工具不知道如何解释注释,因此它不会消除警告。为了帮助解决这个问题,C++17 添加了一个名为 [[fallthrough]]
的新属性。
属性是一种现代 C++ 功能,它允许程序员向编译器提供有关代码的一些附加数据。要指定属性,请将属性名称放在双括号之间。属性不是语句——相反,它们几乎可以在与上下文相关的任何位置使用。
[[fallthrough]]
属性修改 null 语句,以指示穿透是有意为之(不应触发警告):
1 |
|
输出:
1 | 2 |
你可以使用 switch 语句通过按顺序放置多个 case 标签将多个测试合并到一个语句中:
1 | bool isVowel(char c) |
这不被视为穿透行为,因此此处不需要使用注释或 [[fallthrough]]。
6.2 范围界定
使用 if 语句,在 if 条件之后只能有一个语句,并且该语句被视为隐式地位于块内:
1 | if (x > 10) |
但是,对于 switch 语句,labels 后面的语句的作用域都限定为 switch 块。不会创建隐式块。
1 | switch (1) |
你可以在 switch 中声明或定义(但不能初始化)变量,包括 case 标签之前和之后:
1 | switch (1) |
尽管在case 1
中定义了变量 y
,但它也在case 2
中使用。switch 中的所有语句都被视为同一范围的一部分。因此,在一个 case 中声明或定义的变量可以在以后的 case 中使用,即使定义变量的 case 从未执行过(因为 switch 跳过了它)!
如果 case 需要定义和初始化新变量,最佳实践是在 case 语句下的显式块内执行此操作:
1 | switch (1) |
7. goto 语句
在 C++ 中,无条件跳转是通过 goto 语句实现的,要跳转到的位置通过使用语句标签来标识。就像 switch 大小写标签一样,语句标签通常不缩进。以下是 goto 语句和语句标签的示例:
1 |
|
在此程序中,要求用户输入非负数。但是,如果输入负数,则程序将使用 goto 语句跳回到 tryAgain
标签。然后,系统会再次要求用户输入新号码。通过这种方式,我们可以不断要求用户输入,直到输入有效内容。
示例运行:
1 | Enter a non-negative number: -4 |
我们前面介绍了两种范围:本地 (块) 范围和文件 (全局) 范围。语句标签使用第三种类型的范围:函数范围,这意味着标签甚至在其声明点之前就在整个函数中可见。goto 语句及其相应的语句标签必须出现在同一个函数中。
虽然上面的示例显示了向后跳转(到函数中的前一点)的 goto 语句,但 goto 语句也可以向前跳转:
1 |
|
跳转有两个主要限制:只能在单个函数的边界内跳转(不能从一个函数跳转到另一个函数)。如果向前跳转,则无法在跳转位置仍在范围内的任何变量的初始化上向前跳转。例如:
1 | int main() |
请注意,你可以向后跳转变量初始化,并且在执行初始化时,该变量将重新初始化。
在 C++(以及其他现代高级语言)中避免使用 goto
。著名计算机科学家 Edsger W. Dijkstra 在一篇著名但难以阅读的论文中阐述了避免 goto 的理由,名为 Go To Statement Considered Harmful。goto 的主要问题是它允许程序员任意地跳转代码。这就产生了不太亲切地称为意大利面条代码的代码。意大利面条代码是执行路径类似于一碗意大利面条(全部缠结和扭曲)的代码,这使得遵循此类代码的逻辑变得极其困难。
一个值得注意的例外是,当你需要退出嵌套循环而不是整个函数时 —— 在这种情况下,转到循环之外可能是最干净的解决方案。示例:
1 |
|
8. 循环和 while 语句简介
while 语句(也称为 while 循环)是 C++ 提供的三种循环类型中最简单的一种,它的定义与 if 语句的定义非常相似:
1 | while (condition) |
while 语句是使用 while 关键字声明的。执行 while 语句时,将计算表达式条件。如果条件的计算结果为 true
,则执行关联的语句。但是,与 if 语句不同的是,一旦语句完成执行,控制权就会返回到 while 语句的顶部,并重复该过程。这意味着只要条件继续计算为 true
,while 语句就会一直循环。示例:
1 |
|
输出:
1 | 1 2 3 4 5 6 7 8 9 10 done! |
如果条件最初计算结果为 false
,则关联的语句将根本不执行。请考虑以下程序:
1 |
|
另一方面,如果表达式的计算结果始终为 true
,则 while 循环将永远执行。这称为无限循环。下面是一个无限循环的例子:
1 |
|
我们可以像这样声明一个有意的无限循环:
1 | while (true) |
退出无限循环的唯一方法是通过 return 语句、break语句、exit语句、goto 语句、引发的异常或用户终止程序。这里有一个愚蠢的例子来证明这一点:
1 |
|
该程序将持续循环,直到用户输入 n
作为输入,此时 if 语句的计算结果为 true
,关联的返回 0;将导致函数 main()
退出,从而终止程序。
循环变量是用于控制循环执行次数的变量。例如,给定 while (count <= 10),``count
是一个循环变量。虽然大多数循环变量的类型为 int
,但你偶尔会看到其他类型(例如 char
)。循环变量通常被赋予简单的名称,其中 i
、j
和 k
是最常见的。
但是,如果你想知道程序中哪里使用了循环变量,并且您在 i
、j
或 k
上使用了搜索函数,则搜索函数将返回程序中一半的行!因此,一些开发人员更喜欢循环变量名称,如 iii
、jjj
或 kkk
。因为这些名称更唯一,所以这使得搜索循环变量变得更加容易,并有助于它们在循环变量中脱颖而出。更好的主意是使用 “真实” 变量名称,例如 count
、index
或提供有关你正在计算的内容的更多详细信息的名称(例如 userCount
)。最常见的循环变量类型称为计数器,它是一个循环变量,用于计算循环已执行的次数。
整型循环变量几乎总是应该有符号的,因为无符号整数可能会导致意外问题。请考虑以下代码:
1 |
|
事实证明,这个程序是一个无限循环。它首先根据需要打印 10 9 8 7 6 5 4 3 2 1 blastoff!
,但随后循环变量 count
溢出,并从 4294967295
开始倒计时(假设为 32 位整数)。为什么?因为循环条件 count >= 0
永远不会为 false!当 count 为 0
时,0 >= 0
为 true。然后执行 --count
,并将 count 回绕回 4294967295
。由于 4294967295 >= 0
为 true
,因此程序继续进行。因为 count
是无符号的,所以它永远不能是负数,而且因为它永远不能是负数,所以循环不会终止。
每次执行循环时,它称为迭代。通常,我们希望每 2 次、第 3 次或第 4 次迭代做一些事情,例如打印换行符。这可以通过在我们的计数器上使用取余运算符轻松完成:
1 |
|
输出:
1 | 01 02 03 04 05 06 07 08 09 10 |
也可以将循环嵌套在其他循环中。示例:
1 |
|
输出:
1 | 1 |
9. do while 语句
考虑一下这样一种情况:我们想向用户显示一个菜单并要求他们进行选择——如果用户选择了无效的选择,则再次询问他们。显然,菜单和选择应该进入某种循环中(这样我们就可以一直询问用户,直到他们输入有效的输入),但是我们应该选择什么样的循环呢?
由于 while 循环会预先评估条件,因此这是一个尴尬的选择。我们可以像这样解决这个问题:
1 |
|
但这之所以有效,是因为selection
的初始值 0
不在有效值集(1、2、3 或 4
)中。如果 0
是有效选择,该怎么办?我们必须选择一个不同的初始化器来表示 “invalid” —— 现在我们在代码中引入了魔术数字。我们可以添加一个新变量来跟踪有效性,如下所示:
1 |
|
它引入了一个新变量,只是为了确保循环运行一次,这增加了复杂性和出现其他错误的可能性。为了帮助解决上述问题,C++ 提供了 do-while 语句:
1 | do |
do while 语句是一种循环结构,其工作方式与 while 循环类似,不同之处在于该语句始终至少执行一次。执行语句后,do-while 循环会检查条件。如果条件的计算结果为 true
,则执行路径将跳回到 do-while 循环的顶部并再次执行它。
1 |
|
在实践中,do-while 循环并不常用。将条件放在循环的底部会掩盖循环条件,这可能会导致错误。
10. for 语句
到目前为止,C++ 中使用最多的循环语句是 for 语句。当我们有一个明显的循环变量时,for 语句(也称为 for 循环)是首选,因为它让我们可以轻松简洁地定义、初始化、测试和更改循环变量的值。从 C++11 开始,有两种不同类型的 for 循环。
for 语句抽象地看起来非常简单:
1 | for (init-statement; condition; end-expression) |
初步了解 for 语句工作原理的最简单方法是将其转换为等效的 while 语句:
1 | { // note the block here |
for 语句分为 3 个部分进行计算:
-
首先,执行
init-statement
。这仅在启动循环时发生一次。init-statement
通常用于变量定义和初始化。这些变量具有“循环范围”,这实际上只是一种块范围的形式,其中这些变量从定义点一直存在于循环语句的末尾。 -
其次,在每次循环迭代中,都会计算条件。如果此结果为
true
,则执行该语句。如果此结果为false
,则循环终止,并继续执行循环之外的下一个语句。 -
最后,在执行语句后,将计算
end-expression
。通常,此表达式用于递增或递减init-statement
中定义的循环变量。计算完end-expression 后
,执行返回到第二步(并再次计算条件)。
当编写涉及值的循环条件时,我们通常可以用许多不同的方式编写条件。以下两个循环的执行方式相同:
1 |
|
前者是更好的选择,因为即使 i
跳转超过值 10
它也会终止,而后者则不会。以下示例演示了这一点:
1 |
|
在 for 循环条件中进行数值比较时,避免使用
operator!=
。尽可能首选operator<
或operator<=
。
新程序员在使用 for 循环(和其他利用计数器的循环)时遇到的最大问题之一是 差1错误(Off-by-one errors)。当循环迭代太多或 1 次以产生所需的结果时,会发生 差1错误。示例:
1 |
|
这个程序应该打印 1 2 3 4 5
,但它只打印 1 2 3 4
,因为我们使用了错误的关系运算符。
可以编写省略任何或所有语句或表达式的 for 循环。例如,在以下示例中,我们将省略 init-statement
和 end-expression
,只保留 condition
:
1 |
|
输出:
1 | 0 1 2 3 4 5 6 7 8 9 |
以下示例生成了一个无限循环:
1 | for (;;) |
这可能有点出乎意料,因为您可能希望省略的 condition-expression
被视为 false
。但是,C++ 标准明确(且不一致)定义 for 循环中省略的条件表达式应被视为 true
。我们建议完全避免这种形式的 for 循环,而是使用 while (true)
。
尽管 for 循环通常只迭代一个变量,但有时 for 循环需要处理多个变量。为了帮助实现这一点,程序员可以在 init-statement
中定义多个变量,并可以使用逗号运算符来更改 end-expression
中多个变量的值:
1 |
|
这大约是 C++ 中唯一一个在同一语句中定义多个变量并且使用逗号运算符被认为是可接受的做法的地方。
与其他类型的循环一样,for 循环可以嵌套在其他循环中。在以下示例中,我们将一个 for 循环嵌套在另一个 for 循环中:
1 |
|
输出:
1 | a012 |
新程序员通常认为创建变量的成本很高,因此最好创建一次变量(然后为其赋值)而不是多次创建变量(并使用初始化)。这会导致循环看起来像下面的一些变体:
1 |
|
上面的示例使 i
在循环之外可用。除非需要在循环之外使用变量,否则在循环外定义变量可能会产生两个后果:
-
它使我们的程序更加复杂,因为我们必须阅读更多代码才能看到变量的使用位置。
-
它实际上可能会更慢,因为编译器可能无法有效地优化具有更大范围的变量。
与我们在尽可能小的合理范围内定义变量的最佳实践一致,仅在循环中使用的变量应在循环内部定义,而不是在循环外部定义。
11. break 和 continue
11.1 break 语句
break 语句会导致 while 循环、do-while 循环、for 循环或 switch 语句结束,并在循环或 switch 中断后继续执行下一条语句。
在循环的上下文中,可以使用 break
语句提前结束循环。在循环结束后继续执行 next 语句。
1 |
|
输出示例:
1 | Enter a number to add, or 0 to exit: 5 |
break
也是摆脱有意无限循环的常见方法:
1 |
|
break
语句终止 switch 或 loop,并在 switch 或 loop 之后的第一个语句继续执行。return
语句终止循环所在的整个函数,并在调用函数的位置继续执行。
11.2 continue 语句
continue 语句提供了一种在不终止整个循环的情况下结束循环的当前迭代的便捷方法。示例:
1 |
|
此程序打印从 0 到 9 的所有不能被 4 整除的数字:
1 | 1 |
continue
语句的工作原理是使当前执行点跳转到当前循环的底部。
在 for 循环的情况下,for 循环的结束语句(在上面的示例中为 ++count
)仍然在 continue 之后执行(因为这发生在循环体结束后)。
将 continue
语句与 while 或 do-while 循环一起使用时要小心。这些循环通常会更改循环体内条件中使用的变量的值。如果使用 continue
语句导致跳过这些行,则循环可能会变得无限!
示例:
1 |
|
该程序旨在打印 0 到 9 之间的每个数字,但 5 除外。但它实际上打印了:
1 | 0 |
当然,你已经知道,如果你有一个明显的 counter
变量,你应该使用 for
循环,而不是 while
或 do while
循环。
许多教科书告诫读者不要在循环中使用 break
和 continue
,因为它会导致执行流跳来跳去,并且因为它会使逻辑流更难遵循。然而,如果使用得当,break
和 continue
可以通过减少嵌套块的数量并减少对复杂循环逻辑的需求来帮助提高循环的可读性。
请考虑以下程序:
1 |
|
该程序使用 Boolean 变量来控制循环是否继续,以及仅在用户不退出时运行的嵌套块。下面是一个更容易理解的版本,使用 break
语句:
1 |
|
continue
语句最有效地用于 for 循环的顶部,以便在满足某些条件时跳过循环迭代。这可以让我们避免嵌套块。示例:
1 |
|
我们可以这样写:
1 |
|
对于 return 语句,也有类似的参数。不是函数中最后一个语句的 return 语句称为 early return。当 early returns 简化函数的逻辑时,可以使用 early return。
12. halts(提前退出程序)
halt 是终止程序的流控制语句。在 C++ 中,halt 被实现为函数(而不是关键字),因此我们的 halt 语句将是函数调用。
std::exit()
是使程序正常终止的函数。正常终止意味着程序已按预期方式退出。请注意,术语正常并不意味着程序是否成功(这就是status code
的用途)。例如,假设您正在编写一个程序,您希望用户键入要处理的文件名。如果用户键入了无效的文件名,则程序可能会返回非零status code
以指示失败状态,但它仍将正常终止。
std::exit()
执行许多清理功能。首先,销毁具有静态存储持续时间的对象。然后,如果使用了任何文件,则执行一些其他杂项文件清理。最后,控制权返回给作系统,并将传递给 std::exit()
的参数用作status code
。
虽然 std::exit()
是在函数 main()
返回后隐式调用的,但 std::exit()
也可以显式调用,以便在程序正常终止之前停止程序。以这种方式调用 std::exit()
时,您需要包含 cstdlib
标头。
下面是显式使用 std::exit()
的示例:
1 |
|
输出:
1 | 1 |
请注意,调用 std::exit()
后的语句永远不会执行,因为程序已经终止。可以从任何函数调用 std::exit()
以在此时终止程序。
关于显式调用 std::exit()
的一个重要说明:std::exit()
不会清理任何局部变量(无论是在当前函数中,还是在调用堆栈中的函数中)。这意味着如果你的程序依赖于任何自我清理的局部变量,调用 std::exit()
可能会很危险。
C++ 提供了 std::atexit()
函数,该函数允许您指定一个函数,该函数将在程序终止时通过 std::exit()
自动调用。下面是一个示例:
1 |
|
输出和之前的相同。
关于 std::atexit()
和清理函数的一些说明:首先,因为 std::exit()
在 main()
终止时被隐式调用,所以如果程序以这种方式退出,这将调用 std::atexit()
注册的任何函数。其次,被注册的函数必须不带参数,也不能有返回值。最后,如果需要,你可以使用 std::atexit()
注册多个清理函数,它们将按注册的相反顺序调用(最后一个注册的函数将首先调用)。
在多线程程序中,调用 std::exit()
可能会导致程序崩溃(因为调用 std::exit()
的线程将清理其他线程可能仍可访问的静态对象)。出于这个原因,C++引入了另一对函数,它们的工作方式类似于 std::exit()
和 std::atexit()
,称为 std::quick_exit()
和 std::at_quick_exit()
。std::quick_exit()
正常终止程序,但不清理静态对象,并且可能会也可能不会进行其他类型的清理。std::at_quick_exit()
对于以 std::quick_exit()
结尾的程序,执行与 std::atexit()
相同的角色。
C++ 包含另外两个与 halt 相关的函数。std::abort()
函数会导致程序异常终止。异常终止意味着程序存在某种异常的运行时错误,并且程序无法继续运行。例如,尝试除以 0 将导致异常终止。std::abort()
不做任何清理。
1 |
|
仅当没有安全或合理的方法从 main 函数正常返回时,才使用 halt。如果您尚未禁用异常,则首选使用异常来安全地处理错误。