C++ 1.8 Tests 정리: 조건식, short-circuit, if 초기화, switch fall-through, vector::size() | slug: 20260912-cpp-1-8-tests-merged | series: A Tour of C++ 1.8
A Tour of C++ §1.8 Tests는 선택과 반복을 어떻게 표현하는지를 다룹니다. C++는 if, switch, while, for로 분기와 루프를 만들고, 그 조건 자리에서는 정수·포인터·논리 연산·삼항 연산자까지 같은 방식으로 “실행할지 말지”를 결정합니다.
이번 글에서는 문법 나열보다, 공부하면서 실제로 헷갈리기 쉬운 지점—truthiness, short-circuit, 연속 비교의 함정, 삼항 연산자, if 초기화의 세미콜론, switch fall-through, size()와 capacity(), 그리고 FAQ·디버깅 체크리스트—를 한 흐름으로 정리합니다.
핵심 아이디어: 선택과 반복
C++에서 프로그램의 흐름을 바꾸는 대표 구문은 다음과 같습니다.
| 구문 | 역할 |
|---|---|
if | 조건에 따른 선택(분기) |
switch | 여러 상수 값 중 하나로 분기 |
while / for | 조건이 참인 동안 반복 |
입출력도 함께 자주 등장합니다. cout <<는 출력, cin >>는 입력입니다. >>의 오른쪽 변수 타입이 무엇을 읽을지(문자, 정수 등)를 정합니다. 또한 변수는 파일 맨 위에 모두 모아 둘 필요 없이, 필요한 순간에 선언해도 됩니다.
C++에서 무엇이 true인가?
if, while, 조건 연산자 ?:의 첫 번째 식처럼 조건이 필요한 자리에서는 값이 Boolean 판단으로 변환됩니다.
| 표현식 | 조건 결과 | 의미 |
|---|---|---|
0 | false | 정수 0은 false입니다. |
| 0이 아닌 정수 | true | 1, -1, 42 모두 true입니다. |
nullptr | false | 포인터가 어떤 객체도 가리키지 않습니다. |
| null이 아닌 포인터 | true | 유효 여부를 검사할 수 있는 주소값이 들어 있습니다. |
int count = 3;
if (count) {
// 실행됨: 3은 0이 아님
}
int* p = nullptr;
if (p) {
// 실행되지 않음
}
중요: true는 정수값이 꼭 1이라는 뜻이 아닙니다. 조건식 자리에서 true로 변환되는 값이라는 뜻입니다.
논리 연산자와 short-circuit evaluation
&&는 “둘 다 참이어야 함”, ||는 “하나만 참이어도 됨”을 뜻합니다. 여기서 핵심은 결과가 이미 결정되면 뒤의 식을 실행하지 않을 수 있다는 점입니다. 이를 **short-circuit evaluation(단락 평가)**이라고 합니다.
bool is_valid = ptr != nullptr && ptr->ready();
왼쪽의 ptr != nullptr가 false라면 C++은 오른쪽의 ptr->ready()를 호출하지 않습니다. 따라서 null pointer를 역참조하는 일을 막을 수 있습니다. 성능 최적화이기도 하지만, 더 본질적으로는 안전 규칙입니다.
bool open = true;
if (open || expensive_check()) {
// expensive_check()는 호출되지 않음
}
||에서는 왼쪽이 true이면 전체 결과가 true로 확정됩니다. 반대로 &&에서는 왼쪽이 false이면 전체 결과가 false로 확정됩니다.
수학식처럼 연속 비교를 쓰면 안 되는 이유
아래 식은 수학적으로는 자연스러워 보이지만 C++에서 범위 검사가 아닙니다.
if (5 < x < 20) { // 잘못된 범위 검사
}
C++은 왼쪽부터 계산합니다. 먼저 5 < x가 true 또는 false가 되고, 그 결과가 다시 20과 비교됩니다. 범위 검사는 반드시 다음처럼 각각 적어야 합니다.
if (5 < x && x < 20) {
// x가 5보다 크고 20보다 작을 때만 실행
}
조건 연산자: condition ? a : b
조건 연산자는 두 값 중 하나를 선택할 때 사용합니다. 두 분기가 짧고 하나의 값으로 자연스럽게 표현될 때 잘 어울립니다.
const char* mode = hot ? "Hot" : "Normal";
int limit = enabled ? 100 : 0;
hot이 true이면 "Hot", false이면 "Normal"을 선택한다는 뜻입니다. 여러 줄의 처리나 부작용(side effect)이 있다면 일반적인 if/else가 더 읽기 좋습니다.
if 초기화란? if (초기화; 조건)
C++에서는 if 안에서 변수를 만들고 바로 검사할 수 있습니다.
if (auto n = v.size(); n!=0) {
// n을 사용하는 코드
}
처음에는 세미콜론 앞뒤를 둘 다 조건으로 검사한다고 오해하기 쉽습니다. 그렇지 않습니다.
| 위치 | 의미 |
|---|---|
; 앞 | 변수 생성·초기화. 참/거짓 검사가 아닙니다. 항상 실행됩니다. |
; 뒤 | if에 들어갈지를 정하는 유일한 조건입니다. |
공부하면서 잡은 핵심: if (auto n = v.size(); n!=0)에서 앞부분은 “n을 만든다”이고, 뒷부분만 “들어갈지 말지”를 결정합니다.
짧은 형태 if (auto n = v.size())
세미콜론이 없으면, 만든 값 n 자체가 조건이 됩니다. 숫자 0은 거짓, 0이 아니면 참입니다.
if (auto n = v.size()) {
// n != 0 인 경우와 동일 (size()가 정수일 때)
}
v.size()는 보통 0 또는 양수입니다. 따라서 이 예에서 if (n)과 if (n!=0)은 같은 뜻입니다. n!=0이 “원소가 있는지”를 추가로 확인하는 별도 검사가 아닙니다. 짧은 형태가 바로 그 검사입니다.
세미콜론 형태가 필요한 경우는 조건이 n!=0이 아닐 때입니다. 예를 들어 n > 10처럼 임계값이 다르면 초기화와 조건을 분리해야 합니다.
if (auto n = v.size(); n > 10) {
// 원소 개수가 10보다 클 때만
}
변수 n의 범위(scope)
n은 그 if의 참 가지와 거짓 가지 안에서만 사용할 수 있습니다. if 문이 끝난 뒤에는 n이 없습니다.
if (auto n = v.size(); n!=0) {
// 여기서는 n 사용 가능
} else {
// else 안에서도 n 사용 가능
}
// 여기서 n을 쓰면 오류
switch와 break: fall-through는 언제 생기나?
switch의 각 case는 서로 다른 상수여야 합니다. 맞는 case가 없고 default도 없으면 아무 일도 하지 않습니다. default는 선택 사항입니다.
break가 없으면 fall-through가 발생합니다. 즉, 다음 case의 코드가 이어서 실행되고, break(또는 return)을 만날 때까지 계속됩니다. 버그처럼 보이지만, 의도적으로 case를 붙여 같은 동작을 공유하기도 합니다.
switch (ch) {
case 'u':
case 'n':
// 'u'와 'n'이 같은 처리를 공유 (의도적 fall-through)
break;
default:
break;
}
학습 포인트: fall-through는 “아래로 넘어간다”는 뜻입니다. return을 만나면 함수를 떠나므로 그 지점에서 멈춥니다.
자주 헷갈리는 지점: vector::size()와 capacity
std::vector<int> v;
if (auto n = v.size()) {
std::cout << "A";
} else {
std::cout << "B";
}
출력은 B입니다. 빈 vector의 v.size()는 0이기 때문입니다. 이때 vector 객체가 없는 것이 아닙니다. size()는 현재 들어 있는 원소 수를 의미할 뿐, 내부에 메모리가 확보되어 있는지와는 다른 질문입니다.
| 멤버 함수 | 알려주는 값 |
|---|---|
v.size() | 현재 vector에 들어 있는 원소의 개수 |
v.capacity() | 재할당 없이 담을 수 있도록 확보된 공간의 크기 |
std::vector<int> v {10};
if (auto n = v.size()) {
std::cout << "A"; // n은 1, 따라서 A 출력
}
기억할 문장: 빈 vector는 “없는 vector”가 아니라 “원소가 0개인 vector”입니다.
자주 헷갈리는 질문 FAQ
accept()에서 y가 아니면 왜 false인가?
“bool 함수라서 return하니까 y일 때만 true”라고 생각하기 쉽습니다. 정확히는 bool 함수라서가 아닙니다. 코드에 if (answer == 'y') return true;만 있고, 그 외 모든 경우는 바로 return false;로 떨어지기 때문입니다. 그래서 'n'과 'x'를 구분하지 않습니다. (책의 accept2처럼 switch로 n/default를 나누면 다르게 설계할 수 있습니다.)
switch에서 break를 빼면?
fall-through가 발생해 아래로 넘어가고, break(또는 return)가 있는 곳까지 진행됩니다. 의도적으로 case를 붙여 같은 동작을 공유할 수 있습니다.
if (auto n = v.size(); n!=0)와 if (auto n = v.size())는 다른가?
이 예에서는 같습니다. v.size()는 0 또는 양수이고, C++에서 숫자 0은 거짓·0이 아니면 참이므로 if (n)과 if (n!=0)은 정수 size()에 대해 같은 뜻입니다. 세미콜론 형태는 초기화와 조건이 다를 때(n>10 등) 필요합니다.
n을 if 바깥에서 쓸 수 있나?
없습니다. if의 참/거짓 가지 안에서는 쓸 수 있고, if가 끝난 뒤에는 없습니다.
실무 코드에서 조건식을 읽는 순서
펌웨어, 데몬, 애플리케이션 코드를 읽거나 로그를 디버깅할 때는 조건식을 아래 세 단계로 나누어 보면 좋습니다.
- 각 하위 표현식은 어떤 값을 만드는가?
- 그 값은 조건식에서 true/false로 어떻게 해석되는가?
&&,||때문에 뒤의 함수 호출이나 접근이 생략될 수 있는가?
예를 들어 if (device && device->is_ready())는 먼저 포인터 존재 여부를 확인하고, 포인터가 있을 때만 상태를 확인합니다. 이 순서 자체가 코드의 안전 로직입니다.
요약
- 선택·반복의 기본은
if/switch/while/for입니다. 0,nullptr은 false이고, 0이 아닌 정수와 null이 아닌 포인터는 조건식에서 true입니다.&&는 왼쪽이 false이면 멈추고,||는 왼쪽이 true이면 멈춥니다.- 범위 비교는
low < x && x < high처럼 작성합니다. - 작은 두 갈래 값 선택에는
condition ? a : b를 사용할 수 있습니다. if (init; cond)에서 세미콜론 앞은 초기화(항상 실행), 뒤만 조건입니다.if (auto n = v.size())는 size() 정수에 대해n!=0과 같습니다.- switch는 상수 case, 선택적 default, break 없으면 fall-through입니다.
- 빈 vector는 존재하며, 단지
size()가 0입니다.capacity()와는 다른 질문입니다. accept()가 non-y에서 false인 이유는 “bool이라서”가 아니라 분기 설계 때문입니다.- if에서 만든
n은 if 가지 밖으로 나가지 않습니다.
다음: §1.9 Mapping to Hardware
다음에는 이 추상적인 제어 흐름이 하드웨어와 어떻게 대응되는지(§1.9 Mapping to Hardware)를 이어서 정리할 예정입니다.