C++/카루의 C++ 강좌

[카루의 C++ 강좌] 3-4. switch문과 [[fallthrough]], [[likely]]

카루-R 2020. 3. 24. 18:13
반응형

다중 if문을 기억하시나요?

if (조건식) {
}
else if (조건식) {
}
else if (조건식) {
}
else {
}

그런데, 이러한 if문은 길어지면 길어질수록 보기 피곤하고 코드가 너무 길어집니다.

성적을 입력받고 다음과 같이 출력하는 프로그램을 만들어보죠.

100점이면      => A+ 출력
90 ~ 99 점이면 => A 출력
80 ~ 89 점이면 => B 출력
70 ~ 79 점이면 => C 출력
60 ~ 69 점이면 => D 출력
50 ~ 59 점이면 => E 출력
그 외이면      => F 출력

우선 성적을 입력하는 것이므로 0~100인지 검사를 먼저 해야겠죠? 따라서 코드는 이렇습니다.

#include <iostream>

int main() {
    int score{};
    std::cout << "성적을 입력하세요: ";
    std::cin >> score;

    if (score > 100 || score < 0) {
        std::cerr << "성적 범위가 잘못되었습니다." << std::endl;
        return -1;
    }

    score /= 10; // 점수를 10으로 나눔

    if (score == 10)
        std::cout << "A+" << std::endl;
    else if (score == 9)
        std::cout << "A" << std::endl;
    else if (score == 8)
        std::cout << "B" << std::endl;
    else if (score == 7)
        std::cout << "C" << std::endl;
    else if (score == 6)
        std::cout << "D" << std::endl;
    else if (score == 5)
        std::cout << "E" << std::endl;
    else
        std::cout << "F" << std::endl;
}

정수 계열에서 / 연산자는 몫만 구한다고 말씀드렸죠? 따라서 score에는 십의 자리 숫자만 들어가게 됩니다.

만약 score / 10이 7이라면 score의 범위는 70~79가 됩니다. 따라서 저런 비교가 가능하죠.

물론 score >= 70 && score <= 79라는 식을 써도 되지만 그러기엔 너무 타이핑이 많아집니다.

자, 아무튼 위에를 보시면 "너무 길다"라는 게 느껴지실 겁니다. 그래서, 이러한 다중 if문을 대체하기 위해 switch문이 존재합니다.

switch (초기화 식; 수식) 
{
case 상수: /* 문장 */
    [[fallthrough]]; /* 또는 */ break;
default: /* 문장 */
    break;
}

반복문에서 쓰던 그 break; 맞습니다. 일단 위 예제를 switch문으로 바꿔봅시다.

#include <iostream>

int main()
{
    int score{};
    std::cout << "성적을 입력하세요: ";
    std::cin >> score;

    if (score > 100 || score < 0)
    {
        std::cerr << "성적 범위가 잘못되었습니다." << std::endl;
        return -1;
    }

    switch (score /= 10; score)
    {
    case 10: std::cout << "A+" << std::endl; break;
    case 9:  std::cout << "A" << std::endl;  break;
    case 8:  std::cout << "B" << std::endl;  break;
    case 7:  std::cout << "C" << std::endl;  break;
    case 6:  std::cout << "D" << std::endl;  break;
    case 5:  std::cout << "E" << std::endl;  break;
    default: std::cout << "F" << std::endl;  break;
    }
}

조금은 더 정돈된 느낌이 듭니다. switch문에서 초기화 식은 if문처럼 필요 없으면 생략을 할 수도 있으며, 필요할 땐 세미콜론으로 초기화 식을 작성해주면 됩니다 (C++17) 그리고 switch문 안에 있는 변수의 값에 따라 case 가 실행됩니다. score가 10인 경우엔 case 10: 이 실행되고, 7이라면 case 7이 실행되는 식이죠.

그리고 else문과 비슷한 역할은 default에서 실행합니다. case에 하나도 해당이 안 되면 default가 실행됩니다.

case문의 마지막에는 break;를 마지막에 써 주셔야 합니다. 그렇지 않으면 다음 case들이 연이어 실행됩니다. 한번 break;를 빼 볼까요?

    switch (score /= 10; score)
    {
    case 10: std::cout << "A+" << std::endl; 
    case 9:  std::cout << "A" << std::endl; 
    case 8:  std::cout << "B" << std::endl; 
    case 7:  std::cout << "C" << std::endl; 
    case 6:  std::cout << "D" << std::endl; 
    case 5:  std::cout << "E" << std::endl; 
    default: std::cout << "F" << std::endl; 
    }
성적을 입력하세요: 76
C
D
E
F

 

C만 나와야 하는데 아래 case들까지 모조리 실행되어 C, D, E, F가 다 나왔습니다. 그래서 다음 case의 실행을 막고 switch문을 빠져나오기 위해 break는 꼭 필요합니다.

만약, 꼭 다음 case문을 실행해야 할 필요가 있다면 [[fallthrough]] 특성을 이용하면 됩니다.

    switch (score /= 10; score) {
    case 10: 
        std::cout << "A+" << std::endl;
        [[fallthrough]]; // 아래 case문도 같이 실행

    case 9: 
        std::cout << "A 이상" << std::endl; 
        [[fallthrough]];

    case 8:
        std::cout << "B 이상" << std::endl; 
        [[fallthrough]];

    case 7:
        std::cout << "C 이상" << std::endl; 
        [[fallthrough]];

    case 6:  
        std::cout << "D 이상" << std::endl; 
        [[fallthrough]];

    case 5:
        std::cout << "E 이상" << std::endl;
        break; // 여기서는 탈출해야겠죠? F를 출력할 수는 없으니...

    default: 
        std::cout << "F" << std::endl;
        break;
    }
성적을 입력하세요: 98
A 이상
B 이상
C 이상
D 이상
E 이상

switch문은 이렇게 작성하는 것이 좋습니다. 한 줄로 작성하기엔 가독성이 너무 떨어지죠.

아무튼, 이렇게 [[fallthrough]];를 쓰면 case에 break를 안 써도 컴파일러가 경고를 내지 않고, 나중에 자신이 보았을 때도 "아, 이건 의도적으로 break를 안 썼구나" 하고 바로 알 수 있습니다.

참고로 switch문에는 문자열을 사용할 수 없습니다. 상수값만 사용 가능합니다.

문자열을 사용할 수 있는 편법이 있긴 합니다!

"[C++ 심화] 8 switch문에서 문자열 사용하기"를 참고해 주세요.

 

그리고, 만약 다중 if문이나 switch 문 사용에서 "다른 선택지보다 유난히 많이 걸릴 것 같은" 선택지가 있을 수 있습니다. 혹은 그 반대로 다른 선택지에 비해 유난히 적게 걸릴 것 같은 선택지가 있을 수도 있겠죠.

이건 컴파일러의 최적화를 돕는 구문입니다. C++20에서 추가되는 특성 [[likely]], [[unlikely]]가 그 주인공이죠.

사용법은 간단합니다. 자주 쓰일 것 같은 case나 if문 앞에 [[likely]]를, 자주 쓰일 것 같지 않은 case나 if문 앞에 [[unlikely]]를 붙이면 됩니다.

예를 들어서 성적이 F가 나올 확률이 1% 정도라고 해 봅시다. 그렇다면 이렇게 쓰면 됩니다.

    switch (score /= 10; score) {
    case 10: 
        // .......
    case 9: 
        // .......
    case 8:
        // .......
    case 7:
        // .......
    case 6:  
        // .......
    case 5:
        // .......
    [[unlikely]] default: 
        std::cout << "F" << std::endl;
        break;
    }

사실 이 경우에는 default가 가장 마지막에 실행될 문장이므로 굳이 필요하진 않을 것 같습니다만..일단 알려드립니다. 그리고, 이 반 평균이 B라면 대부분 80점대를 맞았다는 뜻인데, 그럴 땐 [[likely]]를 사용합니다.

    switch (score /= 10; score) {
    case 10: 
        // .......
    case 9: 
        // .......
    [[likely]] case 8:
        // .......
    case 7:
        // .......
    case 6:  
        // .......
    case 5:
        // .......
    [[unlikely]] default: 
        // .......
    }

참고로 한 선택지에서 likely나 unlikely는 한 번씩만 등장할 수 있습니다. 어찌보면 당연한 거죠. 다른 것에 비해서 "가장" ~한 선택지들이니..

이해가 안 되신다면 likely나 unlikely는 과감하게 넘어가세요. 어차피 이런 것도 국내 서적에서 설명하기까지 몇 년 더 걸릴 것입니다.

이들에 관한 더 자세한 내용은

"[C++ 특수] 2. [[likely]]와 [[unlikely]]에 대하여" 를 참고하세요.

 

3단원 마지막 도전 과제입니다. 조금만 더 힘냅시다.

아, 일단 오늘은 도전 과제가 없습니다. 다중 if문에서 많이 했던 거라 딱히 낼 문제가 없네요.

이는 추후 올리겠습니다.


수고하셨습니다. 3단원이 끝났습니다.

다음 시간부터는 함수를 직접 만들어볼 겁니다.

참고: 본 강좌의 연계 심화 강좌는

"[C++ 심화] 8.switch 문에서 문자열 사용하기"

"[C++ 특수] 2. [[likely]]와 [[unlikely]]에 대하여" 입니다.

 

반응형