흐름 제어 · 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과 크레이콘에는 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), 입력 끝까지는 줄읽기()가 빈값을 줄 때까지(동안 (줄 != 빈값)).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; )로 써요.
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);.
하나씩 꺼낸 변수를 바꾸면 목록도 바뀔 줄 알았어요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)로 꺼내면 원본이 바뀌어요.
도는 중에 목록에서 값을 빼는 실수는 목록 고치기 단원에서 다뤄요.
고르는 법은 간단해요.
처음부터 끝까지 다 보면 하나씩, 번호가 필요하거나 거꾸로 가면 번호로, 몇 번 돌지 모르면
while, 적어도 한 번은 해야 하면 do-while이에요.끝 표시까지 읽을 때는 "읽기 → 확인 → 처리 → 다시 읽기" 순서를 지켜요.
확인보다 처리가 먼저 오면 끝 표시까지 더해 버려요.