第4章 复合语句和控制语句

复合语句(语句块)

复合语句是由一对大括号{}包围的一组语句,也称为语句块:

1
2
3
4
5
6
7
8
{
// 语句块开始
int x = 10;
int y = 20;
int sum = x + y;
std::cout << "Sum: " << sum << std::endl;
// 语句块结束
}

语句块的特点:

  1. 作用域:语句块内声明的变量只在语句块内有效
  2. 嵌套:语句块可以嵌套使用
  3. 空语句块:空的语句块是合法的,不执行任何操作

条件语句

if 语句

基本形式

1
2
3
if (condition) {
// 条件为真时执行
}

if-else 形式

1
2
3
4
5
if (condition) {
// 条件为真时执行
} else {
// 条件为假时执行
}

if-else if-else 形式

1
2
3
4
5
6
7
if (condition1) {
// 条件1为真时执行
} else if (condition2) {
// 条件2为真时执行
} else {
// 所有条件都为假时执行
}

嵌套 if 语句

1
2
3
4
5
6
7
8
9
if (condition1) {
if (condition2) {
// 条件1和条件2都为真时执行
} else {
// 条件1为真,条件2为假时执行
}
} else {
// 条件1为假时执行
}

if 语句的初始化语句(C++17+)

C++17引入了if语句的初始化语句,允许在if语句中声明和初始化变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在if语句中初始化变量
if (int x = getValue(); x > 0) {
std::cout << "x is positive: " << x << std::endl;
} else {
std::cout << "x is non-positive: " << x << std::endl;
}

// 在if语句中初始化智能指针
if (auto ptr = std::make_unique<Person>("Alice", 30); ptr) {
ptr->display();
}

// 在if语句中初始化容器迭代器
std::vector<int> numbers = {1, 2, 3, 4, 5};
if (auto it = std::find(numbers.begin(), numbers.end(), 3); it != numbers.end()) {
std::cout << "Found: " << *it << std::endl;
}

if constexpr 语句(C++17+)

C++17引入的if constexpr语句,用于编译时条件判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 编译时条件判断
template <typename T>
void printTypeInfo(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral type: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating point type: " << value << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String type: " << value << std::endl;
} else {
std::cout << "Other type" << std::endl;
}
}

// 使用
printTypeInfo(42); // 输出:Integral type: 42
printTypeInfo(3.14); // 输出:Floating point type: 3.14
printTypeInfo(std::string("Hello")); // 输出:String type: Hello

if constexpr的特点:

  1. 编译时执行:条件在编译时求值,未满足的分支会被完全忽略
  2. 类型安全:可以在不同分支中使用不同类型的代码
  3. 零开销:未使用的分支不会生成任何代码
  4. 模板友好:特别适合在模板代码中根据类型执行不同操作

switch 语句

1
2
3
4
5
6
7
8
9
10
11
12
switch (expression) {
case value1:
// 处理value1
break;
case value2:
// 处理value2
break;
// 更多case语句
default:
// 处理其他情况
break;
}

switch语句的特点:

  1. 表达式类型:表达式必须是整型、字符型或枚举类型
  2. case标签:每个case标签必须是常量表达式
  3. break语句:如果没有break语句,程序会继续执行下一个case
  4. default分支:可选的,处理所有未匹配的情况

switch 语句的初始化语句(C++17+)

C++17引入了switch语句的初始化语句,允许在switch语句中声明和初始化变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 在switch语句中初始化变量
switch (int day = getDayOfWeek(); day) {
case 1:
std::cout << "Monday" << std::endl;
break;
case 2:
std::cout << "Tuesday" << std::endl;
break;
// 更多case
default:
std::cout << "Invalid day" << std::endl;
break;
}

// 在switch语句中初始化智能指针
switch (auto ptr = createObject(); ptr->getType()) {
case ObjectType::TypeA:
ptr->processTypeA();
break;
case ObjectType::TypeB:
ptr->processTypeB();
break;
default:
ptr->processDefault();
break;
}

switch 语句与枚举类

使用强类型枚举(enum class)可以提高switch语句的类型安全性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum class Color {
Red,
Green,
Blue
};

void printColor(Color color) {
switch (color) {
case Color::Red:
std::cout << "Red" << std::endl;
break;
case Color::Green:
std::cout << "Green" << std::endl;
break;
case Color::Blue:
std::cout << "Blue" << std::endl;
break;
// 不需要default分支,因为所有枚举值都已覆盖
}
}

switch 语句与模式匹配(C++26 预览)

C++26将引入模式匹配,使switch语句更加强大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// C++26 模式匹配示例
struct Point {
int x;
int y;
};

void processPoint(const Point& p) {
switch (p) {
case {0, 0}:
std::cout << "Origin" << std::endl;
break;
case {x, 0}:
std::cout << "On x-axis: " << x << std::endl;
break;
case {0, y}:
std::cout << "On y-axis: " << y << std::endl;
break;
case {x, y}:
std::cout << "Point: (" << x << ", " << y << ")" << std::endl;
break;
}
}

条件运算符(三元运算符)

1
condition ? expression1 : expression2;

如果条件为真,执行expression1并返回其结果;否则执行expression2并返回其结果。

循环语句

while 循环

1
2
3
while (condition) {
// 循环体
}

while循环的特点:

  1. 先判断后执行:条件为真时才执行循环体
  2. 可能不执行:如果初始条件为假,循环体一次也不执行

do-while 循环

1
2
3
do {
// 循环体
} while (condition);

do-while循环的特点:

  1. 先执行后判断:至少执行一次循环体
  2. 条件为真时继续:执行完循环体后,条件为真则继续循环

for 循环

1
2
3
for (initialization; condition; update) {
// 循环体
}

for循环的执行过程:

  1. 执行初始化语句
  2. 判断条件,如果为假,结束循环
  3. 执行循环体
  4. 执行更新语句
  5. 回到步骤2

范围 for 循环(C++11+)

1
2
3
for (declaration : collection) {
// 循环体
}

范围for循环用于遍历容器、数组等集合类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
std::cout << num << " ";
}

// 使用引用避免拷贝
for (int& num : numbers) {
num *= 2;
}

// 使用const引用读取
for (const int& num : numbers) {
std::cout << num << " ";
}

范围 for 循环的初始化语句(C++20+)

C++20引入了范围for循环的初始化语句,允许在循环开始前声明和初始化变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在范围for循环中初始化计数器
for (int i = 0; auto& num : numbers) {
std::cout << "Index " << i << ": " << num << std::endl;
i++;
}

// 在范围for循环中初始化临时容器
for (auto temp = createTemporaryData(); auto& item : temp) {
processItem(item);
}

// 在范围for循环中初始化迭代器和索引
std::map<std::string, int> scores = { {"Alice", 95}, {"Bob", 87}, {"Charlie", 92} };
for (auto it = scores.begin(); auto& [name, score] : scores) {
std::cout << "Rank " << std::distance(scores.begin(), it) + 1 << ": "
<< name << " - " << score << std::endl;
++it;
}

范围 for 循环与结构化绑定(C++17+)

结合结构化绑定,范围for循环可以更方便地遍历键值对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 遍历map的键值对
std::map<std::string, int> scores = { {"Alice", 95}, {"Bob", 87}, {"Charlie", 92} };
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}

// 遍历tuple的元素
std::vector<std::tuple<std::string, int, double>> students = {
{"Alice", 20, 3.8},
{"Bob", 21, 3.5},
{"Charlie", 19, 3.9}
};
for (const auto& [name, age, gpa] : students) {
std::cout << "Name: " << name << ", Age: " << age << ", GPA: " << gpa << std::endl;
}

// 遍历自定义结构体
struct Point {
int x;
int y;
};
std::vector<Point> points = { {1, 2}, {3, 4}, {5, 6} };
for (const auto& [x, y] : points) {
std::cout << "Point: (" << x << ", " << y << ")" << std::endl;
}

范围 for 循环与视图(C++20+)

结合C++20的Ranges库,范围for循环可以更灵活地处理数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <ranges>
#include <vector>
#include <iostream>

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 过滤偶数并乘以2
auto evenDoubled = numbers | std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; });

for (int num : evenDoubled) {
std::cout << num << " "; // 输出:4 8 12 16 20
}

// 反向遍历
auto reversed = numbers | std::views::reverse;
for (int num : reversed) {
std::cout << num << " "; // 输出:10 9 8 7 6 5 4 3 2 1
}

// 切片
auto sliced = numbers | std::views::drop(2) | std::views::take(5);
for (int num : sliced) {
std::cout << num << " "; // 输出:3 4 5 6 7
}

return 0;
}

跳转语句

break 语句

break语句用于:

  1. 跳出循环:提前结束for、while、do-while循环
  2. 跳出switch语句:结束switch语句的执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 跳出循环
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当i=5时跳出循环
}
std::cout << i << " ";
}

// 跳出switch语句
switch (grade) {
case 'A':
std::cout << "Excellent" << std::endl;
break;
case 'B':
std::cout << "Good" << std::endl;
break;
default:
std::cout << "Other" << std::endl;
break;
}

continue 语句

continue语句用于跳过本次循环的剩余部分,直接进入下一次循环:

1
2
3
4
5
6
7
// 跳过偶数
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
std::cout << i << " "; // 只输出奇数
}

return 语句

return语句用于:

  1. 结束函数执行:立即退出当前函数
  2. 返回值:可选地返回一个值给函数调用者
1
2
3
4
5
6
7
8
9
10
// 无返回值的函数
void printHello() {
std::cout << "Hello" << std::endl;
return; // 可选
}

// 有返回值的函数
int add(int a, int b) {
return a + b; // 返回a + b的值
}

return 语句与移动语义(C++11+)

1
2
3
4
5
6
7
8
9
10
// 返回局部对象,会触发移动语义
std::vector<int> createVector() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v; // C++11+:移动语义,避免拷贝
}

// 返回值优化(RVO)
std::string createString() {
return "Hello, World!"; // 编译器优化,直接构造返回值
}

return 语句与结构化绑定(C++17+)

1
2
3
4
5
6
7
8
// 返回多个值
std::tuple<int, double, std::string> getValues() {
return {42, 3.14, "Hello"};
}

// 使用结构化绑定接收返回值
auto [i, d, s] = getValues();
std::cout << i << ", " << d << ", " << s << std::endl;

[[noreturn]] 属性(C++11+)

C++11引入的[[noreturn]]属性,用于标记不会返回的函数:

1
2
3
4
5
6
7
8
9
10
11
// 标记不会返回的函数
[[noreturn]] void fatalError(const std::string& message) {
std::cerr << "Fatal error: " << message << std::endl;
std::exit(EXIT_FAILURE); // 不会返回
}

[[noreturn]] void infiniteLoop() {
while (true) {
// 无限循环,不会返回
}
}

goto 语句

goto语句用于无条件跳转到函数内的标签位置:

1
2
3
4
5
6
7
8
9
if (error) {
goto errorHandler; // 跳转到errorHandler标签
}

// 正常代码
std::cout << "No error" << std::endl;

errorHandler: // 标签
std::cout << "Error occurred" << std::endl;

注意:goto语句可能会使代码结构混乱,应尽量避免使用。

循环控制的高级技巧

多重循环

多重循环是指循环的嵌套使用:

1
2
3
4
5
6
7
// 打印99乘法表
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
std::cout << j << "*" << i << "=" << i*j << "\t";
}
std::cout << std::endl;
}

循环中的 break 和 continue

在多重循环中,break和continue只影响最内层的循环:

1
2
3
4
5
6
7
8
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (j == 2) {
break; // 只跳出内层循环
}
std::cout << "i=" << i << ", j=" << j << std::endl;
}
}

使用标志变量控制循环

1
2
3
4
5
6
7
8
9
10
bool found = false;
for (int i = 0; i < 10 && !found; i++) {
for (int j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
found = true;
break;
}
std::cout << "i=" << i << ", j=" << j << std::endl;
}
}

异常处理

try-catch 语句

1
2
3
4
5
6
7
8
9
10
11
12
try {
// 可能抛出异常的代码
if (someCondition) {
throw std::exception("Error occurred");
}
} catch (const std::exception& e) {
// 捕获异常并处理
std::cout << "Exception caught: " << e.what() << std::endl;
} catch (...) {
// 捕获所有其他异常
std::cout << "Unknown exception caught" << std::endl;
}

抛出异常

1
2
3
4
5
6
7
8
void validateAge(int age) {
if (age < 0) {
throw std::invalid_argument("Age cannot be negative");
}
if (age > 120) {
throw std::out_of_range("Age is out of range");
}
}

使用std::source_location(C++20+)

C++20引入的std::source_location类用于获取源代码位置信息,可用于异常处理中提供更详细的错误位置。

使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <stdexcept>
#include <source_location>

// 自定义异常类
class MyException : public std::exception {
private:
std::string message;
std::source_location location;

public:
MyException(const std::string& msg,
const std::source_location& loc = std::source_location::current())
: message(msg), location(loc) {}

const char* what() const noexcept override {
return message.c_str();
}

const std::source_location& getLocation() const {
return location;
}
};

// 抛出异常的函数
void riskyOperation(int value, const std::source_location& loc = std::source_location::current()) {
if (value < 0) {
throw MyException("Negative value not allowed", loc);
}
}

int main() {
try {
riskyOperation(-1);
} catch (const MyException& e) {
const auto& loc = e.getLocation();
std::cout << "Exception caught: " << e.what() << std::endl;
std::cout << "File: " << loc.file_name() << std::endl;
std::cout << "Line: " << loc.line() << std::endl;
std::cout << "Function: " << loc.function_name() << std::endl;
}

return 0;
}

异常规格说明(C++11前)

1
2
3
4
5
6
7
8
9
// C++11前的异常规格说明(已废弃)
void func() throw(int, std::exception) {
// 可能抛出int或std::exception类型的异常
}

// 不抛出任何异常
void func() throw() {
// 不抛出任何异常
}

noexcept 说明符(C++11+)

1
2
3
4
5
6
7
8
9
10
11
12
// 不抛出任何异常
void func() noexcept {
// 不抛出任何异常
}

// 条件性noexcept
void func() noexcept(condition) {
// 根据condition决定是否抛出异常
}

// 检查函数是否 noexcept
bool isNoexcept = noexcept(func());

控制语句的最佳实践

1. 代码可读性

  • 缩进:使用一致的缩进风格
  • 空格:在运算符两侧、逗号后添加空格
  • 括号:即使只有一条语句,也使用大括号包围
  • 注释:为复杂的条件和循环添加注释

2. 避免深度嵌套

  • 提取函数:将复杂的嵌套代码提取为单独的函数
  • 使用早期返回:对于简单的错误情况,使用早期返回
  • 简化条件:将复杂的条件表达式分解为多个简单的条件

3. 循环优化

  • 减少循环内计算:将循环不变的计算移到循环外
  • 避免循环内的内存分配:在循环外分配内存
  • 使用合适的循环类型:根据具体情况选择for、while或do-while循环
  • 考虑使用STL算法:对于常见的循环模式,使用STL算法

4. 错误处理

  • 使用异常:对于可恢复的错误,使用异常处理
  • 使用错误码:对于性能敏感的代码,使用错误码
  • 避免使用goto:尽量使用结构化的控制语句
  • 资源管理:使用RAII(资源获取即初始化)管理资源

常见错误和陷阱

1. 无限循环

1
2
3
4
5
6
7
8
9
10
11
// 错误:无限循环
while (true) {
std::cout << "Hello" << std::endl;
// 没有break语句
}

// 错误:条件永远为真
for (int i = 0; i < 10; j++) {
std::cout << i << std::endl;
// 变量名错误,应该是i++
}

2. 边界条件错误

1
2
3
4
5
6
7
8
9
10
11
// 错误:数组越界
int arr[5];
for (int i = 0; i <= 5; i++) {
arr[i] = i; // 当i=5时越界
}

// 错误:字符串索引越界
std::string s = "Hello";
for (int i = 0; i <= s.length(); i++) {
std::cout << s[i]; // 当i=s.length()时越界
}

3. 逻辑错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误:使用赋值运算符而不是相等运算符
if (x = 5) { // 总是为真,因为x被赋值为5
std::cout << "x is 5" << std::endl;
}

// 错误:逻辑运算符优先级
if (a && b || c) { // 等同于(a && b) || c
// 可能不是预期的行为
}

// 错误:浮点数比较
if (x == 0.1) { // 浮点数有精度问题,可能永远为假
std::cout << "x is 0.1" << std::endl;
}

4. 语法错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 错误:缺少分号
if (x > y) {
std::cout << "x is greater than y"
} // 缺少分号

// 错误:括号不匹配
if (x > y {
std::cout << "x is greater than y" << std::endl;
} // 缺少右括号

// 错误:case标签后缺少冒号
switch (x) {
case 1 // 缺少冒号
std::cout << "x is 1" << std::endl;
break;
}

小结

本章介绍了C++中的复合语句和控制语句,包括:

  1. 复合语句:由大括号包围的语句块,用于创建局部作用域
  2. 条件语句:if、if-else、if-else if-else和switch语句,用于根据条件执行不同的代码
  3. 循环语句:while、do-while、for和范围for循环,用于重复执行代码
  4. 跳转语句:break、continue、return和goto语句,用于控制程序的执行流程
  5. 异常处理:try-catch语句,用于处理运行时错误

控制语句是C++程序的重要组成部分,它们使程序能够根据不同的条件执行不同的操作,或者重复执行某些操作。掌握好控制语句的使用方法,对于编写结构清晰、逻辑正确的程序至关重要。

在使用控制语句时,应注意代码的可读性和可维护性,避免深度嵌套和复杂的条件表达式。同时,要注意处理边界条件和异常情况,确保程序的健壮性。

在后续章节中,我们将学习函数、数组、指针等更高级的C++特性,这些特性将与控制语句结合使用,帮助我们构建更复杂、更强大的程序。