第6章 循环和关系表达式

循环语句的基本概念

循环语句用于重复执行一段代码,直到满足特定条件为止。C++提供了三种主要的循环语句:whiledo-whilefor

while 循环

基本语法

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

执行流程

  1. 检查条件表达式
  2. 如果条件为真,执行循环体
  3. 重复步骤1和2,直到条件为假

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 计算1到100的和
int sum = 0;
int i = 1;
while (i <= 100) {
sum += i;
i++;
}
std::cout << "Sum: " << sum << std::endl;

// 读取用户输入直到输入0
int number;
std::cout << "Enter numbers (0 to stop): " << std::endl;
while (true) {
std::cin >> number;
if (number == 0) {
break;
}
std::cout << "You entered: " << number << std::endl;
}

do-while 循环

基本语法

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

执行流程

  1. 执行循环体
  2. 检查条件表达式
  3. 如果条件为真,重复步骤1和2
  4. 如果条件为假,结束循环

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 计算1到100的和
int sum = 0;
int i = 1;
do {
sum += i;
i++;
} while (i <= 100);
std::cout << "Sum: " << sum << std::endl;

// 读取用户输入直到输入有效的数字
int age;
do {
std::cout << "Enter your age: " << std::endl;
std::cin >> age;
if (std::cin.fail()) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Invalid input. Please try again." << std::endl;
}
} while (age < 0 || age > 120);
std::cout << "Your age is: " << age << std::endl;

for 循环

基本语法

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

执行流程

  1. 执行初始化语句
  2. 检查条件表达式
  3. 如果条件为假,结束循环
  4. 如果条件为真,执行循环体
  5. 执行更新语句
  6. 重复步骤2到5

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 计算1到100的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
std::cout << "Sum: " << sum << std::endl;

// 打印数组元素
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; i++) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}

// 嵌套for循环:打印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;
}

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

基本语法

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

示例

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
// 遍历数组
int arr[] = {1, 2, 3, 4, 5};
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;

// 遍历vector
std::vector<int> vec = {10, 20, 30, 40, 50};
for (auto value : vec) {
std::cout << value << " ";
}
std::cout << std::endl;

// 遍历字符串
std::string str = "Hello";
for (char c : str) {
std::cout << c << " ";
}
std::cout << std::endl;

// 使用引用避免复制
for (int& value : vec) {
value *= 2; // 修改原始值
}

// 使用const引用读取
for (const auto& value : vec) {
std::cout << value << " ";
}
std::cout << std::endl;

范围 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 (int i = 0; auto& num : vec) {
std::cout << "Index " << i << ": " << num << std::endl;
i++;
}

// 初始化临时容器
for (auto temp = createTemporaryData(); auto& item : temp) {
processItem(item);
}

// 初始化迭代器和索引
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 找到第一个大于100的数
std::vector<int> numbers = {10, 50, 150, 200, 250};
for (int num : numbers) {
if (num > 100) {
std::cout << "First number greater than 100: " << num << std::endl;
break;
}
}

// 跳出嵌套循环
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i == 2 && j == 2) {
std::cout << "Breaking at i=" << i << ", j=" << j << std::endl;
break; // 只跳出内层循环
}
std::cout << "i=" << i << ", j=" << j << std::endl;
}
}

continue 语句

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 打印奇数
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
std::cout << i << " ";
}
std::cout << std::endl;

// 计算非负数的和
std::vector<int> numbers = {10, -5, 20, -15, 30};
int sum = 0;
for (int num : numbers) {
if (num < 0) {
continue; // 跳过负数
}
sum += num;
}
std::cout << "Sum of non-negative numbers: " << sum << std::endl;

goto 语句

goto语句可以无条件跳转到循环内的标签:

1
2
3
4
5
6
7
8
9
10
11
// 使用goto跳出多层循环
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i == 2 && j == 2) {
goto endLoop; // 跳转到标签
}
std::cout << "i=" << i << ", j=" << j << std::endl;
}
}
endLoop:
std::cout << "Loop ended" << std::endl;

注意:尽量避免使用goto语句,因为它会使代码结构混乱。

关系表达式

关系运算符

运算符描述示例结果
<小于5 < 3false
<=小于等于5 <= 5true
>大于5 > 3true
>=大于等于5 >= 10false
==等于5 == 5true
!=不等于5 != 3true

关系表达式的返回值

关系表达式返回布尔值:

1
2
3
4
5
6
7
8
9
10
bool result1 = (5 > 3); // true
bool result2 = (5 == 3); // false
bool result3 = (5 != 3); // true

// 非布尔值的关系表达式
int a = 5;
int b = 3;

int result4 = (a > b); // 1 (true)
int result5 = (a < b); // 0 (false)

关系运算符的优先级

关系运算符的优先级高于逻辑运算符(除了!),低于算术运算符:

1
2
3
// 算术运算符 > 关系运算符 > 逻辑运算符
bool result = (a + b > c && d - e < f);
// 等价于 ((a + b) > c) && ((d - e) < f)

循环的常见应用

1. 数值计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 计算阶乘
int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}

// 计算斐波那契数列
void printFibonacci(int n) {
int a = 0, b = 1;
std::cout << "Fibonacci sequence: ";
for (int i = 0; i < n; i++) {
std::cout << a << " ";
int next = a + b;
a = b;
b = next;
}
std::cout << std::endl;
}

2. 数组和容器操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 查找数组中的最大值
int findMax(const int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}

// 反转字符串
std::string reverseString(const std::string& str) {
std::string reversed;
for (int i = str.length() - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}

3. 输入验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 读取有效的整数
int readInteger() {
int value;
while (true) {
std::cout << "Enter an integer: ";
std::cin >> value;
if (std::cin.good()) {
break;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Invalid input. Please try again." << std::endl;
}
return value;
}

4. 图形输出

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
// 打印三角形
int height = 5;
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= i; j++) {
std::cout << "* ";
}
std::cout << std::endl;
}

// 打印菱形
int size = 5;
for (int i = 1; i <= size; i++) {
for (int j = 1; j <= size - i; j++) {
std::cout << " ";
}
for (int j = 1; j <= 2 * i - 1; j++) {
std::cout << "*";
}
std::cout << std::endl;
}
for (int i = size - 1; i >= 1; i--) {
for (int j = 1; j <= size - i; j++) {
std::cout << " ";
}
for (int j = 1; j <= 2 * i - 1; j++) {
std::cout << "*";
}
std::cout << std::endl;
}

现代C++中的循环技巧

STL算法的使用

STL(标准模板库)提供了许多算法,可以替代传统的循环,使代码更简洁、更易读、更高效:

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
#include <algorithm>
#include <vector>
#include <iostream>

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

// 1. 排序
std::sort(numbers.begin(), numbers.end());

// 2. 查找
auto it = std::find(numbers.begin(), numbers.end(), 5);
if (it != numbers.end()) {
std::cout << "Found 5 at position: " << std::distance(numbers.begin(), it) << std::endl;
}

// 3. 计数
int count = std::count(numbers.begin(), numbers.end(), 3);
std::cout << "Count of 3: " << count << std::endl;

// 4. 条件计数
int evenCount = std::count_if(numbers.begin(), numbers.end(),
[](int n) { return n % 2 == 0; });
std::cout << "Count of even numbers: " << evenCount << std::endl;

// 5. 转换
std::vector<double> doubled(numbers.size());
std::transform(numbers.begin(), numbers.end(), doubled.begin(),
[](int n) { return n * 2.0; });

// 6. 累积
int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
std::cout << "Sum: " << sum << std::endl;

// 7. 最大值和最小值
auto minIt = std::min_element(numbers.begin(), numbers.end());
auto maxIt = std::max_element(numbers.begin(), numbers.end());
std::cout << "Min: " << *minIt << ", Max: " << *maxIt << std::endl;

return 0;
}

并行算法(C++17+)

C++17引入了并行算法,利用多核处理器提高性能:

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
#include <algorithm>
#include <execution>
#include <vector>
#include <iostream>

int main() {
std::vector<int> numbers(1000000);

// 填充数据
std::iota(numbers.begin(), numbers.end(), 1);

// 1. 并行排序
std::sort(std::execution::par, numbers.begin(), numbers.end());

// 2. 并行转换
std::vector<double> doubled(numbers.size());
std::transform(std::execution::par, numbers.begin(), numbers.end(),
doubled.begin(), [](int n) { return n * 2.0; });

// 3. 并行累积
int sum = std::reduce(std::execution::par, numbers.begin(), numbers.end(), 0);
std::cout << "Sum: " << sum << std::endl;

return 0;
}

范围库的使用(C++20+)

C++20引入的范围库提供了更简洁、更灵活的循环方式:

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};

// 1. 过滤和转换
auto result = numbers | std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; })
| std::views::take(3);

for (int num : result) {
std::cout << num << " "; // 输出:4 8 12
}
std::cout << std::endl;

// 2. 链式操作
auto chained = numbers | std::views::reverse
| std::views::filter([](int n) { return n > 5; })
| std::views::transform([](int n) { return n * n; });

for (int num : chained) {
std::cout << num << " "; // 输出:100 81 64
}
std::cout << std::endl;

return 0;
}

循环的最佳实践

1. 循环条件

  • 明确的终止条件:确保循环有明确的终止条件,避免无限循环
  • 避免复杂条件:保持循环条件简单明了
  • 使用括号:即使循环体只有一条语句,也使用大括号包围

2. 循环变量

  • 初始化:在循环开始前正确初始化循环变量
  • 更新:确保循环变量在每次迭代中正确更新
  • 作用域:将循环变量的作用域限制在循环内部(使用for循环的初始化部分)

3. 循环体

  • 简洁:保持循环体简洁,只包含与循环相关的代码
  • 单一职责:每个循环只做一件事
  • 避免副作用:循环体不应该修改循环条件中使用的外部变量(除非是有意的)

4. 性能考虑

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

5. 可读性

  • 缩进:使用一致的缩进风格
  • 注释:为复杂的循环添加注释,说明循环的目的
  • 变量命名:使用有意义的变量名
  • 使用现代C++特性:优先使用范围for循环、STL算法和范围库,提高代码可读性

常见错误和陷阱

1. 无限循环

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

// 错误:循环变量不更新
for (int i = 0; i < 10; ) {
std::cout << i << std::endl;
// 缺少i++
}

// 错误:条件永远为真
int i = 0;
while (i < 10) {
std::cout << i << std::endl;
// 缺少i++
}

2. 循环变量的作用域

1
2
3
4
5
6
7
8
9
10
11
12
// 错误:循环变量在循环外不可见
for (int i = 0; i < 10; i++) {
std::cout << i << std::endl;
}
std::cout << "Final value of i: " << i << std::endl; // 错误:i不在作用域内

// 正确:在循环外声明循环变量
int i;
for (i = 0; i < 10; i++) {
std::cout << i << std::endl;
}
std::cout << "Final value of i: " << i << std::endl; // 正确

3. 数组越界

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

// 正确:使用正确的边界
for (int i = 0; i < 5; i++) {
arr[i] = i;
}

4. 浮点数精度问题

1
2
3
4
5
6
7
8
9
10
11
12
// 错误:浮点数精度问题导致的无限循环
double x = 0.1;
while (x != 1.0) {
std::cout << x << std::endl;
x += 0.1;
}

// 正确:使用范围比较
while (x < 1.0) {
std::cout << x << std::endl;
x += 0.1;
}

5. 循环内的条件判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 错误:循环内的条件判断导致逻辑错误
int number = 5;
while (number > 0) {
std::cout << number << std::endl;
if (number % 2 == 0) {
number /= 2;
}
// 缺少else分支,当number为奇数时会导致无限循环
}

// 正确:添加else分支
while (number > 0) {
std::cout << number << std::endl;
if (number % 2 == 0) {
number /= 2;
} else {
number -= 1;
}
}

小结

本章介绍了C++中的循环语句和关系表达式,包括:

  1. while 循环:先判断条件,再执行循环体
  2. do-while 循环:先执行循环体,再判断条件
  3. for 循环:适用于已知循环次数的情况
  4. 范围 for 循环:C++11引入,用于遍历容器和数组
  5. 循环控制语句:break、continue和goto
  6. 关系表达式:使用关系运算符比较值
  7. 循环的常见应用:数值计算、数组操作、输入验证等
  8. 循环的最佳实践:循环条件、循环变量、循环体、性能和可读性
  9. 常见错误和陷阱:无限循环、循环变量作用域、数组越界等

循环是C++程序的重要组成部分,掌握好循环的使用方法对于编写高效、可靠的程序至关重要。在后续章节中,我们将学习更高级的C++特性,如数组、指针、类等,这些特性将与循环结合使用,帮助我们构建更复杂、更强大的程序。