文章目录
-
-
- [C++ `if` 语句详解](#C++
if
语句详解) -
- 基本语法
- 高级特性
-
- [1. `init-statement` (从 C++17 开始)](#1.
init-statement
(从 C++17 开始)) - [2. `if constexpr` (从 C++17 开始)](#2.
if constexpr
(从 C++17 开始)) - [3. `if consteval` (从 C++23 开始)](#3.
if consteval
(从 C++23 开始)) - [4. `if !consteval` (从 C++23 开始)](#4.
if !consteval
(从 C++23 开始)) - [5. 结构化绑定声明 (自 C++17 起)](#5. 结构化绑定声明 (自 C++17 起))
- [6. `goto` 和 `longjmp` 的特殊行为](#6.
goto
和longjmp
的特殊行为)
- [1. `init-statement` (从 C++17 开始)](#1.
- 示例代码
- 总结
- [C++ `switch` 语句详解](#C++
switch
语句详解) - [C++ `for` 循环详解](#C++
for
循环详解) - [C++ 基于范围的 `for` 循环详解](#C++ 基于范围的
for
循环详解) -
- 基本语法
- [等效的传统 `for` 循环](#等效的传统
for
循环) - 关键特性
-
- [1. **`item-declaration`**](#1.
item-declaration
) - [2. **`range-initializer`**](#2.
range-initializer
) - [3. **`init-statement`**](#3.
init-statement
) - [4. **临时对象的生命周期**](#4. 临时对象的生命周期)
- [5. **不同类型的 `begin` 和 `end`**](#5. 不同类型的
begin
和end
) - [6. **避免深度复制**](#6. 避免深度复制)
- [1. **`item-declaration`**](#1.
- 示例代码
- 总结
- [C++ `while` 和 `do-while` 循环详解](#C++
while
和do-while
循环详解) - [C++ `continue` 和 `break` 语句详解](#C++
continue
和break
语句详解)
- [C++ `if` 语句详解](#C++
-
C++ if
语句详解
C++ 的 if
语句是用于条件执行代码块的控制结构。它允许根据一个条件表达式的真假来选择性地执行一段代码。自 C++17 和 C++23 起,if
语句引入了一些新的特性和语法糖,如 if constexpr
和 if consteval
,使得编译期计算和立即函数的使用更加方便。
基本语法
cpp
if (condition) {
// statement-true
} else {
// statement-false (可选)
}
condition
: 一个表达式或声明,其结果会被转换为bool
类型。statement-true
: 如果condition
为true
,则执行这部分代码。statement-false
: 如果condition
为false
,且存在else
分支,则执行这部分代码。
高级特性
1. init-statement
(从 C++17 开始)
可以在 if
语句中包含一个初始化语句,这个初始化语句的作用域仅限于 if
语句内部。这在需要局部变量时非常有用。
cpp
if (int x = getValue(); x > 0) {
// 使用 x
} else {
// x 仍然可用
}
2. if constexpr
(从 C++17 开始)
if constexpr
允许在编译期评估条件表达式。如果条件为 true
,则编译器会编译 statement-true
;如果为 false
,则编译器会忽略 statement-true
并编译 statement-false
(如果有)。这在模板编程中特别有用,可以用来编写更高效的代码。
cpp
template <typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
// 处理整数类型
} else if constexpr (std::is_floating_point_v<T>) {
// 处理浮点类型
}
}
3. if consteval
(从 C++23 开始)
if consteval
用于在显式常量求值上下文中执行代码。如果 if consteval
语句在显式常量求值上下文中求值,则执行 compound-statement
;否则,执行 else
分支(如果有)。
cpp
constexpr void f(bool b) {
if consteval {
// 只在编译期执行
} else {
// 只在运行时执行
}
}
4. if !consteval
(从 C++23 开始)
if !consteval
是 if consteval
的否定形式。它在显式常量求值上下文中执行 else
分支,在非显式常量求值上下文中执行 compound-statement
。
cpp
constexpr void g(bool b) {
if !consteval {
// 只在运行时执行
} else {
// 只在编译期执行
}
}
5. 结构化绑定声明 (自 C++17 起)
if
语句中的条件可以是一个结构化绑定声明,允许直接解构返回多个值的函数。
cpp
std::pair<int, int> getPair() {
return {1, 2};
}
void usePair() {
if (auto [x, y] = getPair(); x > 0) {
// 使用 x 和 y
}
}
6. goto
和 longjmp
的特殊行为
如果 statement-true
通过 goto
或 longjmp
进入,则不会对 condition
求值,也不会执行 statement-false
。
cpp
void example() {
goto skip;
if (true) {
// 这里的代码不会被执行
} else {
// 这里的代码也不会被执行
}
skip:
// 继续执行
}
示例代码
以下是一些综合示例,展示了 if
语句的不同用法:
cpp
#include <iostream>
#include <type_traits>
// 简单的 if-else 语句
void simpleIfElse() {
int i = 2;
if (i > 2) {
std::cout << i << " is greater than 2\n";
} else {
std::cout << i << " is not greater than 2\n";
}
}
// 嵌套的 if-else 语句
void nestedIfElse() {
int i = 2, j = 1;
if (i > 1) {
if (j > 2) {
std::cout << i << " > 1 and " << j << " > 2\n";
} else {
std::cout << i << " > 1 and " << j << " <= 2\n";
}
}
}
// 使用 init-statement
void withInitStatement() {
if (int x = 10; x > 5) {
std::cout << "x is greater than 5\n";
}
}
// 使用 if constexpr
template <typename T>
void processValue(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Processing an integral type\n";
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Processing a floating-point type\n";
}
}
// 使用 if consteval
constexpr void compileTimeFunction(bool b) {
if consteval {
std::cout << "This is a compile-time function\n";
} else {
std::cout << "This is a run-time function\n";
}
}
int main() {
simpleIfElse();
nestedIfElse();
withInitStatement();
processValue(42);
processValue(3.14);
compileTimeFunction(true);
return 0;
}
总结
C++ 的 if
语句提供了强大的条件控制功能,并且随着 C++17 和 C++23 的引入,新增了 if constexpr
和 if consteval
等特性,使得编译期优化和立即函数的使用变得更加灵活和高效。理解这些特性可以帮助你编写更简洁、更高效的代码,特别是在模板编程和编译期计算的场景中。
C++ switch
语句详解
C++ 的 switch
语句是一种多分支选择结构,它根据条件表达式的值将控制权转移到多个语句中的一个。与 if-else
语句不同,switch
语句可以更简洁地处理多个离散的值匹配情况。
基本语法
cpp
attr (可选) switch ( init-statement (可选) condition ) statement
attr
: 从 C++11 开始,可以在switch
语句前添加任意数量的属性。init-statement
: 从 C++17 开始,可以在switch
语句中包含一个初始化语句。这个初始化语句的作用域仅限于switch
语句内部。condition
: 一个条件表达式或简单声明,其结果用于确定控制权将转到哪个标签。statement
: 一个语句(通常是复合语句),其中可以包含case
和default
标签。
条件
condition
可以是以下几种类型之一:
- 整型
- 枚举类型
- 类类型(必须能隐式转换为整型或枚举类型)
如果 condition
是类类型,则会尝试隐式转换为整型或枚举类型。如果转换后的类型受整型提升的影响,产生的值将被提升。
标签
switch
语句中的任何语句都可以用以下标签进行标记:
case 常量表达式 :
: 指定一个常量表达式,当condition
的值等于该常量时,控制权将转移到该标签处。default :
: 如果没有case
标签匹配condition
的值,则控制权将转移到default
标签处。default
标签是可选的。
控制流传递
当 switch
语句的 condition
产生一个值时:
- 如果有
case
标签的常量表达式与该值匹配,则控制权将传递到匹配的case
标签处。 - 如果没有匹配的
case
标签,但存在default
标签,则控制权将传递到default
标签处。 - 如果既没有匹配的
case
标签,也没有default
标签,则switch
语句中的任何语句都不会被执行。
case
和 default
标签本身不会改变控制流。要从 switch
语句的中间退出,可以使用 break
语句。
穿透(Fallthrough)
如果没有 break
语句,控制权将继续执行下一个 case
或 default
标签的语句,这种行为称为"穿透"。编译器可能会发出警告,除非你明确表示这是故意的行为,例如使用 [[fallthrough]]
属性(从 C++17 开始)。
cpp
switch (value) {
case 1:
std::cout << "Case 1\n";
[[fallthrough]]; // 明确表示穿透
case 2:
std::cout << "Case 2\n";
break;
}
初始化语句
从 C++17 开始,switch
语句可以包含一个初始化语句,这在需要局部变量时非常有用。初始化语句的作用域仅限于 switch
语句内部。
cpp
switch (auto [x, y] = getPair(); x + y) {
case 0:
std::cout << "Sum is 0\n";
break;
case 1:
std::cout << "Sum is 1\n";
break;
default:
std::cout << "Sum is greater than 1\n";
}
变量作用域
由于控制传递不允许进入变量的作用域,因此如果在 switch
语句中遇到声明语句,必须将其作用域限定在其自己的复合语句中。
cpp
switch (1) {
case 1:
{
int x = 0;
std::cout << x << '\n';
break;
} // scope of 'x' ends here
default:
std::cout << "default\n"; // no error
break;
}
示例代码
以下是一些综合示例,展示了 switch
语句的不同用法:
cpp
#include <iostream>
// 简单的 switch 语句
void simpleSwitch() {
int i = 2;
switch (i) {
case 1:
std::cout << "Case 1\n";
break;
case 2:
std::cout << "Case 2\n";
break;
case 3:
std::cout << "Case 3\n";
break;
default:
std::cout << "Default case\n";
break;
}
}
// 使用 fallthrough
void fallthroughExample() {
int i = 1;
switch (i) {
case 1:
std::cout << "Case 1\n";
[[fallthrough]];
case 2:
std::cout << "Case 2\n";
break;
case 3:
std::cout << "Case 3\n";
break;
default:
std::cout << "Default case\n";
break;
}
}
// 使用初始化语句
void withInitStatement() {
switch (int x = 5; x) {
case 1:
std::cout << "Case 1\n";
break;
case 5:
std::cout << "Case 5\n";
break;
default:
std::cout << "Default case\n";
break;
}
}
// 使用枚举
enum Color { RED, GREEN, BLUE };
void enumSwitch() {
Color color = RED;
switch (color) {
case RED:
std::cout << "Red\n";
break;
case GREEN:
std::cout << "Green\n";
break;
case BLUE:
std::cout << "Blue\n";
break;
default:
std::cout << "Unknown color\n";
break;
}
}
// 处理类类型的转换
struct Device {
enum State { SLEEP, READY, BAD };
State state() const { return m_state; }
private:
State m_state{};
};
void classTypeSwitch() {
Device dev;
switch (dev.state()) {
case Device::SLEEP:
std::cout << "Device is sleeping\n";
break;
case Device::READY:
std::cout << "Device is ready\n";
break;
case Device::BAD:
std::cout << "Device is in bad state\n";
break;
}
}
int main() {
simpleSwitch();
fallthroughExample();
withInitStatement();
enumSwitch();
classTypeSwitch();
return 0;
}
总结
C++ 的 switch
语句提供了一种简洁的方式来处理多个离散的值匹配情况。通过 case
和 default
标签,你可以轻松地控制程序的执行路径。此外,switch
语句还支持一些高级特性,如 [[fallthrough]]
属性、初始化语句和类类型的隐式转换,使得代码更加灵活和强大。
理解这些特性和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理多个条件的情况下。
C++ for
循环详解
C++ 的 for
循环是一种用于重复执行语句的控制结构。它允许你在一个简洁的语法中定义循环的初始化、条件和更新表达式。for
循环广泛应用于需要迭代处理数据或执行重复任务的场景。
基本语法
cpp
attr (可选) for ( init-statement condition (可选) ; expression (可选) ) statement
attr
: 从 C++11 开始,可以在for
循环前添加任意数量的属性。init-statement
: 初始化语句,通常是声明一个或多个变量或执行一个表达式。它可以是:- 表达式语句(可以是空语句
;
) - 简单声明(通常用于声明循环计数器变量),可以声明任意数量的变量或结构化绑定(自 C++17 起)
- 别名声明(自 C++23 起)
- 表达式语句(可以是空语句
condition
: 条件表达式,用于决定是否继续执行循环体。如果省略,则默认为true
。expression
: 更新表达式,通常用于更新循环变量。它是可选的。statement
: 循环体,通常是复合语句(即用大括号{}
包围的多条语句)。如果只有一条语句,可以省略大括号。
等效的 while
循环
for
循环等效于以下 while
循环:
cpp
{
init-statement
while (condition) {
statement
expression;
}
}
不同之处在于:
init-statement
和condition
的作用域相同。statement
和expression
的作用域不相交,并且嵌套在init-statement
和condition
的作用域内。- 在
statement
中执行continue
语句将会计算expression
。 - 空的
condition
等同于true
。
关键特性
1. 初始化语句 (init-statement
)
init-statement
可以是以下几种形式之一:
- 表达式语句 :可以是一个简单的表达式,例如
std::cout << "Loop start\n";
。 - 简单声明 :通常用于声明循环计数器变量,例如
int i = 0;
。可以从 C++17 开始使用结构化绑定。 - 别名声明:从 C++23 开始支持。
cpp
// 使用表达式作为初始化语句
for (std::cout << "Loop start\n"; /* condition */; /* expression */) {
// 循环体
}
// 使用简单声明作为初始化语句
for (int i = 0; i < 10; ++i) {
std::cout << i << ' ';
}
// 使用结构化绑定作为初始化语句(自 C++17 起)
long arr[] = {1, 3, 7};
for (auto [i, j, k] = arr; i + j < k; ++i) {
std::cout << i + j << ' ';
}
2. 条件 (condition
)
condition
可以是以下几种形式之一:
- 表达式:通常是一个布尔表达式,用于决定是否继续执行循环体。
- 简单声明 :从 C++26 开始,
condition
可以是一个简单声明,其值将被转换为bool
类型。
cpp
// 使用表达式作为条件
for (int i = 0; i < 10; ++i) {
std::cout << i << ' ';
}
// 使用简单声明作为条件(自 C++26 起)
char cstr[] = "Hello";
for (int n = 0; char c = cstr[n]; ++n) {
std::cout << c;
}
3. 更新表达式 (expression
)
expression
通常是用于更新循环变量的表达式,例如递增或递减计数器。它是可选的。
cpp
// 使用更新表达式
for (int i = 0; i < 10; ++i) {
std::cout << i << ' ';
}
// 没有更新表达式
for (int i = 0; i < 10;) {
std::cout << i << ' ';
++i; // 手动更新
}
4. 循环体 (statement
)
statement
是循环体,通常是复合语句(即用大括号 {}
包围的多条语句)。如果只有一条语句,可以省略大括号。
cpp
// 复合语句作为循环体
for (int i = 0; i < 10; ++i) {
std::cout << i << ' ';
std::cout << " is less than 10\n";
}
// 单条语句作为循环体
for (int i = 0; i < 10; ++i)
std::cout << i << ' ';
控制流
break
语句:用于立即终止循环。continue
语句 :用于跳过当前迭代并继续下一次迭代。continue
会执行expression
,然后重新评估condition
。
cpp
// 使用 break 语句
for (int i = 0; i < 10; ++i) {
if (i == 5) {
break; // 终止循环
}
std::cout << i << ' ';
}
// 使用 continue 语句
for (int i = 0; i < 10; ++i) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
std::cout << i << ' ';
}
示例代码
以下是一些综合示例,展示了 for
循环的不同用法:
cpp
#include <iostream>
#include <vector>
void typicalLoop() {
std::cout << "1) 典型的循环,单条语句作为循环体:\n";
for (int i = 0; i < 10; ++i)
std::cout << i << ' ';
std::cout << '\n';
}
void multipleDeclarations() {
std::cout << "2) 初始化语句可以声明多个名称,只要它们可以使用相同的声明说明符:\n";
for (int i = 0, *p = &i; i < 9; i += 2)
std::cout << i << ':' << *p << ' ';
std::cout << '\n';
}
void conditionAsDeclaration() {
std::cout << "3) 条件可以是一个声明:\n";
char cstr[] = "Hello";
for (int n = 0; char c = cstr[n]; ++n)
std::cout << c;
std::cout << '\n';
}
void autoTypeSpecifier() {
std::cout << "4) 初始化语句可以使用 auto 类型说明符:\n";
std::vector<int> v = {3, 1, 4, 1, 5, 9};
for (auto iter = v.begin(); iter != v.end(); ++iter)
std::cout << *iter << ' ';
std::cout << '\n';
}
void expressionAsInitStatement() {
std::cout << "5) 初始化语句可以是一个表达式:\n";
int n = 0;
for (std::cout << "Loop start\n";
std::cout << "Loop test\n";
std::cout << "Iteration " << ++n << '\n')
{
if (n > 1)
break;
}
std::cout << '\n';
}
void objectLifecycle() {
std::cout << "6) 构造函数和析构函数在每次迭代时都会被调用:\n";
struct S {
S(int x, int y) { std::cout << "S::S(" << x << ", " << y << "); "; }
~S() { std::cout << "S::~S()\n"; }
};
for (int i{0}, j{5}; i < j; ++i, --j)
S s{i, j};
std::cout << '\n';
}
void structuredBindings() {
std::cout << "7) 初始化语句可以使用结构化绑定:\n";
long arr[]{1, 3, 7};
for (auto [i, j, k] = arr; i + j < k; ++i)
std::cout << i + j << ' ';
std::cout << '\n';
}
int main() {
typicalLoop();
multipleDeclarations();
conditionAsDeclaration();
autoTypeSpecifier();
expressionAsInitStatement();
objectLifecycle();
structuredBindings();
return 0;
}
总结
C++ 的 for
循环提供了一种灵活且强大的方式来实现迭代和重复任务。通过 init-statement
、condition
和 expression
,你可以精确地控制循环的行为。此外,for
循环还支持一些高级特性,如结构化绑定、auto
类型推导和复杂的初始化语句,使得代码更加简洁和易读。
理解这些特性和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理复杂迭代逻辑的情况下。
C++ 基于范围的 for
循环详解
自 C++11 引入以来,基于范围的 for
循环(Range-based for
loop)提供了一种更加简洁和可读的方式来遍历容器或数组中的元素。它简化了传统的 for
循环语法,减少了代码冗余,并且更容易理解和维护。
基本语法
cpp
attr (可选) for ( init-statement (可选) item-declaration : range-initializer ) statement
attr
: 从 C++11 开始,可以在for
循环前添加任意数量的属性。init-statement
: 从 C++20 开始,可以包含一个初始化语句。它可以是:- 表达式语句(可以是空语句
;
) - 简单声明(通常用于声明循环计数器变量),可以声明任意数量的变量或结构化绑定
- 别名声明(自 C++23 起)
- 表达式语句(可以是空语句
item-declaration
: 每个范围项的声明,定义如何访问当前迭代的元素。range-initializer
: 一个表达式或花括号括起来的初始化列表,用于初始化要迭代的序列或范围。statement
: 任何语句(通常是复合语句),表示循环体。
等效的传统 for
循环
基于范围的 for
循环等效于以下传统 for
循环:
cpp
{
auto&& /* 范围 */ = range-initializer;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement;
}
}
range-initializer
的临时对象生存期会延长到循环结束(自 C++23 起)。begin-expr
和end-expr
的定义取决于range-initializer
的类型:- 如果
range
是对数组类型的引用,则begin-expr
为range
,end-expr
为range + N
,其中N
是数组的大小。 - 如果
range
是对类类型的引用,并且类中有begin()
和end()
成员函数,则begin-expr
为range.begin()
,end-expr
为range.end()
。 - 否则,
begin-expr
为begin(range)
,end-expr
为end(range)
,其中begin
和end
是通过依赖于参数的查找(ADL)找到的。
- 如果
关键特性
1. item-declaration
item-declaration
定义了如何访问当前迭代的元素。它可以是以下几种形式之一:
- 按值访问 :
auto i
,每次迭代都会复制元素。 - 按常量引用访问 :
const int& i
,避免不必要的复制,适用于只读访问。 - 按转发引用访问 :
auto&& i
,适用于多种情况,包括修改元素。
cpp
std::vector<int> v = {0, 1, 2, 3, 4, 5};
// 按值访问
for (auto i : v) {
std::cout << i << ' ';
}
// 按常量引用访问
for (const int& i : v) {
std::cout << i << ' ';
}
// 按转发引用访问
for (auto&& i : v) {
std::cout << i << ' ';
}
2. range-initializer
range-initializer
可以是以下几种形式之一:
- 容器 :如
std::vector
、std::list
等。 - 数组:C 风格数组或 C++ 数组。
- 初始化列表 :用花括号
{}
包围的值列表,会被推导为std::initializer_list
。 - 其他支持
begin()
和end()
的对象:如自定义类。
cpp
// 使用容器
std::vector<int> v = {0, 1, 2, 3, 4, 5};
for (int i : v) {
std::cout << i << ' ';
}
// 使用数组
int a[] = {0, 1, 2, 3, 4, 5};
for (int i : a) {
std::cout << i << ' ';
}
// 使用初始化列表
for (int n : {0, 1, 2, 3, 4, 5}) {
std::cout << n << ' ';
}
3. init-statement
从 C++20 开始,for
循环可以包含一个初始化语句,这在需要局部变量时非常有用。例如:
cpp
for (auto n = v.size(); auto i : v) {
std::cout << --n + i << ' ';
}
4. 临时对象的生命周期
如果 range-initializer
返回一个临时对象,其生命周期会延长到循环结束(自 C++23 起)。在此之前,直接使用临时对象会导致未定义行为。可以通过将临时对象绑定到变量来解决这个问题。
cpp
// C++23 之前,未定义行为
for (auto& x : foo().items()) { /* ... */ }
// 解决方法
for (T thing = foo(); auto& x : thing.items()) { /* ... */ }
5. 不同类型的 begin
和 end
从 C++17 开始,begin-expr
和 end-expr
的类型不必相同,实际上 end-expr
的类型不必是迭代器,只要能够与迭代器进行不等式比较即可。这使得可以通过谓词来界定范围。
cpp
struct Range {
int* begin() { return data; }
const int* end() const { return data + size; }
// ...
};
6. 避免深度复制
当与具有写时复制语义的对象一起使用时,基于范围的 for
循环可能会触发深度复制。可以使用 std::as_const
来避免这种情况。
cpp
struct cow_string { /* ... */ }; // 一个带有写时复制语义的字符串
cow_string str = /* ... */;
// 可能导致深度复制
for (auto x : str) { /* ... */ }
// 避免深度复制
for (auto x : std::as_const(str)) { /* ... */ }
示例代码
以下是一些综合示例,展示了基于范围的 for
循环的不同用法:
cpp
#include <iostream>
#include <vector>
void iterateVector() {
std::vector<int> v = {0, 1, 2, 3, 4, 5};
// 按常量引用访问
for (const int& i : v) {
std::cout << i << ' ';
}
std::cout << '\n';
// 按值访问
for (auto i : v) {
std::cout << i << ' ';
}
std::cout << '\n';
// 按转发引用访问
for (auto&& i : v) {
std::cout << i << ' ';
}
std::cout << '\n';
}
void iterateArray() {
int a[] = {0, 1, 2, 3, 4, 5};
// 使用数组
for (int n : a) {
std::cout << n << ' ';
}
std::cout << '\n';
// 不使用循环变量
for ([[maybe_unused]] int n : a) {
std::cout << 1 << ' ';
}
std::cout << '\n';
}
void useInitStatement() {
std::vector<int> v = {0, 1, 2, 3, 4, 5};
// 使用初始化语句
for (auto n = v.size(); auto i : v) {
std::cout << --n + i << ' ';
}
std::cout << '\n';
}
void useAliasDeclaration() {
std::vector<int> v = {0, 1, 2, 3, 4, 5};
// 使用别名声明作为初始化语句
for (using elem_t = decltype(v)::value_type; elem_t i : v) {
std::cout << i << ' ';
}
std::cout << '\n';
}
int main() {
iterateVector();
iterateArray();
useInitStatement();
useAliasDeclaration();
return 0;
}
总结
C++ 的基于范围的 for
循环提供了一种简洁、易读的方式遍历容器或数组中的元素。它不仅简化了传统的 for
循环语法,还引入了许多高级特性,如转发引用、初始化语句和临时对象的生命周期延长,使得代码更加灵活和强大。
理解这些特性和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理复杂迭代逻辑的情况下。基于范围的 for
循环特别适合用于标准库容器和自定义类,只要你确保这些类提供了适当的 begin()
和 end()
方法。
.
C++ while
和 do-while
循环详解
在 C++ 中,while
和 do-while
循环是两种常用的控制结构,用于有条件地重复执行语句。它们的主要区别在于 while
循环在每次迭代前检查条件,而 do-while
循环则至少执行一次循环体后再检查条件。
1. while
循环
基本语法
cpp
attr (可选) while ( condition ) statement
attr
: 从 C++11 开始,可以在while
循环前添加任意数量的属性。condition
: 条件表达式,决定是否继续执行循环体。可以是:- 表达式:通常是一个布尔表达式,用于决定是否继续执行循环体。
- 简单声明 :从 C++26 开始,
condition
可以是一个简单声明,其值将被转换为bool
类型。
statement
: 一个语句(通常是复合语句),表示循环体。
等效的 if-goto
结构
while
循环等效于以下 if-goto
结构:
cpp
/* label */ :
{
if ( condition )
{
statement;
goto /* label */;
}
}
关键特性
- 条件表达式 :
condition
是一个表达式,其结果会被隐式转换为bool
类型。如果转换是非法的,则程序是非法的。 - 声明作为条件 :从 C++26 开始,
condition
可以是一个简单声明,声明的变量将在每次迭代中创建和销毁。 - 作用域 :无论
statement
是否是复合语句,它都引入了一个块作用域,其中声明的变量只在循环体内可见。 break
和continue
:可以在statement
中使用break
语句终止循环,使用continue
语句跳过当前迭代并继续下一次迭代。
示例代码
cpp
#include <iostream>
int main() {
// 单条语句作为循环体
int i = 0;
while (i < 10)
i++;
std::cout << i << '\n';
// 复合语句作为循环体
int j = 2;
while (j < 9) {
std::cout << j << ' ';
j += 2;
}
std::cout << '\n';
// 使用声明作为条件
char cstr[] = "Hello";
int k = 0;
while (char c = cstr[k++])
std::cout << c;
std::cout << '\n';
return 0;
}
输出
10
2 4 6 8
Hello
2. do-while
循环
基本语法
cpp
attr (可选) do statement while ( expression );
attr
: 从 C++11 开始,可以在do-while
循环前添加任意数量的属性。statement
: 一个语句(通常是复合语句),表示循环体。expression
: 表达式,决定是否继续执行循环体。表达式的值会被隐式转换为bool
类型。
解释
do-while
循环的特点是它会无条件地执行一次循环体,然后在每次循环体完成后检查 expression
。如果 expression
的结果为 true
,则再次执行循环体;否则,循环结束。
关键特性
- 至少执行一次 :
do-while
循环至少执行一次循环体,然后再检查条件。 break
和continue
:可以在statement
中使用break
语句终止循环,使用continue
语句跳过当前迭代并继续下一次迭代。- 作用域 :
statement
引入一个块作用域,其中声明的变量只在循环体内可见。
示例代码
cpp
#include <algorithm>
#include <iostream>
#include <string>
int main() {
// 使用复合语句作为循环体
int j = 2;
do {
j += 2;
std::cout << j << ' ';
} while (j < 9);
std::cout << '\n';
// 使用单条语句作为循环体
std::string s = "aba";
std::sort(s.begin(), s.end());
do
std::cout << s << '\n';
while (std::next_permutation(s.begin(), s.end()));
return 0;
}
输出
4 6 8 10
aab
aba
baa
3. 无限循环与未定义行为
作为 C++ 进步保证的一部分,如果一个循环不是一个平凡的无限循环(自 C++26 起)且没有可观察的行为,则其行为是未定义的。编译器可以删除此类循环。因此,编写循环时应确保它们最终会终止,或者具有明确的退出条件。
4. 比较 while
和 do-while
循环
特性 | while 循环 |
do-while 循环 |
---|---|---|
条件检查时机 | 在每次迭代前检查条件 | 在每次迭代后检查条件 |
至少执行一次 | 不一定(如果条件初始为 false ) |
至少执行一次 |
适用场景 | 适用于需要先检查条件再执行的场景 | 适用于需要至少执行一次的场景 |
总结
while
和 do-while
循环是 C++ 中两种重要的控制结构,分别适用于不同的场景。while
循环在每次迭代前检查条件,适用于需要先检查条件再执行的场景;而 do-while
循环则至少执行一次循环体,适用于需要至少执行一次的场景。
理解这些特性和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理复杂迭代逻辑的情况下。选择合适的循环类型可以使代码更加简洁和易读。
C++ continue
和 break
语句详解
在 C++ 中,continue
和 break
是两种用于控制循环和 switch
语句执行流程的关键字。它们分别用于跳过当前迭代的剩余部分和终止整个循环或 switch
语句。
1. continue
语句
基本语法
cpp
attr (可选) continue;
attr
: 从 C++11 开始,可以在continue
语句前添加任意数量的属性。continue
: 关键字,用于跳过当前循环体的剩余部分,并立即开始下一次迭代。
解释
continue
语句的作用类似于使用 goto
跳到循环体的末尾。具体来说:
while
循环 :continue
会导致程序跳转到条件检查处,重新评估条件并决定是否继续下一次迭代。do-while
循环 :continue
会导致程序跳转到条件表达式处,重新评估条件并决定是否继续下一次迭代。for
和range-for
循环 :continue
会导致程序跳过当前迭代的剩余部分,直接执行更新表达式(如果有),然后重新评估条件并决定是否继续下一次迭代。
示例代码
cpp
#include <iostream>
int main() {
// 使用 continue 跳过 i != 5 的情况
for (int i = 0; i < 10; ++i) {
if (i != 5)
continue;
std::cout << i << ' '; // 只有当 i == 5 时才会输出
}
std::cout << '\n';
// 嵌套循环中使用 continue
for (int j = 0; j < 2; ++j) {
for (int k = 0; k < 5; ++k) {
if (k == 3)
continue; // 只影响内层循环
std::cout << '(' << j << ',' << k << ") ";
}
}
std::cout << '\n';
return 0;
}
输出
5
(0,0) (0,1) (0,2) (0,4) (1,0) (1,1) (1,2) (1,4)
2. break
语句
基本语法
cpp
attr (可选) break;
attr
: 从 C++11 开始,可以在break
语句前添加任意数量的属性。break
: 关键字,用于立即终止最内层的for
、while
、do-while
循环或switch
语句。
解释
break
语句会立即终止最内层的循环或 switch
语句,并将控制权转移到该语句之后的第一条语句。具体来说:
while
、do-while
、for
和range-for
循环 :break
会立即终止循环,不再进行后续的迭代。switch
语句 :break
会终止当前的case
分支,防止"贯穿"到下一个case
或default
分支。
示例代码
cpp
#include <iostream>
int main() {
// 使用 break 终止 switch 语句
int i = 2;
switch (i) {
case 1: std::cout << "1"; // 可能会有警告:fall through
case 2: std::cout << "2"; // 执行从这个 case 标签开始 (+警告)
case 3: std::cout << "3"; // 可能会有警告:fall through
case 4: // 可能会有警告:fall through
case 5: std::cout << "45"; //
break; // 终止后续语句的执行
case 6: std::cout << "6";
}
std::cout << '\n';
// 使用 break 终止嵌套循环
for (char c = 'a'; c < 'c'; c++) {
for (int i = 0; i < 5; i++) {
if (i == 2)
break; // 只影响内层循环
std::cout << c << i << ' ';
}
}
std::cout << '\n';
return 0;
}
输出
2345
a0 a1 b0 b1
3. goto
语句
虽然 goto
语句在现代编程中不推荐使用,但在某些情况下它仍然可以提供更简洁的控制流。goto
语句允许无条件地转移控制到同一函数中的指定标签位置。
基本语法
cpp
attr (可选) goto label;
attr
: 从 C++11 开始,可以在goto
语句前添加任意数量的属性。label
: 标签名,必须在同一函数中声明,并且可以出现在goto
语句之前或之后。
解释
- 作用范围 :
goto
语句只能跳转到同一函数中的标签。不能跳转到其他函数中的标签。 - 自动变量的析构 :如果
goto
语句导致控制离开某个作用域,则该作用域内的所有自动变量将被销毁,其顺序与构造顺序相反。 - 进入作用域的限制 :
goto
语句不能跳入任何具有非平凡构造函数或析构函数的自动变量的作用域,也不能跳入带有初始化器的变量的作用域。
示例代码
cpp
#include <iostream>
struct Object {
// 非平凡的析构函数
~Object() { std::cout << 'd'; }
};
struct Trivial {
double d1;
double d2;
}; // 平凡的构造函数和析构函数
int main() {
int a = 10;
// 使用 goto 实现循环
label:
Object obj;
std::cout << a << ' ';
a -= 2;
if (a != 0)
goto label; // 跳出 obj 的作用域,调用 obj 的析构函数
std::cout << '\n';
// 使用 goto 离开多层嵌套循环
for (int x = 0; x < 3; ++x)
for (int y = 0; y < 3; ++y) {
std::cout << '(' << x << ',' << y << ") " << '\n';
if (x + y >= 3)
goto endloop;
}
endloop:
std::cout << '\n';
// 跳入作用域的限制
goto label2; // 跳入 n 和 t 的作用域
[[maybe_unused]] int n; // 没有初始化器
[[maybe_unused]] Trivial t; // 平凡的构造函数和析构函数,没有初始化器
// int x = 1; // 错误:有初始化器
// Object obj2; // 错误:非平凡的析构函数
label2:
{
Object obj3;
goto label3; // 向前跳转,跳出 obj3 的作用域
}
label3:
std::cout << '\n';
return 0;
}
输出
10 d8 d6 d4 d2
(0,0)
(0,1)
(0,2)
(1,0)
(1,1)
(1,2)
d
d
4. return
语句
return
语句用于终止当前函数的执行,并将控制权返回给调用者。它可以返回一个值(如果函数有返回类型),或者在返回类型为 void
的函数中不返回任何值。
基本语法
cpp
attr (可选) return expression (可选); // 返回表达式的值
attr (可选) return braced-init-list; // 使用大括号初始化列表返回值 (自 C++11 起)
attr (可选) co_return expression (可选); // 协程中的返回 (自 C++20 起)
attr (可选) co_return braced-init-list; // 协程中的返回 (自 C++20 起)
attr
: 从 C++11 开始,可以在return
语句前添加任意数量的属性。expression
: 表达式,可转换为函数的返回类型。braced-init-list
: 大括号括起来的初始化列表,用于初始化返回值。
解释
- 按值返回 :
return
语句会求值expression
,并将结果返回给调用者。在返回类型为void
的函数中,expression
是可选的。 - 复制省略:C++ 编译器可能会优化掉不必要的复制或移动操作,直接将返回值初始化为目标对象。
- 协程 :在协程中,必须使用
co_return
关键字来表示最终的挂起点。 - 默认返回值 :如果控制流到达
main
函数的末尾而没有遇到return
语句,编译器会隐式插入return 0;
。 - 未定义行为 :如果控制流到达返回值函数的末尾而没有遇到
return
语句(除了main
函数和特定的协程),则行为是未定义的。
示例代码
cpp
#include <iostream>
int add(int a, int b) {
return a + b;
}
std::pair<int, int> createPair(int x, int y) {
return {x, y}; // 使用大括号初始化列表返回值
}
int main() {
std::cout << "Sum: " << add(3, 5) << '\n';
auto p = createPair(10, 20);
std::cout << "Pair: (" << p.first << ", " << p.second << ")\n";
return 0;
}
输出
Sum: 8
Pair: (10, 20)
总结
continue
:用于跳过当前循环体的剩余部分,立即开始下一次迭代。适用于for
、while
、do-while
和range-for
循环。break
:用于立即终止最内层的循环或switch
语句。适用于for
、while
、do-while
循环和switch
语句。goto
:用于无条件地转移控制到同一函数中的指定标签位置。虽然不推荐使用,但在某些复杂控制流的情况下可能有用。return
:用于终止当前函数的执行,并将控制权返回给调用者。可以返回一个值或不返回任何值,具体取决于函数的返回类型。
理解这些关键字的使用场景和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理复杂的控制流逻辑时。选择合适的控制结构可以使代码更加简洁和易读。