PHpullh
언어 비교/반복문

LANGUAGE COMPARISON

반복문를 언어별로 비교하기

같은 반복을 열 개 언어로 나란히 놓고, 인덱스 루프와 범위 루프가 갈리는 지점을 확인합니다.

반복은 셋 중 하나입니다

언어마다 반복문 문법은 다르지만 하는 일은 대개 셋 중 하나입니다. 정해진 횟수만큼 돌기, 컬렉션의 원소를 하나씩 꺼내기, 조건이 참인 동안 계속하기입니다. 문법이 낯설어 보여도 이 셋 중 무엇인지부터 짚으면 읽는 속도가 달라집니다.

갈리는 지점은 인덱스를 직접 다루느냐입니다. C++이나 Java의 전통적인 for (int i = 0; i < n; i++)는 카운터를 손으로 관리하므로 경계를 하나 틀리면 마지막 원소를 빠뜨리거나 범위를 넘습니다. 반면 Python의 for x in items, Kotlin의 for (x in items), Go의 for _, v := range items는 인덱스를 감춰 그 실수를 구조적으로 막습니다.

Go에는 while이 아예 없고 for 하나가 세 역할을 모두 맡습니다. 조건만 적으면 while처럼, 아무것도 안 적으면 무한 루프가 됩니다. 키워드 수를 줄이려는 설계 판단인데, 다른 언어에서 넘어오면 처음엔 어색하고 익숙해지면 오히려 단순합니다.

Kotlin

반복문 — for, while, repeat

Range, withIndex, until, downTo, step 등 Kotlin 반복문의 모든 패턴을 익힙니다.

// 닫힌 범위 1..5 (5 포함)
for (i in 1..5) print("$i ")          // 1 2 3 4 5

// 열린 범위 until (5 미포함)
for (i in 1 until 5) print("$i ")     // 1 2 3 4

// 역순
for (i in 5 downTo 1) print("$i ")   // 5 4 3 2 1

// 스텝
for (i in 0..10 step 2) print("$i ") // 0 2 4 6 8 10

// 컬렉션 순회
val fruits = listOf("🍎", "🍌", "🍇")
for (fruit in fruits) println(fruit)

// 인덱스와 값 동시에
for ((idx, fruit) in fruits.withIndex()) {
    println("$idx: $fruit")
}

// while
var n = 5
while (n > 0) { print("$n "); n-- }

// repeat
repeat(3) { i -> println("반복 $i") }

Python

조건문 & 반복문

Python의 if/elif/else, for, while 문법과 else 절.

score = 85

# if / elif / else
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

# 한 줄 조건식 (삼항 연산자)
result = "합격" if score >= 60 else "불합격"

# 연속 비교 (Python만의 특징)
if 80 <= score < 90:
    print("B등급")

# for + range
for i in range(5):          # 0 1 2 3 4
    print(i, end=" ")

for i in range(1, 10, 2):   # 1 3 5 7 9
    print(i, end=" ")

# enumerate — 인덱스와 값 동시에
fruits = ["🍎", "🍌", "🍇"]
for idx, fruit in enumerate(fruits, start=1):
    print(f"{idx}: {fruit}")

# zip — 두 리스트 병렬 순회
names  = ["Alice", "Bob"]
scores = [95, 87]
for name, sc in zip(names, scores):
    print(f"{name}: {sc}")

# for-else: break 없이 완료되면 else 실행
for n in range(2, 10):
    if 7 % n == 0 and n != 7:
        print(f"{n}는 7의 약수")
        break
else:
    print("7은 소수")

Go

조건문 & 반복문 — if, for, switch

Go에는 while이 없습니다. for가 모든 반복문 역할을 합니다.

package main

import "fmt"

func main() {
	// if — 초기화 구문 포함 가능
	if x := 42; x > 40 {
		fmt.Println("x는 40보다 큼:", x)
	}

	// for — 기본 (C의 for)
	for i := 0; i < 5; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println()

	// for — while처럼
	n := 1
	for n < 100 {
		n *= 2
	}
	fmt.Println("n:", n)

	// for — 무한루프
	count := 0
	for {
		count++
		if count >= 3 {
			break
		}
	}

	// for range — 슬라이스/맵/채널
	fruits := []string{"🍎", "🍌", "🍇"}
	for i, v := range fruits {
		fmt.Printf("%d: %s
", i, v)
	}
	// 인덱스만
	for i := range fruits { _ = i }
	// 값만
	for _, v := range fruits { _ = v }

	// switch — fallthrough 없음 (기본)
	score := 85
	switch {
	case score >= 90:
		fmt.Println("A")
	case score >= 80:
		fmt.Println("B")
	default:
		fmt.Println("C 이하")
	}

	// switch — 타입 스위치
	var i interface{} = "hello"
	switch v := i.(type) {
	case int:    fmt.Println("int:", v)
	case string: fmt.Println("string:", v)
	default:     fmt.Println("unknown")
	}
}

Java

조건문 & 반복문 & switch expression

Java의 전통적 제어흐름과 Java 14+의 switch 표현식으로 코드를 더 안전하게 작성합니다.

public class ControlFlow {
    public static void main(String[] args) {
        // if-else
        int score = 85;
        String grade = score >= 90 ? "A" :
                       score >= 80 ? "B" :
                       score >= 70 ? "C" : "F";

        // switch expression (Java 14+) — 값 반환, break 불필요
        String result = switch (score / 10) {
            case 10, 9 -> "우수";
            case 8     -> "양호";
            case 7     -> "보통";
            default    -> "미흡";
        };

        // switch expression with yield
        int dayNum = 3;
        String day = switch (dayNum) {
            case 1 -> "월요일";
            case 2 -> "화요일";
            case 3 -> {
                System.out.println("수요일 처리");
                yield "수요일";   // 블록에서 값 반환
            }
            default -> "기타";
        };

        // 향상된 for 루프
        int[] nums = {1, 2, 3, 4, 5};
        int sum = 0;
        for (int n : nums) sum += n;

        // 레이블 break (중첩 루프 탈출)
        outer:
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (i == 1 && j == 1) break outer;
                System.out.print(i + "" + j + " ");
            }
        }
        System.out.println();

        System.out.println(grade + " / " + result + " / " + day);
    }
}

JavaScript

for 반복문

반복문 없이 sort 한 번으로 순서를 정리합니다. 직접 for value in &values로 돌면 원소를 빌려 읽기만 하고, &를 빼면 벡터가 통째로 소비되어 뒤에서 다시 쓸 수 없습니다. 정렬은 원소 수에 로그를 곱한 만큼 비교하므로, 최댓값 하나만 필요하다면 iter().max()가 더 싸게 끝납니다.

// for 반복문
const user = { name: "user12", level: 12 };
console.log(user.name, user.level);

TypeScript

for 반복문

반복문 없이 sort 한 번으로 순서를 정리합니다. 직접 for value in &values로 돌면 원소를 빌려 읽기만 하고, &를 빼면 벡터가 통째로 소비되어 뒤에서 다시 쓸 수 없습니다. 정렬은 원소 수에 로그를 곱한 만큼 비교하므로, 최댓값 하나만 필요하다면 iter().max()가 더 싸게 끝납니다.

// for 반복문
const user = { name: "user12", level: 12 };
console.log(user.name, user.level);

C#

for 반복문

반복문 없이 sort 한 번으로 순서를 정리합니다. 직접 for value in &values로 돌면 원소를 빌려 읽기만 하고, &를 빼면 벡터가 통째로 소비되어 뒤에서 다시 쓸 수 없습니다. 정렬은 원소 수에 로그를 곱한 만큼 비교하므로, 최댓값 하나만 필요하다면 iter().max()가 더 싸게 끝납니다.

// for 반복문
using System;
using System.Linq;

var values = new[] { 3, 1, 4, 12 };
Console.WriteLine(string.Join(", ", values.OrderBy(v => v)));

C++

for 반복문

반복문 없이 sort 한 번으로 순서를 정리합니다. 직접 for value in &values로 돌면 원소를 빌려 읽기만 하고, &를 빼면 벡터가 통째로 소비되어 뒤에서 다시 쓸 수 없습니다. 정렬은 원소 수에 로그를 곱한 만큼 비교하므로, 최댓값 하나만 필요하다면 iter().max()가 더 싸게 끝납니다.

// for 반복문
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values = {3, 1, 4, 12};
    std::sort(values.begin(), values.end());
    std::cout << values.front() << "\n";
}

Rust

for 반복문

반복문 없이 sort 한 번으로 순서를 정리합니다. 직접 for value in &values로 돌면 원소를 빌려 읽기만 하고, &를 빼면 벡터가 통째로 소비되어 뒤에서 다시 쓸 수 없습니다. 정렬은 원소 수에 로그를 곱한 만큼 비교하므로, 최댓값 하나만 필요하다면 iter().max()가 더 싸게 끝납니다.

// for 반복문
fn main() {
    let mut values = vec![3, 1, 4, 12];
    values.sort();
    println!("{:?}", values);
}

순회 중에 컬렉션을 바꾸면

가장 자주 나는 사고입니다. 리스트를 순회하면서 원소를 지우면 대부분의 언어에서 문제가 생기는데, 증상이 언어마다 다릅니다. Java는 ConcurrentModificationException을 던져 즉시 알려주지만, Python이나 C++은 조용히 원소를 건너뜁니다. 인덱스가 밀리면서 지운 자리 다음 원소가 검사되지 않기 때문입니다.

안전한 방법은 셋입니다. 뒤에서부터 순회하거나, 남길 것만 모아 새 컬렉션을 만들거나, 언어가 제공하는 전용 API(Java의 removeIf, Python의 리스트 컴프리헨션, Rust의 retain)를 쓰는 것입니다. 순회 도중 직접 지우는 코드는 지금 동작하더라도 원소 하나 차이로 깨집니다.

클로저를 만들 때 무엇이 캡처되는가

루프 안에서 함수를 만들어 나중에 실행하면, 그 함수가 루프 변수의 값을 잡는지 변수 자체를 잡는지에 따라 결과가 갈립니다. JavaScript의 var는 변수 자체를 공유해 모든 함수가 마지막 값만 보게 되지만, let은 반복마다 새 바인딩을 만들어 각자의 값을 기억합니다. Go도 오랫동안 같은 함정이 있다가 루프 변수의 범위가 반복마다 새로 잡히도록 바뀌었습니다.

이 차이는 반복문 자체보다 람다와 클로저 쪽 문제에 가깝지만, 증상은 항상 루프에서 나타납니다. 콜백을 배열에 모아 두는 코드를 쓸 때는 한 번 확인해 보십시오.

인덱스가 정말 필요한지

인덱스를 쓰는 코드는 대개 인덱스가 필요해서가 아니라 습관입니다. 원소만 있으면 되는데 인덱스로 접근하면 경계 실수의 여지가 생기고, 읽는 사람도 인덱스에 다른 의미가 있는지 확인해야 합니다. 위치가 결과에 실제로 쓰일 때만 인덱스를 꺼내고, 그때도 enumerate(Python)나 withIndex(Kotlin)처럼 원소와 위치를 함께 주는 형태를 쓰는 편이 안전합니다.