다중 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]]에 대하여" 입니다.
'C++ > 카루의 C++ 강좌' 카테고리의 다른 글
[카루의 C++ 강좌] 3-3. 반복문의 중첩, 무한반복과 break (0) | 2020.03.24 |
---|---|
[카루의 C++ 강좌] 3-2. 조건문과 if ~ else if ~ else (0) | 2020.03.24 |
[카루의 C++ 강좌] 3-1. 반복문과 for, while, do~while (0) | 2020.03.22 |
[카루의 C++ 강좌] 2-5. 참조형 변수 I (0) | 2020.03.22 |
[카루의 C++ 강좌] 2-4. 변수의 초기화 I (0) | 2020.03.21 |