icon
4장 : 제어문

조건문 switch

지난 장에서 if, else if, else 문을 통해 조건에 따라 프로그램의 흐름을 제어하는 방법을 학습했습니다.

이 문법은 매우 유연하고 강력하지만, 하나의 변수 값이 여러 가지 경우의 수에 따라 각각 다른 동작을 해야 할 때는 else if를 길게 나열해야 하는 경우가 발생합니다.

예를 들어 사용자 입력에 따라 요일을 판별하거나, 메뉴 선택에 따라 다른 기능을 실행해야 할 때 말이죠.

이런 상황에서 if-else if 체인보다 더 깔끔하고 효율적인 대안을 제공하는 것이 바로 switch입니다.

switch 문은 하나의 변수(또는 표현식)의 값에 따라 여러 case 중 하나를 선택하여 실행하는 조건문입니다.


switch 문이란 무엇인가?

switch 문은 주어진 표현식의 값을 평가하여 그 값과 일치하는 case 레이블로 프로그램의 실행 흐름을 점프시킵니다.

여러 개의 가능한 값 중 하나를 선택하여 처리할 때 특히 유용합니다.

switch 문을 사용하는 경우

  • 하나의 정수형 변수(또는 정수형으로 평가될 수 있는 표현식)의 값이 여러 개의 특정 값 중 하나일 때.
  • 다수의 else if 문이 동일한 변수에 대한 동등 비교(==)를 반복하는 경우.

switch 문 제약사항

  • switch 문의 조건 표현식은 반드시 정수 계열 타입이어야 합니다. (예: int, char, enum, long, bool 등). 실수형(float, double)이나 문자열(std::string)은 switch 문의 조건으로 직접 사용할 수 없습니다.
  • case 레이블 뒤에 오는 값은 반드시 상수(Constant) 여야 합니다. 변수는 사용할 수 없습니다.

switch 문 형식 및 기본 사용법

switch 문의 기본적인 형식은 다음과 같습니다.

switch 문 형식
switch (표현식) {
    case 상수값1:
        // 표현식의 값이 상수값1과 같을 때 실행할 코드
        break; // break 문은 필수!
    case 상수값2:
        // 표현식의 값이 상수값2와 같을 때 실행할 코드
        break;
    case 상수값3:
        // 표현식의 값이 상수값3과 같을 때 실행할 코드
        break;
    default: // 선택 사항
        // 위의 어떤 case에도 해당하지 않을 때 실행할 코드
        break; // default도 보통 break를 사용 (습관화)
}
  • switch (표현식): 괄호 안의 표현식은 정수 계열 타입으로 평가되어야 합니다.
  • case 상수값: case 키워드 뒤에 표현식과 비교할 상수값을 적고 콜론(:)을 붙입니다. 상수값은 중복될 수 없습니다.
  • break;: case 블록의 끝에 break 문은 매우 중요합니다. break 문은 switch 문을 즉시 종료하고 switch 문 다음의 코드로 실행 흐름을 넘깁니다.
  • default:: 선택 사항입니다. switch 문의 표현식이 어떤 case상수값과도 일치하지 않을 때 default 블록의 코드가 실행됩니다. default는 어떤 위치에 두어도 상관없지만, 일반적으로 가장 마지막에 둡니다.
switch 문 예시
#include <iostream>

int main() {
    int choice;
    std::cout << "메뉴를 선택하세요 (1:불고기, 2:비빔밥, 3:냉면, 4:음료): ";
    std::cin >> choice;

    switch (choice) {
        case 1:
            std::cout << "불고기를 선택하셨습니다." << std::endl;
            std::cout << "가격은 12000원입니다." << std::endl;
            break; // switch 문 종료

        case 2:
            std::cout << "비빔밥을 선택하셨습니다." << std::endl;
            std::cout << "가격은 9000원입니다." << std::endl;
            break; // switch 문 종료

        case 3:
            std::cout << "냉면을 선택하셨습니다." << std::endl;
            std::cout << "가격은 8000원입니다." << std::endl;
            break; // switch 문 종료

        case 4:
            std::cout << "음료를 선택하셨습니다." << std::endl;
            std::cout << "가격은 2000원입니다." << std::endl;
            break; // switch 문 종료

        default: // 어떤 case에도 해당하지 않을 경우
            std::cout << "잘못된 메뉴 번호입니다." << std::endl;
            break; // default도 break를 붙이는 것이 일반적
    }

    std::cout << "주문 처리를 완료했습니다." << std::endl;

    return 0;
}

break 문의 중요성: Fall-through

switch 문에서 break 문을 생략하면, 해당 case 블록의 코드가 실행된 후 다음 case 블록으로 실행 흐름이 연속적으로 흘러들어갑니다(Fall-through).

이는 의도적인 경우도 있지만, 대부분은 프로그래머의 실수로 발생하여 논리적 오류를 유발합니다.

Fall-through 예시
#include <iostream>

int main() {
    char grade = 'B';

    switch (grade) {
        case 'A':
            std::cout << "탁월합니다!" << std::endl;
            // break; // 여기에 break가 없으면 다음 case로 넘어감

        case 'B':
            std::cout << "훌륭합니다!" << std::endl;
            // break; // 여기에 break가 없으면 다음 case로 넘어감

        case 'C':
            std::cout << "잘 했습니다." << std::endl;
            break; // 여기는 break가 있으므로 switch 문 종료

        default:
            std::cout << "다시 시도하세요." << std::endl;
            break;
    }
    // 출력:
    // 훌륭합니다!
    // 잘 했습니다.

    return 0;
}

위 예시에서 grade'B'이므로 "훌륭합니다!"가 출력된 후, break가 없기 때문에 case 'C'로 Fall-through되어 "잘 했습니다."까지 출력됩니다.

이는 보통 의도하는 바가 아닙니다.

의도적인 Fall-through: 가끔은 여러 case가 동일한 코드를 공유해야 할 때 의도적으로 Fall-through를 사용할 수 있습니다.

의도적인 Fall-through 예시
#include <iostream>

int main() {
    int month = 2; // 2월

    switch (month) {
        case 1:  // 1월
        case 3:  // 3월
        case 5:  // 5월
        case 7:  // 7월
        case 8:  // 8월
        case 10: // 10월
        case 12: // 12월
            std::cout << month << "월은 31일까지 있습니다." << std::endl;
            break;
        case 4:  // 4월
        case 6:  // 6월
        case 9:  // 9월
        case 11: // 11월
            std::cout << month << "월은 30일까지 있습니다." << std::endl;
            break;
        case 2: // 2월
            std::cout << month << "월은 28일 또는 29일까지 있습니다." << std::endl;
            break;
        default:
            std::cout << "잘못된 월입니다." << std::endl;
            break;
    }
    // 출력: 2월은 28일 또는 29일까지 있습니다.

    return 0;
}

이처럼 연속된 case 레이블 뒤에 코드를 작성하지 않고 break도 생략하여 여러 case가 하나의 코드 블록으로 Fall-through되도록 할 수 있습니다.

이 경우 Fall-through가 의도적임을 명시하기 위해 C++17부터는 [[fallthrough]]; 속성(attribute)을 사용할 수 있습니다.

의도적인 Fall-through C++17 예시
// C++17 이상
case 'A':
    std::cout << "매우 잘함!" << std::endl;
    [[fallthrough]]; // 컴파일러에게 의도적인 fall-through임을 알림
case 'B':
    std::cout << "잘함!" << std::endl;
    break;

if-else if vs. switch

두 조건문 모두 여러 조건을 처리할 수 있지만, 각각 장단점이 있습니다.

if-else if 체인의 장점

  • 유연성: 다양한 타입의 조건(정수, 실수, 문자열, 복잡한 논리 표현식 등)을 사용할 수 있습니다.
  • 범위 비교: 특정 범위(score >= 80 && score < 90)를 비교하는 데 유리합니다.
  • 논리 연산자 사용: &&, ||, !와 같은 논리 연산자를 사용하여 복잡한 조건을 쉽게 조합할 수 있습니다.

switch 문의 장점

  • 명확성: 하나의 변수 값을 기준으로 여러 case를 명확하게 나열할 수 있어 코드의 구조가 한눈에 들어옵니다.
  • 효율성 (잠재적): 컴파일러가 switch 문을 최적화하여 if-else if 체인보다 더 빠르게 실행될 수 있도록 할 가능성이 있습니다 (특히 case가 많을 경우 점프 테이블 등을 사용).
  • 가독성 (특정 상황): 동일한 변수에 대한 다수의 동등 비교를 처리할 때 if-else if보다 가독성이 좋습니다.

언제 무엇을 사용할까?

  • 범위 비교, 복잡한 논리, 실수/문자열 조건이라면 if-else if 를 사용하세요.
  • 하나의 정수형 변수 값이 여러 개의 특정 상수 중 하나와 일치하는 경우라면 switch 문이 더 적합하고 가독성이 좋습니다.