5.1 for循环 循环是编程中的基本控制结构,它允许我们重复执行一段代码,直到满足某个条件为止。for
循环是 C++ 中最常用的循环结构之一,特别适用于已知循环次数或需要按特定步长迭代的情况。
5.1.1 for循环的组成部分 for
循环的头部包含三个由分号 ;
分隔的部分,控制着循环的执行流程:
初始化 (Initialization):
在循环开始前执行一次 。
通常用于声明和/或初始化循环控制变量(计数器)。
可以包含多条语句,用逗号分隔(见 5.1.11)。
也可以为空。
测试条件 (Test Condition / Condition):
在每次 循环迭代开始前进行求值。
结果必须是一个布尔值 (true
或 false
) 或可以转换为布尔值(非零为 true
,零为 false
)。
如果条件为 true
,则执行循环体。
如果条件为 false
,则循环终止,程序继续执行循环后面的语句。
也可以为空,空条件被视为 true
,形成无限循环(需要其他方式跳出,如 break
)。
更新 (Update / Increment / Decrement):
在每次 循环迭代结束时 (执行完循环体之后,下次测试条件之前)执行。
通常用于修改循环控制变量(例如,递增或递减计数器)。
可以包含多条语句,用逗号分隔。
也可以为空。
语法:
1 2 3 4 5 6 7 8 9 for (initialization; test_condition; update) { statement1; statement2; } for (initialization; test_condition; update) single_statement;
执行流程:
执行 initialization
。
计算 test_condition
。
如果 test_condition
为 false
,跳出循环,执行循环后面的代码。
如果 test_condition
为 true
,执行循环体中的语句。
执行 update
。
回到步骤 2。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> int main () { for (int i = 0 ; i < 5 ; i = i + 1 ) { std ::cout << "i = " << i << std ::endl ; } std ::cout << "Loop finished." << std ::endl ; return 0 ; }
5.1.2 回到for循环 for
循环提供了一种非常结构化的方式来编写计数循环。上面的例子展示了一个典型的从 0 开始计数到某个值之前的循环。
基本计数循环示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> int main () { int sum = 0 ; for (int i = 1 ; i <= 10 ; i = i + 1 ) { sum = sum + i; } std ::cout << "Sum of 1 to 10 is: " << sum << std ::endl ; std ::cout << "Countdown:" << std ::endl ; for (int count = 5 ; count > 0 ; count = count - 1 ) { std ::cout << count << "..." << std ::endl ; } std ::cout << "Liftoff!" << std ::endl ; return 0 ; }
5.1.3 修改步长 for
循环的更新部分不一定总是加 1 或减 1。你可以根据需要指定任何有效的更新表达式。
示例:
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 #include <iostream> int main () { std ::cout << "Even numbers less than 10:" << std ::endl ; for (int i = 0 ; i < 10 ; i = i + 2 ) { std ::cout << i << " " ; } std ::cout << std ::endl ; std ::cout << "Counting down by 5s:" << std ::endl ; for (int n = 50 ; n >= 0 ; n = n - 5 ) { std ::cout << n << " " ; } std ::cout << std ::endl ; std ::cout << "Powers of 2 less than 100:" << std ::endl ; for (int p = 1 ; p < 100 ; p = p * 2 ) { std ::cout << p << " " ; } std ::cout << std ::endl ; return 0 ; }
5.1.4 使用for循环访问字符串 for
循环是遍历字符串(无论是 C 风格字符串还是 std::string
对象)中每个字符的常用方法。
示例 (C 风格字符串):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <cstring> // 为了 strlen() int main () { char message[] = "Hello" ; int len = strlen (message); std ::cout << "Characters in \"" << message << "\":" << std ::endl ; for (int i = 0 ; i < len; i = i + 1 ) { std ::cout << "Index " << i << ": " << message[i] << std ::endl ; } std ::cout << "Characters using pointer:" << std ::endl ; for (char *p = message; *p != '\0' ; p = p + 1 ) { std ::cout << *p << " " ; } std ::cout << std ::endl ; return 0 ; }
示例 (std::string):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <string> int main () { std ::string word = "World" ; std ::cout << "Characters in \"" << word << "\":" << std ::endl ; for (size_t i = 0 ; i < word.size(); i = i + 1 ) { std ::cout << "Index " << i << ": " << word[i] << std ::endl ; } return 0 ; }
5.1.5 递增运算符(++)和递减运算符(–) C++ 提供了两个非常有用的运算符来简化变量加 1 或减 1 的操作:
递增运算符 (++
): 将操作数的值增加 1。
递减运算符 (--
): 将操作数的值减少 1。
它们可以用于整数类型、浮点类型和指针类型。
用法与示例:
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 #include <iostream> int main () { int count = 5 ; double value = 10.5 ; count++; ++value; std ::cout << "Count after increment: " << count << std ::endl ; std ::cout << "Value after increment: " << value << std ::endl ; count--; --value; std ::cout << "Count after decrement: " << count << std ::endl ; std ::cout << "Value after decrement: " << value << std ::endl ; std ::cout << "Loop using ++:" << std ::endl ; for (int i = 0 ; i < 3 ; ++i) { std ::cout << "i = " << i << std ::endl ; } return 0 ; }
5.1.6 副作用和顺序点 副作用 (Side Effect): 指的是修改变量的值或执行 I/O 操作等改变程序状态的行为。递增 (++
) 和递减 (--
) 运算符都具有副作用,因为它们会修改操作数的值。
顺序点 (Sequence Point): 是程序执行过程中的一个时间点,在该点之前的所有副作用都已完成,并且后续的副作用尚未发生。C++ 标准定义了一些顺序点,例如:
分号 ;
(语句结束处)
完整表达式结束时(如 if
条件、while
条件、for
循环的三个部分之后)
函数调用之前(所有参数的副作用完成)
某些运算符(如 &&
, ||
, ,
逗号运算符)的特定位置
重要性: 在两个顺序点之间,不要 对同一个变量进行多次修改,或者既修改它又读取它(除了读取它的值以计算要写入的值之外)。否则,行为是**未定义的 (Undefined Behavior)**,编译器可能产生任何结果。
示例 (未定义行为):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> int main () { int x = 5 ; int y; y = x++; std ::cout << "y = " << y << ", x = " << x << std ::endl ; y = ++x; std ::cout << "y = " << y << ", x = " << x << std ::endl ; return 0 ; }
结论: 避免在单个表达式中对同一变量产生复杂的、依赖于副作用顺序的操作。将它们分解成多个语句通常更安全、更清晰。
5.1.7 前缀格式和后缀格式 递增 (++
) 和递减 (--
) 运算符都有两种使用形式:
前缀 (Prefix): 运算符放在操作数之前 (++x
, --x
)。
行为: 先 修改操作数的值(加 1 或减 1),然后 使用修改后的值作为整个表达式的结果。
后缀 (Postfix): 运算符放在操作数之后 (x++
, x--
)。
行为: 先 使用操作数的原始值 作为整个表达式的结果,然后 再修改操作数的值(加 1 或减 1)。
区别在于表达式的值:
表达式
行为描述
表达式的值
操作数最终值
++x
先将 x
加 1
x
的新值
新值
x++
先使用 x
的原始值
x
的原始值
新值
--x
先将 x
减 1
x
的新值
新值
x--
先使用 x
的原始值
x
的原始值
新值
用法与示例:
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 #include <iostream> int main () { int a = 5 , b = 5 ; int result_a, result_b; result_a = ++a; std ::cout << "Prefix: result_a = " << result_a << ", a = " << a << std ::endl ; result_b = b++; std ::cout << "Postfix: result_b = " << result_b << ", b = " << b << std ::endl ; int arr[] = {10 , 20 , 30 }; int index = 0 ; int val1 = arr[index++]; std ::cout << "val1 = " << val1 << ", index = " << index << std ::endl ; index = 0 ; int val2 = arr[++index]; std ::cout << "val2 = " << val2 << ", index = " << index << std ::endl ; return 0 ; }
性能建议: 对于内置类型(如 int
, double
, 指针),前缀和后缀的性能差异通常可以忽略。但对于用户定义的类类型(迭代器等),前缀形式 (++it
) 通常比后缀形式 (it++
) 效率更高 ,因为后缀形式需要创建一个临时对象来保存原始值。因此,在不需要使用原始值的情况下,养成优先使用前缀递增/递减的习惯是好的。
5.1.8 递增/递减运算符和指针 ++
和 --
运算符可以应用于指针,其效果是使指针指向内存中的下一个或上一个元素 。编译器会自动根据指针指向的数据类型的大小来调整地址。
用法与示例:
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 45 46 47 48 49 50 #include <iostream> int main () { double arr[3 ] = {1.1 , 2.2 , 3.3 }; double *ptr = arr; std ::cout << "Initial pointer: " << ptr << ", value: " << *ptr << std ::endl ; ++ptr; std ::cout << "After ++ptr: " << ptr << ", value: " << *ptr << std ::endl ; ptr++; std ::cout << "After ptr++: " << ptr << ", value: " << *ptr << std ::endl ; --ptr; std ::cout << "After --ptr: " << ptr << ", value: " << *ptr << std ::endl ; ptr--; std ::cout << "After ptr--: " << ptr << ", value: " << *ptr << std ::endl ; ptr = arr; double val; val = *ptr++; std ::cout << "val = *ptr++ : val = " << val << ", ptr now points to value " << *ptr << std ::endl ; ptr = arr; val = *++ptr; std ::cout << "val = *++ptr : val = " << val << ", ptr now points to value " << *ptr << std ::endl ; ptr = arr; ++*ptr; std ::cout << "After ++*ptr : arr[0] = " << arr[0 ] << ", ptr points to value " << *ptr << std ::endl ; ptr = arr; arr[0 ] = 1.1 ; (*ptr)++; std ::cout << "After (*ptr)++: arr[0] = " << arr[0 ] << ", ptr points to value " << *ptr << std ::endl ; return 0 ; }
优先级: 解引用 *
和前/后缀 ++
/--
的优先级相同,结合性是从右到左。为了清晰起见,当结合使用时,使用括号 ()
是个好主意,例如 (*ptr)++
。
5.1.9 组合赋值运算符 C++ 提供了一组组合赋值运算符,将算术运算和赋值运算合并为一个运算符,使代码更简洁。
运算符
示例
等价于
+=
x += y
x = x + y
-=
x -= y
x = x - y
*=
x *= y
x = x * y
/=
x /= y
x = x / y
%=
x %= y
x = x % y
&=
x &= y
x = x & y
`
=`
`x
^=
x ^= y
x = x ^ y
<<=
x <<= y
x = x << y
>>=
x >>= y
x = x >> y
用法与示例:
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 #include <iostream> int main () { int score = 100 ; int bonus = 10 ; int penalty = 5 ; int factor = 2 ; score += bonus; std ::cout << "Score after bonus: " << score << std ::endl ; score -= penalty; std ::cout << "Score after penalty: " << score << std ::endl ; score *= factor; std ::cout << "Score after factor: " << score << std ::endl ; score /= 3 ; std ::cout << "Score after division: " << score << std ::endl ; score %= 8 ; std ::cout << "Score after modulo: " << score << std ::endl ; int total = 0 ; for (int i = 1 ; i <= 5 ; ++i) { total += i; } std ::cout << "Total (1-5): " << total << std ::endl ; return 0 ; }
组合赋值运算符通常更易读,并且可能比分开写稍微高效一些。
5.1.10 复合语句(语句块) 复合语句 (Compound Statement) 或 语句块 (Block) 是由一对花括号 {}
括起来的零条或多条语句。
作用:
语法需要: 在 C++ 语法要求只能出现一条 语句的地方(例如 if
, else
, for
, while
的循环体),可以使用语句块来包含多条 语句。
创建作用域: 语句块会创建一个新的**局部作用域 (Local Scope)**。在块内声明的变量(自动存储变量)只在该块内部可见,并在块结束时销毁。
用法与示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> int main () { int x = 10 ; if (x > 5 ) { std ::cout << "x is greater than 5." << std ::endl ; int y = x * 2 ; std ::cout << "Double x is: " << y << std ::endl ; } std ::cout << "Loop with block:" << std ::endl ; for (int i = 0 ; i < 2 ; ++i) { std ::cout << " Outer loop i = " << i << std ::endl ; int j = i + 10 ; std ::cout << " Inner variable j = " << j << std ::endl ; } return 0 ; }
5.1.11 其他语法技巧——逗号运算符 逗号运算符 (,
) 是 C++ 中优先级最低的运算符。它允许将两个表达式连接成一个表达式。
行为:
先计算逗号左侧的表达式。
丢弃 左侧表达式的计算结果。
然后计算逗号右侧的表达式。
整个逗号表达式的结果是右侧表达式 的值和类型。
主要用途:
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 30 31 32 #include <iostream> int main () { int i, j; std ::cout << "Using comma in for loop:" << std ::endl ; for (i = 0 , j = 10 ; i < j; ++i, --j) { std ::cout << "i = " << i << ", j = " << j << std ::endl ; } int x; int result = (x = 5 , x + 10 ); std ::cout << "Result of comma expression: " << result << std ::endl ; std ::cout << "x after comma expression: " << x << std ::endl ; int a = 1 , b = 2 , c = 3 ; int value = a++, b += a, c += b; std ::cout << "value=" << value << ", a=" << a << ", b=" << b << ", c=" << c << std ::endl ; value = (++a, b += a, c += b); std ::cout << "value=" << value << ", a=" << a << ", b=" << b << ", c=" << c << std ::endl ; return 0 ; }
虽然逗号运算符提供了这种能力,但过度使用可能降低代码的可读性。在 for
循环的初始化和更新部分是其最常见且合理的用途。
5.1.12 关系表达式 关系表达式 (Relational Expression) 使用关系运算符来比较两个操作数的值,其结果是一个布尔值 (true
或 false
)。
关系运算符:
<
: 小于 (Less than)
>
: 大于 (Greater than)
<=
: 小于或等于 (Less than or equal to)
>=
: 大于或等于 (Greater than or equal to)
==
: 等于 (Equal to)
!=
: 不等于 (Not equal to)
这些运算符的优先级低于算术运算符,但高于赋值运算符。==
和 !=
的优先级低于其他四个关系运算符。
用法与示例:
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 #include <iostream> int main () { int score = 85 ; int passing_score = 60 ; double temp1 = 36.5 , temp2 = 37.0 ; bool isPassing = (score >= passing_score); bool isFever = (temp1 > temp2); bool isEqual = (score == 85 ); bool isNotEqual = (temp1 != temp2); std ::cout << std ::boolalpha; std ::cout << "Score: " << score << ", Passing Score: " << passing_score << std ::endl ; std ::cout << "Is passing? " << isPassing << std ::endl ; std ::cout << "Is fever? " << isFever << std ::endl ; std ::cout << "Is score 85? " << isEqual << std ::endl ; std ::cout << "Temps not equal? " << isNotEqual << std ::endl ; int count = 0 ; while (count < 3 ) { std ::cout << "Count is " << count << std ::endl ; count++; } return 0 ; }
5.1.13 赋值、比较和可能犯的错误 一个非常常见的 C++ 编程错误是将赋值运算符 (=
) 误用在需要比较运算符 (==
) 的地方,尤其是在 if
或 while
的条件语句中。
示例:
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 #include <iostream> int main () { int score = 0 ; std ::cout << "Enter your score: " ; std ::cin >> score; if (score = 100 ) { std ::cout << "Mistake: You entered 100 (or maybe not, score is now 100)." << std ::endl ; } else { std ::cout << "Mistake: This part will likely never execute." << std ::endl ; } std ::cout << "Score after mistaken if: " << score << std ::endl ; std ::cout << "\nEnter your score again: " ; std ::cin >> score; if (score == 100 ) { std ::cout << "Correct: Perfect score!" << std ::endl ; } else { std ::cout << "Correct: Score is not 100." << std ::endl ; } std ::cout << "Score after correct if: " << score << std ::endl ; return 0 ; }
如何避免:
仔细检查条件语句中的 =
和 ==
。
一些编码风格建议将常量放在比较运算符的左边(”Yoda conditions”),例如 if (100 == score)
。这样如果意外写成 if (100 = score)
,编译器会报错,因为不能给常量赋值。
5.1.14 C风格字符串的比较 对于 C 风格字符串(char
数组或 char*
指针),不能 直接使用关系运算符(==
, !=
, <
, >
等)来比较字符串的内容 。
当对两个 char*
指针使用 ==
或 !=
时,比较的是指针存储的内存地址 ,而不是它们指向的字符串内容。
要比较 C 风格字符串的内容,需要使用 C 字符串库 <cstring>
(或 C 的 <string.h>
) 中提供的函数,主要是 strcmp()
。
strcmp(str1, str2)
:
如果 str1
按字典序等于 str2
,返回 0。
如果 str1
小于 str2
,返回负值。
如果 str1
大于 str2
,返回正值。
用法与示例:
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 <iostream> #include <cstring> // 为了 strcmp() int main () { char word1[] = "apple" ; char word2[] = "apply" ; char word3[] = "apple" ; const char *p1 = "banana" ; const char *p2 = "banana" ; const char *p3 = word1; if (word1 == word3) { std ::cout << "Mistake: word1 == word3 (comparing addresses)" << std ::endl ; } else { std ::cout << "Mistake: word1 != word3 (comparing addresses)" << std ::endl ; } if (p1 == p2) { std ::cout << "Info: p1 == p2 (compiler might optimize literals)" << std ::endl ; } else { std ::cout << "Info: p1 != p2 (compiler might not optimize literals)" << std ::endl ; } if (p3 == word1) { std ::cout << "Info: p3 == word1 (same address)" << std ::endl ; } if (strcmp (word1, word3) == 0 ) { std ::cout << "Correct: strcmp(word1, word3) == 0 (contents are equal)" << std ::endl ; } else { std ::cout << "Correct: strcmp(word1, word3) != 0" << std ::endl ; } if (strcmp (word1, word2) < 0 ) { std ::cout << "Correct: strcmp(word1, word2) < 0 (\"apple\" < \"apply\")" << std ::endl ; } return 0 ; }
5.1.15 比较string类字符串 与 C 风格字符串不同,C++ 的 std::string
类重载 了所有的关系运算符(==
, !=
, <
, >
, <=
, >=
)。
这意味着你可以直接使用这些运算符来比较两个 std::string
对象的内容 (按字典序进行比较)。
用法与示例:
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 #include <iostream> #include <string> int main () { std ::string s1 = "apple" ; std ::string s2 = "apply" ; std ::string s3 = "apple" ; std ::string s4 = "Banana" ; if (s1 == s3) { std ::cout << "s1 == s3 is true (contents are equal)" << std ::endl ; } else { std ::cout << "s1 == s3 is false" << std ::endl ; } if (s1 != s2) { std ::cout << "s1 != s2 is true" << std ::endl ; } else { std ::cout << "s1 != s2 is false" << std ::endl ; } if (s1 < s2) { std ::cout << "s1 < s2 is true (\"apple\" < \"apply\")" << std ::endl ; } if (s1 < s4) { std ::cout << "s1 < s4 is true (\"apple\" > \"Banana\" due to case)" << std ::endl ; } else { std ::cout << "s1 < s4 is false (\"apple\" > \"Banana\" due to case)" << std ::endl ; } if (s1 == "apple" ) { std ::cout << "s1 == \"apple\" is true" << std ::endl ; } return 0 ; }
使用 std::string
进行字符串比较比使用 C 风格字符串和 strcmp()
更直观、更安全。
5.2 while循环 while
循环是 C++ 中另一种重要的循环结构。与 for
循环不同,while
循环在结构上更简单,它只包含一个**测试条件 (Test Condition)**。只要该条件为 true
,循环体就会一直执行。
语法:
1 2 3 4 5 6 7 8 9 while (test_condition) { statement1; statement2; } while (test_condition) single_statement;
执行流程:
计算 test_condition
。
如果 test_condition
为 false
,跳出循环,执行循环后面的代码。
如果 test_condition
为 true
,执行循环体中的语句。
回到步骤 1。
关键点:
while
循环是一种**入口条件循环 (Entry-Condition Loop)**,即在每次执行循环体之前检查条件。如果第一次检查条件就为 false
,则循环体一次也不会执行。
循环体内部必须 有能够影响 test_condition
的语句(例如修改用于判断的变量),否则如果条件初始为 true
,循环将永远不会停止,形成**无限循环 (Infinite Loop)**。
用法与示例:
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 #include <iostream> #include <string> int main () { int count = 0 ; std ::cout << "Counting with while (0-4):" << std ::endl ; while (count < 5 ) { std ::cout << "count = " << count << std ::endl ; count++; } std ::cout << "Loop finished." << std ::endl ; char response; std ::cout << "\nEnter 'y' to continue: " ; std ::cin >> response; while (response != 'y' && response != 'Y' ) { std ::cout << "Invalid input. Please enter 'y' to continue: " ; std ::cin >> response; } std ::cout << "Continuing..." << std ::endl ; return 0 ; }
5.2.1 for与while for
循环和 while
循环在很多情况下是可以互换的,因为它们都可以用来实现基于条件的重复执行。
转换关系:
一个典型的 for
循环:
1 2 3 for (initialization; test_condition; update) { body; }
可以等价地转换为 while
循环:
1 2 3 4 5 initialization; while (test_condition) { body; update; }
选择依据:
for
循环:
优点: 将初始化、测试和更新逻辑集中在循环头部,结构清晰,特别适用于计数循环 (循环次数已知或易于计算)或需要按固定步长迭代的情况。
适用场景: 遍历数组、按索引处理字符串、执行固定次数的操作。
while
循环:
优点: 结构更简单,只关注循环条件,适用于循环次数不确定 ,依赖于某个事件或状态改变的情况。
适用场景: 等待用户输入、读取文件直到结束、处理链表、当循环条件比计数器更新更重要时。
示例 (两种循环实现相同功能):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> int main () { int sum_for = 0 ; for (int i = 1 ; i <= 5 ; ++i) { sum_for += i; } std ::cout << "Sum using for: " << sum_for << std ::endl ; int sum_while = 0 ; int i_while = 1 ; while (i_while <= 5 ) { sum_while += i_while; i_while++; } std ::cout << "Sum using while: " << sum_while << std ::endl ; return 0 ; }
虽然两者可以转换,但选择更自然地表达循环意图的结构可以提高代码的可读性。
5.2.2 等待一段时间:编写延时循环 有时我们需要让程序暂停执行一段时间。虽然有更精确、更现代的方法(如 C++11 <chrono>
和 <thread>
库),但可以使用循环来实现简单的、基于处理器时间的**延时循环 (Delay Loop)**。
这种方法不精确且不推荐 用于实际的精确延时,因为它:
依赖于处理器速度: 在快的 CPU 上执行时间短,在慢的 CPU 上执行时间长。
受编译器优化影响: 编译器可能会识别出循环体为空或无副作用,并将其完全优化掉。
浪费 CPU 资源: 循环在空转,消耗 CPU 时间,无法执行其他有用任务。
基本思路: 执行一个已知需要一定时间的空循环或简单操作的循环。
示例 (使用 <ctime>
库):
<ctime>
(或 C 的 <time.h>
) 库提供了一些与时间相关的函数,可以用来实现稍微好一点(但仍不理想)的延时。
clock()
: 返回程序启动以来所用的时钟计时单元 (clock ticks) 数。
CLOCKS_PER_SEC
: 一个常量,表示每秒包含的时钟计时单元数。
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 #include <iostream> #include <ctime> // 为了 clock() 和 CLOCKS_PER_SEC int main () { std ::cout << "Starting delay..." << std ::endl ; float delay_seconds = 2.5f ; clock_t start_time = clock(); clock_t target_ticks = delay_seconds * CLOCKS_PER_SEC; while (clock() < start_time + target_ticks) { } std ::cout << "Delay finished after approximately " << delay_seconds << " seconds." << std ::endl ; return 0 ; }
再次强调: 对于需要精确延时或暂停执行而不浪费 CPU 的场景,应使用 C++11 及更高版本提供的 <chrono>
和 <thread>
中的 std::this_thread::sleep_for()
或 std::this_thread::sleep_until()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <chrono> // 为了时间单位 (e.g., seconds, milliseconds) #include <thread> // 为了 std::this_thread::sleep_for int main () { std ::cout << "Starting modern delay..." << std ::endl ; std ::chrono::seconds sec (2 ) ; std ::chrono::milliseconds ms (500 ) ; std ::this_thread::sleep_for(sec + ms); std ::cout << "Modern delay finished." << std ::endl ; return 0 ; }
这个现代方法更精确、可移植性更好,并且不会浪费 CPU 周期。
5.3 do while循环 do while
循环是 C++ 提供的第三种循环结构。它与 while
循环非常相似,但有一个关键区别:do while
循环是**出口条件循环 (Exit-Condition Loop)**,而 while
循环是入口条件循环。
这意味着 do while
循环会先执行一次循环体 ,然后再 检查测试条件。只要条件为 true
,循环就会继续执行。
语法:
1 2 3 4 5 6 do { statement1; statement2; } while (test_condition);
或者如果循环体只有一条语句(虽然不常见,且为了清晰通常还是用花括号):
1 2 3 do single_statement; while (test_condition);
执行流程:
执行循环体中的语句。
计算 test_condition
。
如果 test_condition
为 true
,回到步骤 1。
如果 test_condition
为 false
,循环终止,执行循环后面的代码。
关键点:
至少执行一次: 由于条件是在循环体执行之后检查的,do while
循环的循环体至少会执行一次 ,即使条件初始就为 false
。
分号: while (test_condition)
后面必须有一个分号 ;
。
适用场景: 当你需要确保循环体中的代码至少执行一次时,do while
循环是理想的选择。例如,获取用户输入并验证,至少需要获取一次输入才能进行验证。
用法与示例:
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 #include <iostream> int main () { int number; do { std ::cout << "Enter a positive number: " ; std ::cin >> number; if (number <= 0 ) { std ::cout << "Invalid input. Please try again." << std ::endl ; } } while (number <= 0 ); std ::cout << "You entered the positive number: " << number << std ::endl ; int count = 5 ; std ::cout << "\nStarting do-while with count = 5 (condition count < 5 is false):" << std ::endl ; do { std ::cout << " Inside do-while loop, count = " << count << std ::endl ; count++; } while (count < 5 ); std ::cout << "After do-while loop, count = " << count << std ::endl ; return 0 ; }
与 while
和 for
的比较:
while
: 入口条件,可能一次都不执行。
for
: 通常用于计数或已知迭代次数,结构包含初始化、条件、更新。
do while
: 出口条件,保证至少执行一次。
根据循环逻辑选择最合适的循环结构可以使代码更清晰、更易于理解。如果需要确保操作至少发生一次(如菜单选择、输入验证),do while
是一个很好的选择。
5.4 基于范围的for循环(C++11) C++11 引入了一种更简洁、更易读的 for
循环语法,称为基于范围的 for 循环 (Range-Based for Loop) 或 增强 for 循环 (Enhanced for Loop)**。它专门用于 遍历一个序列(或范围)中的所有元素**,例如数组、STL 容器(如 vector
, array
, string
)、初始化列表等。
目的: 简化遍历操作,减少手动管理索引或迭代器的代码,避免常见的差一错误 (off-by-one errors)。
语法:
1 2 3 4 5 for (declaration : range_expression) { statement; }
declaration
: 声明一个变量,其类型应与 range_expression
中元素的类型兼容(或可以转换)。在每次循环迭代中,该变量会被初始化为范围中的当前元素。
通常使用 auto
让编译器自动推断类型。
auto variable
: variable
会成为当前元素的副本 。修改 variable
不会影响原始序列中的元素。
auto& variable
: variable
会成为当前元素的引用 。修改 variable
会 修改原始序列中的元素。用于需要修改元素或避免复制大型对象开销的情况。
const auto& variable
: variable
会成为当前元素的常量引用 。不能通过 variable
修改元素,但可以避免复制开销。用于只读访问。
:
: 用于分隔声明和范围表达式。
range_expression
: 一个可以表示序列的表达式。这通常是:
数组名。
STL 容器对象(如 std::vector
, std::array
, std::string
, std::list
等)。
初始化列表 { ... }
。
任何定义了 begin()
和 end()
成员函数或可以通过全局 begin()
和 end()
函数获取迭代器的对象。
执行流程:
循环会自动遍历 range_expression
中的每一个元素。在每次迭代中:
从序列中获取下一个元素。
将该元素的值(或引用)赋给 declaration
中声明的变量。
执行循环体。
重复此过程,直到遍历完序列中的所有元素。
用法与示例:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <iostream> #include <vector> #include <string> #include <array> // 为了 std::array int main () { double prices[] = {19.99 , 25.50 , 9.75 , 100.0 }; std ::cout << "Prices (array):" ; for (double price : prices) { std ::cout << " " << price; } std ::cout << std ::endl ; std ::vector <int > numbers = {1 , 2 , 3 , 4 , 5 }; std ::cout << "Numbers (vector):" ; for (int num : numbers) { std ::cout << " " << num; } std ::cout << std ::endl ; std ::string message = "Hello" ; std ::cout << "Characters (string):" ; for (char c : message) { std ::cout << " " << c; } std ::cout << std ::endl ; std ::cout << "Initializer list:" ; for (int x : {10 , 20 , 30 , 40 }) { std ::cout << " " << x; } std ::cout << std ::endl ; std ::array <std ::string , 3> fruits {"Apple" , "Banana" , "Cherry" }; std ::cout << "Fruits (array with auto):" ; for (auto fruit : fruits) { std ::cout << " " << fruit; } std ::cout << std ::endl ; std ::vector <int > scores = {70 , 85 , 90 }; std ::cout << "Original scores:" ; for (int score : scores) std ::cout << " " << score; std ::cout << std ::endl ; for (auto & score_ref : scores) { score_ref += 5 ; } std ::cout << "Scores after adding 5:" ; for (int score : scores) std ::cout << " " << score; std ::cout << std ::endl ; std ::cout << "Reading scores (const auto&):" ; for (const auto & score_cref : scores) { std ::cout << " " << score_cref; } std ::cout << std ::endl ; return 0 ; }
优点:
简洁: 代码更短,意图更清晰(“对范围中的每个元素做某事”)。
安全: 避免了手动管理索引或迭代器可能导致的错误(如越界访问、迭代器失效等)。
通用: 适用于所有定义了 begin()
和 end()
的标准容器以及内置数组和初始化列表。
局限性:
无法直接获取索引: 如果在循环中需要知道当前元素的索引,基于范围的 for 循环本身不提供这个信息。需要额外维护一个计数器变量。 1 2 3 4 5 6 std ::vector <int > data = {100 , 200 , 300 };int index = 0 ;for (int val : data) { std ::cout << "Index " << index << ": " << val << std ::endl ; index++; }
遍历整个范围: 它总是从头到尾遍历整个范围。如果需要更复杂的遍历模式(如反向、跳跃、只遍历部分范围),传统的 for
循环或 while
循环配合迭代器可能更合适。
修改容器大小: 在循环体内修改容器的大小(例如,在 vector
中 push_back
或 erase
)通常是不安全的,可能导致迭代器失效和未定义行为。基于范围的 for 循环不适合这种情况。
总结:
基于范围的 for
循环是 C++11 提供的一个非常有用的特性,极大地简化了对序列中所有元素的遍历操作。在不需要索引且需要遍历整个序列的情况下,它通常是比传统 for
循环更优选、更安全、更易读的选择。
5.5 循环和文本输入 循环结构在处理文本输入时非常有用,特别是当我们需要逐个字符或逐行读取数据,直到满足某个条件(如遇到特定字符、文件结束或达到一定数量)时。本节将探讨使用 cin
及其相关方法进行文本输入的常见模式和技巧。
5.5.1 使用原始的cin进行输入 我们已经知道,使用 cin >> variable
可以从标准输入读取数据。当用于读取文本(如 char
或 string
)时,cin >>
的行为特点是:
跳过空白: 它会自动忽略输入流中开头的任何空白字符(空格、制表符、换行符)。
读取直到空白: 它会读取非空白字符,直到遇到下一个空白字符为止。
空白符留在流中: 停止读取时遇到的那个空白字符会留在 输入流(输入缓冲区)中,等待下一次读取操作。
这使得 cin >>
适合读取单个单词或以空白分隔的数据项,但不适合读取包含空格的整行文本或精确地逐个字符处理(包括空格)。
用法与示例:
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 #include <iostream> #include <string> int main () { char ch; std ::string word; std ::cout << "Enter some characters (e.g., 'a b c'): " ; std ::cin >> ch; std ::cout << "First char read: '" << ch << "'" << std ::endl ; std ::cin >> word; std ::cout << "Next word read: \"" << word << "\"" << std ::endl ; std ::cin >> ch; std ::cout << "Next char read: '" << ch << "'" << std ::endl ; std ::cout << "\nEnter words (Ctrl+Z/D to stop):" << std ::endl ; while (std ::cin >> word) { std ::cout << "Read word: " << word << std ::endl ; } return 0 ; }
while (std::cin >> word)
是一种常见的读取模式,它利用了 cin
对象在成功读取时可以被转换为 true
的特性。当读取失败(例如到达文件末尾或遇到无效输入)时,cin
对象会转换为 false
,循环终止。
5.5.2 使用cin.get(char)进行补救 cin >>
跳过空白并停止于空白的行为有时不是我们想要的,特别是当我们需要读取包括空格在内的每一个字符时。cin.get(char& ch)
成员函数提供了解决方案。
cin.get(char& ch)
:
尝试从输入流中读取下一个 字符(无论它是什么,包括空格、制表符、换行符)。
如果成功读取,将该字符存储在参数 ch
中,并返回 cin
对象本身(可以转换为 true
)。
如果到达文件末尾或发生错误,不修改 ch
,并将 cin
置于失败状态(转换为 false
)。
这使得 cin.get(char)
非常适合在循环中逐个读取所有字符。
用法与示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> int main () { char ch; int count = 0 ; std ::cout << "Enter text (Ctrl+Z/D to stop):" << std ::endl ; while (std ::cin .get(ch)) { std ::cout << ch; count++; } std ::cout << "\n--- End of input ---" << std ::endl ; std ::cout << "Total characters read: " << count << std ::endl ; return 0 ; }
这个循环会读取并回显用户输入的所有字符,包括空格和换行,直到遇到文件结束符。
5.5.3 使用哪个cin.get() istream
类(cin
是其对象)实际上提供了几个名为 get
的成员函数(函数重载):
cin.get(char& ch)
: (已在 5.5.2 讨论)
读取下一个字符到参数 ch
中。
返回 cin
对象。
适合在 while
条件中直接使用 while(cin.get(ch))
。
cin.get()
: (无参数版本)
读取下一个字符。
返回该字符的整数 ASCII 码(或 wchar_t
对应的值)。
如果到达文件末尾或发生错误,返回特殊值 EOF
(End Of File,通常定义为 -1,在 <iostream>
或 <cstdio>
中定义)。
不直接将字符存入变量,需要接收其返回值。
选择依据:
cin.get(char& ch)
: 当你需要将读取的字符直接存储到一个 char
变量中,并且想利用 cin
对象在 while
条件中的布尔转换特性时,这是最常用的选择。
cin.get()
(无参数): 当你需要显式地检查文件结束符 EOF
时,或者当你需要获取字符的整数值时,这个版本更合适。返回值需要与 EOF
进行比较。
用法与示例 (cin.get() 无参数版本):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <cstdio> // 为了 EOF (虽然 iostream 通常也包含) int main () { int ch_int; int count = 0 ; std ::cout << "Enter text (Ctrl+Z/D to stop):" << std ::endl ; while ((ch_int = std ::cin .get()) != EOF) { std ::cout << static_cast <char >(ch_int); count++; } std ::cout << "\n--- End of input ---" << std ::endl ; std ::cout << "Total characters read: " << count << std ::endl ; return 0 ; }
两种 get
方法都可以用于逐字符读取,选择哪种取决于你喜欢的判断循环结束的方式(检查 cin
状态还是检查 EOF
返回值)。
5.5.4 文件尾条件 当从输入流(如 cin
或文件流)读取数据时,最终会到达输入的末尾,这被称为**文件尾 (End-of-File, EOF)**。程序需要能够检测到 EOF 条件以正常终止读取循环。
有几种方法可以检测 EOF:
检查 cin
状态:
cin
对象本身可以转换为布尔值。当读取操作成功时,它转换为 true
;当遇到 EOF 或其他错误导致读取失败时,它转换为 false
。这是 while (cin >> word)
和 while (cin.get(ch))
能够工作的原因。
cin.eof()
: 如果流是因为到达文件末尾而失败,此函数返回 true
。注意: eof()
只有在尝试读取并失败 后才会变为 true
。不能用它来预测下一次读取是否会到达 EOF。
cin.fail()
: 如果发生了非 EOF 的 I/O 错误(例如读取了无效数据类型)或到达 EOF,此函数返回 true
。
cin.good()
: 如果流处于正常状态(没有设置 eofbit
, failbit
, badbit
),返回 true
。
检查 cin.get()
的返回值:
无参数的 cin.get()
在到达 EOF 时返回特殊值 EOF
。这是 while ((ch = cin.get()) != EOF)
能够工作的原因。
EOF 的触发:
键盘输入: 通常通过按下特定的组合键来模拟 EOF:
Unix/Linux/macOS: Ctrl+D
(通常需要在行首按)
Windows: Ctrl+Z
(通常需要在一行结束后按 Enter,然后再按 Ctrl+Z
再按 Enter)
文件输入: 当读取操作尝试越过文件的最后一个字节时,会触发 EOF。
示例 (使用 cin
状态):
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 #include <iostream> int main () { int number; int sum = 0 ; int count = 0 ; std ::cout << "Enter numbers (non-number or Ctrl+Z/D to stop):" << std ::endl ; while (std ::cin >> number) { sum += number; count++; } std ::cout << "\n--- Input finished ---" << std ::endl ; if (std ::cin .eof()) { std ::cout << "Reason: End-of-File reached." << std ::endl ; } else if (std ::cin .fail()) { std ::cout << "Reason: Invalid input (non-number)." << std ::endl ; } else if (std ::cin .bad()) { std ::cout << "Reason: Unrecoverable stream error." << std ::endl ; } if (count > 0 ) { std ::cout << "Read " << count << " numbers. Sum = " << sum << std ::endl ; } else { std ::cout << "No valid numbers were entered." << std ::endl ; } return 0 ; }
理解如何检测 EOF 对于编写能正确处理输入结束的循环至关重要。最常用的方法是利用 cin
对象或 cin.get()
在 while
条件中的行为。
5.5.5 另一个cin.get()版本 除了读取单个字符的 get()
函数外,istream
还提供了用于读取 C 风格字符串(字符数组)的 get()
版本:
cin.get(char* buffer, int size, char delimiter = '\n')
:
从输入流中读取字符,并将它们存储到 buffer
指向的字符数组中。
最多读取 size - 1
个字符(为末尾的空字符 \0
留出空间)。
如果在读取 size - 1
个字符之前遇到 delimiter
字符,则停止读取。
delimiter
字符本身不会被读取到 buffer
中,而是会留在输入流中。 (这是与 getline
的主要区别之一)。
读取结束后,总会在 buffer
的末尾添加一个空字符 \0
。
返回 cin
对象。
用法与示例:
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 #include <iostream> const int BUFFER_SIZE = 20 ;int main () { char name[BUFFER_SIZE]; char address[BUFFER_SIZE]; std ::cout << "Enter your name (max " << BUFFER_SIZE - 1 << " chars): " ; std ::cin .get(name, BUFFER_SIZE); if (std ::cin ) { std ::cout << "Name entered: " << name << std ::endl ; if (std ::cin .peek() == '\n' ) { std ::cin .ignore(); } std ::cout << "Enter your address (max " << BUFFER_SIZE - 1 << " chars): " ; std ::cin .get(address, BUFFER_SIZE); if (std ::cin ) { std ::cout << "Address entered: " << address << std ::endl ; } else { std ::cout << "Error reading address or EOF reached." << std ::endl ; } } else { std ::cout << "Error reading name or EOF reached." << std ::endl ; } return 0 ; }
与 getline(cin, string)
的比较:
cin.get(buffer, size)
:
用于 C 风格字符数组。
需要指定缓冲区大小以防止溢出。
不读取 分隔符,分隔符留在流中。
需要手动处理留在流中的分隔符。
getline(cin, str)
:
用于 std::string
对象。
自动管理内存,无需担心缓冲区溢出。
读取并丢弃 分隔符(默认为 \n
)。
通常更方便、更安全。
由于 cin.get(buffer, size)
不读取分隔符并将其留在流中,这常常导致后续输入出现问题。因此,在现代 C++ 中,当需要读取整行文本时,**强烈推荐使用 getline(cin, std::string)
**。cin.get(buffer, size)
主要用于需要与 C 风格字符串 API 交互或有特定限制的场景。
5.6 嵌套循环和二维数组 嵌套循环 (Nested Loop) 是指一个循环结构完全包含在另一个循环结构的循环体内部。外层循环每执行一次,内层循环会完整地执行一遍(从开始到结束)。嵌套循环常用于处理具有多维结构的数据,例如表格、矩阵或图像的像素。
二维数组 (Two-Dimensional Array) 是数组的一种扩展,可以看作是“数组的数组”。它在概念上像一个表格或网格,有行 (row) 和列 (column)。二维数组是使用嵌套循环处理的典型数据结构。
嵌套循环示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> int main () { const int ROWS = 3 ; const int COLS = 4 ; std ::cout << "Nested loop example (printing coordinates):" << std ::endl ; for (int i = 0 ; i < ROWS; ++i) { for (int j = 0 ; j < COLS; ++j) { std ::cout << "(" << i << "," << j << ") " ; } std ::cout << std ::endl ; } return 0 ; }
输出:
1 2 3 4 Nested loop example (printing coordinates): (0,0) (0,1) (0,2) (0,3) (1,0) (1,1) (1,2) (1,3) (2,0) (2,1) (2,2) (2,3)
5.6.1 初始化二维数组 声明二维数组需要指定两个维度的大小:第一个是行数 ,第二个是列数 。
声明语法:
1 typeName arrayName[numberOfRows][numberOfColumns];
初始化方法:
可以使用嵌套的花括号 {}
来初始化二维数组。外层花括号代表整个数组,内层花括号代表每一行。
完整初始化: 提供所有行的初始化列表。 1 2 3 4 5 int matrix[3 ][4 ] = { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } };
部分初始化: 如果提供的初始化值不足,剩余元素会被自动初始化为 0(对于数值类型)。 1 2 3 4 5 6 7 8 int partial[3 ][4 ] = { {1 , 2 }, {5 } }; int allZeros[10 ][20 ] = {0 };
省略行数 (但不能省略列数): 如果在声明时提供了初始化列表,可以省略第一个维度(行数),编译器会根据初始化列表推断行数。但第二个维度(列数)必须指定。 1 2 3 4 int inferredRows[][4 ] = { {1 , 1 , 1 , 1 }, {2 , 2 , 2 , 2 } };
C++11 列表初始化: 可以省略等号 =
。 1 2 int matrix_cpp11[2 ][3 ] { {1 , 2 , 3 }, {4 , 5 , 6 } };int zeros_cpp11[5 ][5 ] {};
用法与示例:
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 #include <iostream> int main () { int data[2 ][3 ] = { {10 , 20 , 30 }, {40 , 50 , 60 } }; float coords[3 ][2 ] = { {1.1f , 2.2f }, {3.3f } }; char messages[][10 ] = { "Hello" , "World" }; int table[2 ][2 ] { {1 }, {3 , 4 } }; std ::cout << "data[1][1]: " << data[1 ][1 ] << std ::endl ; std ::cout << "coords[1][1]: " << coords[1 ][1 ] << std ::endl ; std ::cout << "messages[0]: " << messages[0 ] << std ::endl ; std ::cout << "table[0][1]: " << table[0 ][1 ] << std ::endl ; return 0 ; }
5.6.2 使用二维数组 访问二维数组的元素需要提供两个索引:第一个是行索引 ,第二个是列索引 。索引同样从 0 开始。
访问语法:
1 arrayName[rowIndex][columnIndex]
使用嵌套循环处理二维数组:
嵌套循环是处理二维数组所有元素的标准方法。通常,外层循环遍历行,内层循环遍历列。
用法与示例:
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> const int NUM_CITIES = 3 ;const int NUM_MONTHS = 4 ; int main () { double temperatures[NUM_CITIES][NUM_MONTHS] = { {10.5 , 12.1 , 15.3 , 18.0 }, {8.2 , 9.5 , 13.0 , 16.5 }, {12.0 , 14.5 , 17.8 , 21.2 } }; std ::cout << "Monthly average temperatures:" << std ::endl ; for (int city = 0 ; city < NUM_CITIES; ++city) { std ::cout << "City " << city << ": " ; for (int month = 0 ; month < NUM_MONTHS; ++month) { std ::cout << temperatures[city][month] << "\t" ; } std ::cout << std ::endl ; } double city1_total = 0.0 ; int city_index = 1 ; for (int month = 0 ; month < NUM_MONTHS; ++month) { city1_total += temperatures[city_index][month]; } double city1_average = city1_total / NUM_MONTHS; std ::cout << "\nAverage temperature for City " << city_index << ": " << city1_average << std ::endl ; double month0_total = 0.0 ; int month_index = 0 ; for (int city = 0 ; city < NUM_CITIES; ++city) { month0_total += temperatures[city][month_index]; } double month0_average = month0_total / NUM_CITIES; std ::cout << "Average temperature for Month " << month_index << " across all cities: " << month0_average << std ::endl ; return 0 ; }
内存布局: 在内存中,二维数组通常是按行主序 (Row-Major Order) 存储的。这意味着第一行的所有元素连续存储,然后是第二行的所有元素,依此类推。例如,matrix[3][4]
的内存布局看起来像:matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], matrix[1][0], matrix[1][1], ... , matrix[2][3]
理解这一点对于将二维数组传递给函数(通常需要知道列数)或进行某些指针操作很重要。
5.7 总结 本章重点介绍了C++中的循环结构 和关系表达式 ,它们是控制程序流程和处理重复任务的基础。
我们学习了三种主要的循环语句:
for
循环: 这是一种入口条件循环,其头部包含了初始化、测试条件和更新三个部分,结构清晰,特别适用于计数或已知迭代次数的情况。我们探讨了其组成部分、如何修改步长、使用它访问字符串(C风格和std::string
),并详细学习了递增 (++
) 和递减 (--
) 运算符(包括前缀和后缀形式及其区别、副作用和顺序点问题、在指针上的应用)。此外,还介绍了组合赋值运算符(如 +=
, -=
)和逗号运算符在 for
循环中的应用。
while
循环: 这也是一种入口条件循环,但结构更简单,只包含一个测试条件。它适用于循环次数不确定、依赖于某个条件持续满足的情况。我们比较了 for
和 while
的适用场景,并了解了如何使用循环(虽然不推荐)以及现代 C++ 的 <chrono>
和 <thread>
库来实现延时。
do while
循环: 这是一种出口条件循环,其特点是循环体至少执行一次,然后在每次迭代结束时检查条件。它适用于需要确保操作至少发生一次的场景,如用户输入验证。
C++11 引入的基于范围的 for
循环 提供了一种更简洁、更安全的遍历序列(如数组、vector
、string
、初始化列表)中所有元素的方式。我们学习了其语法、如何使用 auto
、引用 (&
) 和常量引用 (const &
) 来声明循环变量,以及它的优点和局限性(如无法直接获取索引)。
关系表达式 使用关系运算符(<
, >
, <=
, >=
, ==
, !=
)来比较值,结果为布尔值 true
或 false
,常用于循环和分支语句的条件判断。我们特别强调了将赋值运算符 (=
) 误用为比较运算符 (==
) 的常见错误及其后果。对于字符串比较,我们了解到 C 风格字符串需要使用 <cstring>
中的 strcmp()
函数来比较内容,而 std::string
类则可以直接使用重载的关系运算符进行内容的字典序比较。
本章还深入探讨了循环与文本输入 的结合。我们分析了 cin >>
读取单词(跳过并停止于空白)的行为,以及如何使用 cin.get(char)
和无参数的 cin.get()
来逐个读取字符(包括空白符)。我们学习了如何检测文件尾 (EOF) 条件以正确终止输入循环,包括检查 cin
流状态和 cin.get()
的返回值。最后,我们了解了读取 C 风格字符串的 cin.get(buffer, size)
版本及其与 getline
的区别(分隔符处理)。
最后,我们学习了嵌套循环 的概念,即一个循环包含在另一个循环内部,以及如何使用嵌套循环来处理二维数组 (数组的数组)。我们了解了二维数组的初始化方法和如何使用双重索引 [row][col]
配合嵌套循环来访问和处理其所有元素。
通过本章的学习,我们掌握了 C++ 中控制重复执行和进行比较的核心工具,为编写更复杂、更强大的程序奠定了基础。