流程控制 · 07
更多循环
逐个取值、倒着循环、读到结束标记、至少执行一次的 do-while。
在“循环”一课学过的 for 和 while 之上,再加四种写法。
下面的例子一直读正整数直到出现 0 并把它们收集起来,逐个取出求和,再从后往前倒着输出,最后数一数总和是几位数。
nums = []x = int(input())while x != 0: # 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.append(x) x = int(input()) total = 0for v in nums: # 번호 없이 값을 하나씩 total += vprint(total) for i in range(len(nums) - 1, -1, -1): # 마지막 번호부터 0번까지 print(nums[i]) digits = 0while True: # do-while이 없어서: 먼저 한 번 하고 끝에서 확인해요 digits += 1 total //= 10 if total == 0: breakprint(digits)const lines = require("fs").readFileSync(0, "utf8").split("\n");let at = 0;const nums = [];let x = Number(lines[at++]);while (x !== 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.push(x); x = Number(lines[at++]);} let total = 0;for (const v of nums) { // 번호 없이 값을 하나씩 total += v;}console.log(total); for (let i = nums.length - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 console.log(nums[i]);} let digits = 0;do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total = Math.trunc(total / 10);} while (total !== 0);console.log(digits);import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");let at = 0;const nums: number[] = [];let x: number = Number(lines[at++]);while (x !== 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.push(x); x = Number(lines[at++]);} let total: number = 0;for (const v of nums) { // 번호 없이 값을 하나씩 total += v;}console.log(total); for (let i = nums.length - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 console.log(nums[i]);} let digits: number = 0;do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total = Math.trunc(total / 10);} while (total !== 0);console.log(digits);import java.util.ArrayList;import java.util.List;import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); List<Integer> nums = new ArrayList<>(); int x = sc.nextInt(); while (x != 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.add(x); x = sc.nextInt(); } int total = 0; for (int v : nums) { // 번호 없이 값을 하나씩 total += v; } System.out.println(total); for (int i = nums.size() - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 System.out.println(nums.get(i)); } int digits = 0; do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total /= 10; } while (total != 0); System.out.println(digits); }}using System;using System.Collections.Generic; class Program{ static void Main() { List<int> nums = new List<int>(); int x = int.Parse(Console.ReadLine()); while (x != 0) // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 { nums.Add(x); x = int.Parse(Console.ReadLine()); } int total = 0; foreach (int v in nums) // 번호 없이 값을 하나씩 { total += v; } Console.WriteLine(total); for (int i = nums.Count - 1; i >= 0; i--) // 마지막 번호부터 0번까지 { Console.WriteLine(nums[i]); } int digits = 0; do // 먼저 한 번 하고 끝에서 확인해요 { digits++; total /= 10; } while (total != 0); Console.WriteLine(digits); }}#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';}#include <stdio.h> int main(void) { int nums[1000], n = 0, x; scanf("%d", &x); while (x != 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums[n++] = x; scanf("%d", &x); } int total = 0; for (int i = 0; i < n; i++) { // 값을 하나씩 꺼내는 꼴이 없어서 번호로 돌아요 total += nums[i]; } printf("%d\n", total); for (int i = n - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 printf("%d\n", nums[i]); } int digits = 0; do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total /= 10; } while (total != 0); printf("%d\n", digits); return 0;}nums = [];x = 控制台.读行().转数字();// 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요当 (x != 0){ nums.添加(x); x = 控制台.读行().转数字();} total = 0;// 번호 없이 값을 하나씩遍历 (v 在 nums){ total += v;}控制台.输出(total); // 마지막 번호부터 0번까지循环 (i = nums.数量 - 1; i >= 0; i--){ 控制台.输出(nums[i]);} digits = 0;// do-while이 없어서: 먼저 한 번 하고 끝에서 확인해요当 (真){ digits++; total = 数学.商(total, 10); 如果 (total == 0) { 跳出; }}控制台.输出(digits);12 7 30 0 99
49 30 7 12 2
要点
- 逐个循环(for-each 循环)不用下标,直接取出值。
没有下标可写错,所以从头到尾全部看一遍时最安全。
需要知道是第几个,或者要倒着走时,就按下标循环。 - 倒序循环把起点、条件和变化量全部反过来。
从最后一个下标个数 - 1开始,只要i >= 0就继续,用i--递减。 - 读到结束标记的循环(哨兵循环)用在不事先告诉个数、而是用约定的值(比如“出现 0 就结束”)表示结束的输入上。
先读一个,只要不是标记就处理,在每一轮的末尾读下一个值。
标记本身不处理。 - 连标记也没有时,就读到输入结束,也就是“读取输入”一课里见过的写法。
它和读到结束标记的循环形式相同,只是条件变成“读成功了吗”。 - do-while 先执行一次循环体,在末尾才看条件,所以至少执行一次。
数位数就是这样的工作:0 也是一位数,但写成while (total != 0)时一次也不执行,会回答 0 位。 - Python 和 Craykon 没有 do-while。
在无限循环里先做事,到末尾再看条件并跳出,效果完全一样。
各语言的写法
| Python | for 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 |
|---|---|
| JavaScript | for (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 一样 |
| Java | for (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,所以写 当 (真) { … 如果 (条件) { 跳出; } } |
常见错误
以为改了逐个取出的变量,列表也会跟着变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);。
用无符号下标倒着循环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; )。
选择方法很简单。
从头到尾全部看一遍用逐个循环,需要下标或要倒着走就按下标,不知道要循环几次用
while,至少要做一次用 do-while。读到结束标记时,要守住“读 → 检查 → 处理 → 再读”的顺序。
如果处理写在检查之前,就会把标记也加进去。