{ }codelper

学习 › C++

流程控制 · 07

更多循环

逐个取值、倒着循环、读到结束标记、至少执行一次的 do-while。

在“循环”一课学过的 for 和 while 之上,再加四种写法。
下面的例子一直读正整数直到出现 0 并把它们收集起来,逐个取出求和,再从后往前倒着输出,最后数一数总和是几位数。

#include <iostream>#include <vector> int main() {    std::vector<int> nums;    int x;    std::cin >> x;    while (x != 0) {            // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요        nums.push_back(x);        std::cin >> x;    }     int total = 0;    for (int v : nums) {        // 번호 없이 값을 하나씩        total += v;    }    std::cout << total << '\n';     for (int i = (int)nums.size() - 1; i >= 0; i--) {   // 마지막 번호부터 0번까지        std::cout << nums[i] << '\n';    }     int digits = 0;    do {                        // 먼저 한 번 하고 끝에서 확인해요        digits++;        total /= 10;    } while (total != 0);    std::cout << digits << '\n';}
stdin
12
7
30
0
99
stdout
49
30
7
12
2

要点

  • 逐个循环(for-each 循环)不用下标,直接取出值。
    没有下标可写错,所以从头到尾全部看一遍时最安全。
    需要知道是第几个,或者要倒着走时,就按下标循环。
  • 倒序循环把起点、条件和变化量全部反过来。
    从最后一个下标 个数 - 1 开始,只要 i >= 0 就继续,用 i-- 递减。
  • 读到结束标记的循环(哨兵循环)用在不事先告诉个数、而是用约定的值(比如“出现 0 就结束”)表示结束的输入上。
    先读一个,只要不是标记就处理,在每一轮的末尾读下一个值。
    标记本身不处理。
  • 连标记也没有时,就读到输入结束,也就是“读取输入”一课里见过的写法。
    它和读到结束标记的循环形式相同,只是条件变成“读成功了吗”。
  • do-while 先执行一次循环体,在末尾才看条件,所以至少执行一次。
    数位数就是这样的工作:0 也是一位数,但写成 while (total != 0) 时一次也不执行,会回答 0 位。
  • Python 和 Craykon 没有 do-while。
    在无限循环里先做事,到末尾再看条件并跳出,效果完全一样。

各语言的写法

Pythonfor v in nums:,倒序用 for v in reversed(nums): 或按下标 range(len(nums) - 1, -1, -1)。
结束标记用 while x != 0:,读到输入结束用 for line in sys.stdin:。
没有 do-while,所以在 while True: 里写 if 条件: break
JavaScriptfor (const v of nums),倒序用 for (let i = nums.length - 1; i >= 0; i--)。
nums.reverse() 会把原列表本身反转。
do { } while (条件); 末尾要加 ;。
输入一次读完,分成行的列表,再按下标取。
读到输入结束:用 .trim().split("\n") 分成行的列表,再用 for (const line of lines) 走到底
TypeScript和 JavaScript 一样
Javafor (int v : nums),倒序用 for (int i = nums.size() - 1; i >= 0; i--)(数组用 nums.length)。
do { } while (条件);。
读到输入结束用 while (sc.hasNextInt())
C#foreach (int v in nums),倒序用 for (int i = nums.Count - 1; i >= 0; i--)。
do { } while (条件);。
读到输入结束用 while ((line = Console.ReadLine()) != null)
C++for (int v : nums),倒序用 for (int i = (int)nums.size() - 1; i >= 0; i--) — size() 是无符号数,要转成 int 再用。
do { } while (条件);。
读到输入结束用 while (std::cin >> x)
C没有逐个取值的写法,所以用 for (int i = 0; i < n; i++),倒序用 for (int i = n - 1; i >= 0; i--)。
do { } while (条件);。
读到输入结束用 while (scanf("%d", &x) == 1)
Craykon遍历 (v 在 nums),倒序用 循环 (i = nums.数量 - 1; i >= 0; i--)。
结束标记用 当 (x != 0),读到输入结束就是读到 读行() 返回 空 为止(当 (line != 空))。
没有 do-while,所以写 当 (真) { … 如果 (条件) { 跳出; } }

常见错误

用无符号下标倒着循环C · C++

std::vector<int> nums = {12, 7, 30};
for (size_t i = nums.size() - 1; i >= 0; i--) {
    std::cout << nums[i] << '\n';
}

→ 输出 30、7、12 之后也不会停。无符号的 i 在 0 时减 1 不是 -1,而是 18446744073709551615,所以 i >= 0 永远为真。接下来读的是列表外面,这是未定义行为,会发生什么并没有规定。在评测机上运行时,它输出了一连串乱七八糟的值,然后以 Segmentation fault 崩溃

用有符号整数循环:int i = (int)nums.size() - 1。
要保持无符号,就写成 for (size_t i = nums.size(); i-- > 0; )。

以为改了逐个取出的变量,列表也会跟着变Python · JavaScript · TypeScript · Java · C# · C++ · Craykon

nums = [1, 2, 3]
for v in nums:
    v = v * 2
print(nums)

→ [1, 2, 3] — 没有变。v 只是装着取出来的值的变量,改 v 不会改列表里的格子。JavaScript(for (let v of nums))、Java、C++(for (int v : nums))、Craykon 也一样不变。C# 则在给 foreach 变量赋值时就报编译错误(CS1656)

要改格子,就按下标循环,写 nums[i] = nums[i] * 2。
C++ 用 for (int& v : nums) 取出就能改到原列表。
边循环边从列表里删除值的错误在“修改列表”一课里讲。

在条件里用了 do-while 循环体中创建的变量Java · C# · C · C++ · JavaScript · TypeScript

int total = 0;
do {
    int x = sc.nextInt();
    total += x;
} while (x != 0);

→ 编译错误(Java 是 cannot find symbol)。在 { } 里创建的 x 到右花括号就消失了,后面的条件看不到它。TypeScript 也是编译错误(Cannot find name 'x'),JavaScript 则在运行时以 ReferenceError: x is not defined 停止

条件里要用的变量在 do 之前创建:先写 int x;,再写 do { x = sc.nextInt(); … } while (x != 0);。

各语言的差异
选择方法很简单。
从头到尾全部看一遍用逐个循环,需要下标或要倒着走就按下标,不知道要循环几次用 while,至少要做一次用 do-while。
读到结束标记时,要守住“读 → 检查 → 处理 → 再读”的顺序。
如果处理写在检查之前,就会把标记也加进去。

练习题