{ }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과 크레이콘에는 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), 입력 끝까지는 줄읽기()가 빈값을 줄 때까지(동안 (줄 != 빈값)).
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)), 크레이콘도 똑같이 그대로예요. 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이에요.
끝 표시까지 읽을 때는 "읽기 → 확인 → 처리 → 다시 읽기" 순서를 지켜요.
확인보다 처리가 먼저 오면 끝 표시까지 더해 버려요.

연습 문제