{ }codelper

学ぶ › C++

制御フロー · 07

繰り返しをもっと

一つずつ取り出す、逆順に回る、終わりの印まで読む、必ず1回は回る 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 は本体を先に1回実行し、条件を最後に見ます。
    だから必ず1回は回ります。
    桁数を数えるのがそういう仕事です。
    0 も1桁の数ですが、while (total != 0) と書くと1回も回らず、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) で最後まで回します
TypeScriptJavaScript と同じです
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、必ず1回はやるなら do-while です。
終わりの印まで読むときは「読む → 確かめる → 処理する → また読む」の順を守ります。
確かめるより先に処理すると、印まで足してしまいます。

練習問題