现代C++ 10 语句

文章目录

      • [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. gotolongjmp 的特殊行为)
        • 示例代码
        • 总结
      • [C++ `switch` 语句详解](#C++ switch 语句详解)
      • [C++ `for` 循环详解](#C++ for 循环详解)
        • 基本语法
        • [等效的 `while` 循环](#等效的 while 循环)
        • 关键特性
          • [1. 初始化语句 (`init-statement`)](#1. 初始化语句 (init-statement))
          • [2. 条件 (`condition`)](#2. 条件 (condition))
          • [3. 更新表达式 (`expression`)](#3. 更新表达式 (expression))
          • [4. 循环体 (`statement`)](#4. 循环体 (statement))
        • 控制流
        • 示例代码
        • 总结
      • [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. 不同类型的 beginend)
          • [6. **避免深度复制**](#6. 避免深度复制)
        • 示例代码
        • 总结
      • [C++ `while` 和 `do-while` 循环详解](#C++ whiledo-while 循环详解)
      • [C++ `continue` 和 `break` 语句详解](#C++ continuebreak 语句详解)

C++ if 语句详解

C++ 的 if 语句是用于条件执行代码块的控制结构。它允许根据一个条件表达式的真假来选择性地执行一段代码。自 C++17 和 C++23 起,if 语句引入了一些新的特性和语法糖,如 if constexprif consteval,使得编译期计算和立即函数的使用更加方便。

基本语法
cpp 复制代码
if (condition) {
    // statement-true
} else {
    // statement-false (可选)
}
  • condition : 一个表达式或声明,其结果会被转换为 bool 类型。
  • statement-true : 如果 conditiontrue,则执行这部分代码。
  • statement-false : 如果 conditionfalse,且存在 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 !constevalif 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. gotolongjmp 的特殊行为

如果 statement-true 通过 gotolongjmp 进入,则不会对 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 constexprif 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 : 一个语句(通常是复合语句),其中可以包含 casedefault 标签。
条件

condition 可以是以下几种类型之一:

  • 整型
  • 枚举类型
  • 类类型(必须能隐式转换为整型或枚举类型)

如果 condition 是类类型,则会尝试隐式转换为整型或枚举类型。如果转换后的类型受整型提升的影响,产生的值将被提升。

标签

switch 语句中的任何语句都可以用以下标签进行标记:

  • case 常量表达式 : : 指定一个常量表达式,当 condition 的值等于该常量时,控制权将转移到该标签处。
  • default : : 如果没有 case 标签匹配 condition 的值,则控制权将转移到 default 标签处。default 标签是可选的。
控制流传递

switch 语句的 condition 产生一个值时:

  • 如果有 case 标签的常量表达式与该值匹配,则控制权将传递到匹配的 case 标签处。
  • 如果没有匹配的 case 标签,但存在 default 标签,则控制权将传递到 default 标签处。
  • 如果既没有匹配的 case 标签,也没有 default 标签,则 switch 语句中的任何语句都不会被执行。

casedefault 标签本身不会改变控制流。要从 switch 语句的中间退出,可以使用 break 语句。

穿透(Fallthrough)

如果没有 break 语句,控制权将继续执行下一个 casedefault 标签的语句,这种行为称为"穿透"。编译器可能会发出警告,除非你明确表示这是故意的行为,例如使用 [[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 语句提供了一种简洁的方式来处理多个离散的值匹配情况。通过 casedefault 标签,你可以轻松地控制程序的执行路径。此外,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-statementcondition 的作用域相同。
  • statementexpression 的作用域不相交,并且嵌套在 init-statementcondition 的作用域内。
  • 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-statementconditionexpression,你可以精确地控制循环的行为。此外,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-exprend-expr 的定义取决于 range-initializer 的类型:
    • 如果 range 是对数组类型的引用,则 begin-exprrangeend-exprrange + N,其中 N 是数组的大小。
    • 如果 range 是对类类型的引用,并且类中有 begin()end() 成员函数,则 begin-exprrange.begin()end-exprrange.end()
    • 否则,begin-exprbegin(range)end-exprend(range),其中 beginend 是通过依赖于参数的查找(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::vectorstd::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. 不同类型的 beginend

从 C++17 开始,begin-exprend-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++ whiledo-while 循环详解

在 C++ 中,whiledo-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 是否是复合语句,它都引入了一个块作用域,其中声明的变量只在循环体内可见。
  • breakcontinue :可以在 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 循环至少执行一次循环体,然后再检查条件。
  • breakcontinue :可以在 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. 比较 whiledo-while 循环
特性 while 循环 do-while 循环
条件检查时机 在每次迭代前检查条件 在每次迭代后检查条件
至少执行一次 不一定(如果条件初始为 false 至少执行一次
适用场景 适用于需要先检查条件再执行的场景 适用于需要至少执行一次的场景
总结

whiledo-while 循环是 C++ 中两种重要的控制结构,分别适用于不同的场景。while 循环在每次迭代前检查条件,适用于需要先检查条件再执行的场景;而 do-while 循环则至少执行一次循环体,适用于需要至少执行一次的场景。

理解这些特性和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理复杂迭代逻辑的情况下。选择合适的循环类型可以使代码更加简洁和易读。

C++ continuebreak 语句详解

在 C++ 中,continuebreak 是两种用于控制循环和 switch 语句执行流程的关键字。它们分别用于跳过当前迭代的剩余部分和终止整个循环或 switch 语句。

1. continue 语句
基本语法
cpp 复制代码
attr (可选) continue;
  • attr : 从 C++11 开始,可以在 continue 语句前添加任意数量的属性。
  • continue: 关键字,用于跳过当前循环体的剩余部分,并立即开始下一次迭代。
解释

continue 语句的作用类似于使用 goto 跳到循环体的末尾。具体来说:

  • while 循环continue 会导致程序跳转到条件检查处,重新评估条件并决定是否继续下一次迭代。
  • do-while 循环continue 会导致程序跳转到条件表达式处,重新评估条件并决定是否继续下一次迭代。
  • forrange-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 : 关键字,用于立即终止最内层的 forwhiledo-while 循环或 switch 语句。
解释

break 语句会立即终止最内层的循环或 switch 语句,并将控制权转移到该语句之后的第一条语句。具体来说:

  • whiledo-whileforrange-for 循环break 会立即终止循环,不再进行后续的迭代。
  • switch 语句break 会终止当前的 case 分支,防止"贯穿"到下一个 casedefault 分支。
示例代码
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 :用于跳过当前循环体的剩余部分,立即开始下一次迭代。适用于 forwhiledo-whilerange-for 循环。
  • break :用于立即终止最内层的循环或 switch 语句。适用于 forwhiledo-while 循环和 switch 语句。
  • goto:用于无条件地转移控制到同一函数中的指定标签位置。虽然不推荐使用,但在某些复杂控制流的情况下可能有用。
  • return:用于终止当前函数的执行,并将控制权返回给调用者。可以返回一个值或不返回任何值,具体取决于函数的返回类型。

理解这些关键字的使用场景和规则可以帮助你编写更清晰、更高效的代码,特别是在需要处理复杂的控制流逻辑时。选择合适的控制结构可以使代码更加简洁和易读。

相关推荐
zjw_rp10 分钟前
Spring-AOP
java·后端·spring·spring-aop
呆萌很14 分钟前
C++ 集合 list 使用
c++
Oneforlove_twoforjob22 分钟前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder31 分钟前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
向宇it39 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行41 分钟前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate
诚丞成1 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
星河梦瑾2 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富2 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想2 小时前
JMeter 使用详解
java·jmeter