#19. [레거시 C++ 가이드] 제어문
모던 C++
- (C++11~) 범위 기반 for()가 추가되어 컨테이너 요소의 탐색 처리가 쉬워졌습니다.
- (C++17~) 초기식을 포함하는 if(), switch()가 추가되어 함수 리턴값을 평가하고 소멸하는 코드가 단순해 졌습니다.
- (C++20~) 범위 기반 for()에서 초기식이 추가되었습니다.
개요
조건식에 따라 분기하거나 반복하는 제어문들을 제공합니다.
단순한 조건 검사에서 복잡한 로직 구현까지 두루두루 제어문이 사용되는데요, STL 에서는 시퀀스 처리, 정렬, 검색, 수학 등 일반화된 알고리즘들을 제공합니다. 한번 훑어보시고, 필요한 것이 있다면 알고리즘을 활용하시는 게 유지보수 측면에서 좋습니다.
항목 | 내용 |
---|---|
if (조건) {표현식;} |
조건이 참일 경우 표현식을 실행 |
if (조건1) {표현식1;} else if (조건2) {표현식2;} else {표현식3;} |
조건1이 참일 경우 표현식1을 실행하고, 거짓일 경우 조건2가 참일 경우 표현식 2를 실행하고, 거짓일 경우 표현식3을 실행 |
switch(조건) { case 상수1: 표현식; break; case 상수2: 표현식; break; default: 표현식; break; } |
조건과 매칭되는 case 문 으로 분기.* 조건 : 정수 또는 열거형 * break : 해당 case 표현식을 실행하고 switch 문 탈출* default : 매칭되는 case 문이 없는 경우 실행 |
while (조건) {표현식;} |
조건이 참이면 표현식 반복 실행 |
do {표현식;} while(조건); |
표현식 실행후 조건 검사 하여, 조건이 참이면 표현식 반복 실행 |
for (초기식; 조건식; 표현식2;) {표현식1;} |
초기식; while (조건식) { 표현식1; 표현식2; } 의 순서로 반복 |
continue; |
반복문의 범위 끝으로 이동하여 다음 조건 검사 |
break; |
반복문 탈출 |
return; |
함수 종료 |
goto LABEL; |
지정한 LABEL 로 강제 이동(LABEL: 와 같이 콜론으로 표기함)코드 분석을 방해하기 때문에 비권장 |
(C++17~) 초기식을 포함하는 if(), switch()가 추가되어 함수 리턴값을 평가하고 소멸하는 코드가 단순해 졌습니다.
중첩된 제어문
제어문이 중첩되면, 코드 분석이 어려워 집니다.
1
2
3
4
5
6
7
if (a) {
if (b) {
if (c) {
f(); // (△) 비권장. 분석하기 어렵습니다.
}
}
}
보다는
1
2
3
if (a && b && c) {
f();
}
가 낫습니다.
제어 판단과 실행의 구분(조건 상태표)
복잡한 제어 구조를 가진 경우, 상태를 판단하는 역할(조건 상태표 기준으로 계산)과 실행하는 역할을 분리하면, 구조가 간결해 질 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (a && b) { // (△) 비권장. if 중첩이 많아 분석하기 힘듭니다.
if (c || d) {
}
else {
if (b && e) {
}
else {
}
}
}
else {
if (c || d) {
if (e || f) {
}
else {
}
}
else {
}
}
보다는
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
State CalcState() {
if (a && b && (c || d)) {return State::A;}
if (a && b && !(c || d) && (b && e)) {return State::B;}
if (a && b && !(c || d) && !(b && e)) {return State::C;}
if (!(a && b) && (c || d) && (e || f)) {return State::D;}
if (!(a && b) && (c || d) && !(e || f)) {return State::E;}
if (!(a && b) && !(c || d)) {return State::F;}
assert(false);
}
State state = CalcState(); // 조건 상태표에 따라 상태를 리턴합니다.
switch (state) {
case State::A: break;
case State::B: break;
case State::C: break;
case State::D: break;
case State::E: break;
case State::F: break;
default: assert(false); break;
}
가 낫습니다.
if와 else
if
는 else
상황을 동반할 때가 많습니다. 단순한 널검사인 경우는 상관없습니다만, 복잡한 상황일 경우 else
인 경우의 처리가 정말 필요 없어서 생략한 건지, 실수로 빼먹은 건지 혼동될 수 있으니, else
상황에 대해 주석으로나마 고려한 흔적을 남기는게 좋습니다.
1
2
if (condition) { // (△) 비권장. else문이 고려되었는지 판단하기 어렵습니다.
}
보다는
1
2
3
4
5
if (condition) {
}
else {
assert(false); // (O) "해당 case 절대 없음"
}
가 낫습니다.
사전 조건 검사
함수내에서 본문을 실행하기 전에, 실행하기에 적합한 상태인지 검사(인자 조건은 맞는지 등)한 뒤 본문을 실행하는게 좋습니다. 그렇지 않고 뒤죽박죽 쓰다보면 예외에 안전한 코드를 만들기 어려워 집니다.
1
2
3
4
5
6
7
8
9
10
11
ErrorCode f(param1, param2) {
if (param1 is OK) {
표현식1;
}
if (param2 is FAIL) {
return ErrorCode; // (△) 비권장. 이미 실행된 표현식1을 되돌리기 어렵습니다.
}
else {
표현식2;
}
}
위와 같이 작성한다면, param2
가 부적절한 상황이어서 ErrorCode
를 리턴할때 이미 실행된 표현식1
을 되돌리기가 어렵습니다.
이렇게 작성하기 보다는,
1
2
3
4
5
6
7
8
9
10
11
12
ErrorCode f(param1, param2) {
if (param1 is FAIL) {
return ErrorCode;
}
if (param2 is FAIL) {
return ErrorCode;
}
표현식1;
표현식2;
...
}
가 낫습니다.
일관된 조건 검사
조건의 참/거짓이 일관된 표현으로 작성되는게 좋습니다.
1
2
3
4
if (!condition1) {return;}
if (!condition2) {return;}
if (condition3) {return;} // (△) 비권장. 코딩 실수처럼 보입니다.
if (!condition4) {return;}
보다는
1
2
3
4
if (!condition1) {return;}
if (!condition2) {return;}
if (!condition3) {return;}
if (!condition4) {return;}
으로 condition3
의 참/거짓 논리를 수정하는게 좋습니다.
일관된 대소 비교
대소 비교는 일관된 표현으로 작성하는게 코드 분석에 도움이 됩니다.
1
2
3
4
if (a < b) {
}
if (c > d) { // (△) 비권장. 비교에 일관성이 없어 코드 분석시 헷갈리게 됩니다.
}
보다는
1
2
3
4
if (a < b) {
}
if (d < c) {
}
가 낫습니다.
댓글남기기