switch 문

switch 문 (switch statement)

여러 분기 중에서, 어떤 조건 값에 따라 실행할 문장을 고르고 싶을 때 쓰는 게 switch 문이에요. 이 페이지에서는 switch 문의 문법과 조건, 레이블, 그리고 제어 흐름이 실제로 어떻게 이동하는지 정리할게요.

출처: cppreference

본문

switch 문은 조건의 값에 따라 여러 문장 중 하나로 제어를 옮겨요.

문법

attr(선택) switch ( init-statement(선택) condition ) statement
항목 의미
attr - (C++11부터) 임의 개수의 속성
init-statement - (C++17부터) 다음 중 하나 — 표현식 문(널 문 ;일 수도 있음) — 단순 선언(보통 이니셜라이저가 있는 변수 선언이지만, 임의 개수의 변수나 구조적 바인딩을 선언할 수 있음) — 별명 선언(C++23부터). 어떤 init-statement든 세미콜론으로 끝나야 한다는 점에 유의하세요. 그래서 흔히 비공식적으로 "표현식이나 선언 뒤에 세미콜론이 붙은 것"이라고 표현해요.
condition - 조건
statement - 문장(보통 복합 문)

조건 (Condition)

조건은 표현식이거나 단순 선언일 수 있어요.

문법적으로 구조적 바인딩 선언으로 해석될 수 있으면, 구조적 바인딩 선언으로 해석돼요. (C++26부터)
  • 문법적으로 표현식으로 해석될 수 있으면 표현식으로 다뤄져요. 그렇지 않으면 구조적 바인딩 선언이 아닌 선언으로 다뤄져요(C++26부터).

제어가 condition에 도달하면, condition은 값을 산출하고, 그 값으로 제어가 어느 레이블로 갈지 결정돼요.

표현식

condition이 표현식이면, 산출하는 값은 그 표현식의 값이에요.

선언

condition이 단순 선언이면, 산출하는 값은 결정 변수(아래 참고)의 값이에요.

비-구조적 바인딩 선언

선언은 다음 제한을 가져요:

  • 문법적으로 다음 형태에 부합해야 해요:
    • type-specifier-seq declarator = assignment-expression (C++11 이전)
    • attribute-specifier-seq(선택) decl-specifier-seq declarator brace-or-equal-initializer (C++11부터)
  • declarator는 함수배열을 지정할 수 없어요.
  • 타입 지정자 시퀀스(C++11 이전) / 선언 지정자 시퀀스(C++11부터)는 타입 지정자와 constexpr만 담을 수 있고, 클래스열거를 정의할 수 없어요.

선언의 결정 변수는 그 선언된 변수예요.

타입

condition은 다음 타입만 산출할 수 있어요:

  • 정수 타입
  • 열거 타입
  • 클래스 타입

산출된 값이 클래스 타입이면, 맥락에 따라 암묵적으로 정수 또는 열거 타입으로 변환돼요.

(변환 후) 타입이 정수 승격의 대상이면, 산출된 값은 승격된 타입으로 변환돼요.

레이블 (Labels)

switch 문 안의 어떤 문장이라도 다음 레이블 중 하나 이상으로 레이블을 붙일 수 있어요:

attr(선택) case constant-expression : (1)
attr(선택) default: (2)

case 또는 default 레이블은 그 레이블을 둘러싸는 가장 안쪽 switch 문과 연결돼요.

다음 조건 중 하나라도 만족하면 프로그램은 ill-formed예요:

  • switch 문에, 변환 후 같은 값을 갖는 constant-expression을 가진 case 레이블이 여러 개 연결된 경우.
  • switch 문에 default 레이블이 여러 개 연결된 경우.

제어 흐름 이동 (Control flow transfer)

switch 문의 조건이 (변환 후) 값을 산출하면:

  • 연결된 case 레이블 상수 중 하나가 같은 값이면, 매칭된 case 레이블이 붙은 문장으로 제어가 이동해요.
  • 그렇지 않고 연결된 default 레이블이 있으면, default 레이블이 붙은 문장으로 제어가 이동해요.
  • 그렇지 않으면, switch 문 안의 어떤 문장도 실행되지 않아요.

casedefault 레이블 자체는 제어 흐름을 바꾸지 않아요. switch 문 중간에서 빠져나가려면 break을 봐요.

컴파일러는 case 레이블 바로 앞에 [[fallthrough]] 속성이 와서 의도적인 낙하(fallthrough)임을 알리지 않는 한, 낙하(break 없이 다음 case·default 레이블에 도달)에 경고를 발행할 수 있어요(C++17부터).

switch (1)
{
    case 1: std::cout << '1'; // "1"을 출력,
    case 2: std::cout << '2'; // 그다음 "2"를 출력
}
switch (1)
{
    case 1: std::cout << '1'; // "1"을 출력
            break;            // 그리고 switch를 빠져나감
    case 2: std::cout << '2';
            break;
}
### 이니셜라이저가 있는 switch 문 init-statement를 쓰면, switch 문은 다음과 같다: { init-statement switch ( condition ) statement } — 단, init-statement가 선언일 때 그 선언이 선언한 이름들과 condition이 선언일 때 그 선언이 선언한 이름들은 같은 범위에 있고, 그 범위가 statement의 범위이기도 한다. (C++17부터)

주의할 점 (Notes)

제어 이동은 변수의 범위에 들어가는 것이 허용되지 않으므로, statement 안에서 선언 문을 만나면 그 선언은 자기 자신의 복합 문으로 범위를 감싸야 해요:

switch (1)
{
    case 1:
        int x = 0;             // 초기화
        std::cout << x << '\n';
        break;
    default:
        // 컴파일 오류: default로 점프하면
        // 'x'를 초기화하지 않고 그 범위에 들어가게 됨
        std::cout << "default\n";
        break;
}
switch (1)
{
    case 1:
    {
        int x = 0;
        std::cout << x << '\n';
        break;
    }                       // 'x'의 범위는 여기서 끝
    default:
        std::cout << "default\n"; // 오류 없음
        break;
}

키워드

switch

예제

다음 코드는 switch 문의 여러 사용 사례를 보여줘요.

#include <iostream>

int main()
{
    const int i = 2;

    switch (i)
    {
        case 1:
            std::cout << '1';
        case 2:   // 실행은 이 case 레이블에서 시작
            std::cout << '2';
        case 3:
            std::cout << '3';
            [[fallthrough]];  // C++17 속성: 낙하 경고를 끔
        case 5:
            std::cout << "45";
            break;  // 이후 문의 실행은 종료
        case 6:
            std::cout << '6';
    }

    std::cout << '\n';

    switch (i)
    {
        case 4:
            std::cout << 'a';
        default:
            std::cout << 'd'; // 적용 가능한 상수 표현식이 없으므로
                              // default가 실행됨
    }

    std::cout << '\n';

    switch (i)
    {
        case 4:
            std::cout << 'a'; // 아무것도 실행되지 않음
    }

    // switch 문에서 열거를 사용하면 많은 컴파일러가
    // 열거자 중 하나가 처리되지 않으면 경고를 발행함
    enum color { RED, GREEN, BLUE };

    switch (RED)
    {
        case RED:   std::cout << "red\n";   break;
        case GREEN: std::cout << "green\n"; break;
        case BLUE:  std::cout << "blue\n";  break;
    }

    // 정수나 열거 타입으로의 암묵 변환이 없을 때
    // C++17 init-statement 문법이 유용할 수 있음
    struct Device
    {
        enum State { SLEEP, READY, BAD };
        auto state() const { return m_state; }
        /* ... */
    private:
        State m_state{};
    };

    switch (auto dev = Device{}; dev.state())
    {
        case Device::SLEEP: /* ... */ break;
        case Device::READY: /* ... */ break;
        case Device::BAD:   /* ... */ break;
    }

    // 병리적 예제
    // statement가 복합 문일 필요는 없음
    switch (0)
        std::cout << "this does nothing\n";

    // 레이블도 복합 문을 요구하지 않음
    switch (int n = 1)
        case 0:
        case 1: std::cout << n << '\n';
}

출력:

2345
d
red
1

i가 2라서 case 2:에서 실행이 시작되고, case 3:을 지나 [[fallthrough]]로 낙하가 의도적임을 알린 뒤 case 5:break까지 "2345"가 이어져 출력돼요. 두 번째 switch는 매칭되는 case가 없으니 default의 'd'가 출력되고, 세 번째는 아무것도 출력되지 않아요. enum color switch는 RED의 처리로 "red"가 나와요. 마지막 switch (int n = 1)은 이니셜라이저 문법과 case 레이블이 복합 문 없이도 가능하다는 걸 보여주며 n 값 1을 출력해요.

결함 보고 (Defect reports)

다음 동작 변경 결함 보고는 과거에 발표된 C++ 표준에 소급 적용됐어요.

(이 페이지에는 동작 변경 결함 보고가 없어요.)

더 알아보기

  • break: switch 블록 중간에서 빠져나온다.
  • [[fallthrough]](C++17): 이전 case 레이블에서의 낙하가 의도적임을 알리는 속성.