본문 바로가기
Topos Uranos

요약

문을 차례로 늘어놓기만 하는 프로그램은 데이터가 무엇이든 언제나 같은 일을 한다. 데이터에 반응하려면 판단을 내려야 한다. 이 수업은 C++의 조건문을 공부한다. 조건에 따라 두 길 가운데 하나를 고르는 if 문과 else 문, 여러 길 가운데 하나를 고르며 조건의 순서가 각 분기가 맡는 경우를 정하는 else if 연쇄, 중첩 조건문과 매달린 else 문의 함정, 그리고 이름 붙은 이산적인 값 가운데 하나를 고르는 switch 문과 enum class 열거형이다. 마지막 경우에는 빠진 경우가 있거나 어떤 경우가 그 뒤의 경우로 떨어지면 컴파일러가 경고한다. 이 모두를 하나로 묶는 개념은 경우 나누기이다. 올바른 조건문은 입력의 집합을, 서로 겹치지 않으면서 그 집합을 빠짐없이 덮는 경우들로 나누고, 각 경우는 그 내부의 테스트 하나와 각 경계에서의 테스트 하나로 확인한다.

학습 목표

  1. 명세의 모든 경우를 다루는 단순 조건문, 연쇄 조건문, 중첩 조건문을 쓴다.
  2. if 문과 else if 문의 연쇄에서 경우들이 겹치지 않고 빠짐없음을 확인하고, 경우마다 테스트 입력을 하나씩 설계한다.
  3. 이산적인 값에 enum class 열거형과 함께 switch 문을 쓰고, 경우가 빠졌을 때 컴파일러가 무엇을 경고하는지 설명한다.
  4. 매달린 else 문과 switch 문의 폴스루를 찾아내고 바로잡는다.

if와 else

조건문의 형태

문제에서 알고리즘으로 수업은 모든 알고리즘을 짓는 세 형태, 곧 순차, 조건문, 반복을 소개했다. 지금까지 이 강좌의 프로그램은, 1단원에서 미리 쓴 몇몇 if 문을 빼면, 모두 순차였다. 이 수업은 두 번째 형태를 C++로 쓴다. 조건문은 조건, 곧 관계와 논리 수업에서 다룬 것과 같은 bool 타입의 식을 계산하여, 조건이 참이면 한 블록을, 거짓이면 다른 블록을 실행한다.

C++ 코드
if (조건) {    // 조건이 참일 때 실행된다} else {    // 조건이 거짓일 때 실행된다}

조건은 언제나 괄호 안에 쓴다. else 부분은 생략할 수 있다. 생략하면 조건이 거짓일 때 아무것도 하지 않고, 프로그램은 조건문 뒤의 문으로 넘어간다. 어느 쪽이든 두 길은 끝에서 다시 만나므로, 조건문은 밖에서 보면 입구 하나와 출구 하나를 가진 하나의 문이며, 1.1강이 요구한 그대로이다.

예시유클리드 나머지

산술 연산 수업은 a % b 식이 피제수의 부호를 가지므로 a=−7a = -7, b=2b = 2일 때 그 값이 −1-1의 값이 되는 반면, 결코 음수가 되지 않는 유클리드 나머지는 1이라는 것을 보였고, 이를 안전하게 고치는 방법은 나머지가 음수일 때만 bb의 값을 더하는 것이라고 예고했다. 프로그램은 b>0b > 0임을 가정한다.

C++ 프로그램
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    int r{a % b};    if (r < 0) {        r = r + b;    }    std::println("{} 나누기 {}의 유클리드 나머지: {}", a, b, r);}
프로그램 입력
-7 2
출력
-7 나누기 2의 유클리드 나머지: 1

조건문이 값을 바꿀 수 있으므로 r 변수는 더 이상 상수가 아니다. 그림은 두 가능한 길을 따라간다.

증명

  1. 실행은 r 변수가 이미 계산된 상태로 조건문에 이르고, 맨 먼저 r < 0 조건을 계산한다. 마름모는 길이 갈라지는 프로그램 안의 유일한 지점이다.

  2. 조건이 참이면, 예컨대 입력이 −7-7, 2일 때처럼, if 블록이 실행되어 r 변수의 값이 −1-1에서 1로 바뀐다.

  3. 거짓이면, 예컨대 입력이 7, 2일 때처럼, 블록은 통째로 건너뛰고 r 변수는 값을 유지한다.

  4. 두 길은 같은 지점에서 만나고, 거기서부터 프로그램은 두 경우 모두 똑같이 진행한다. 결과를 쓰는 문은 언제나 실행된다.

else 문이 있으면 조건문은 서로 배타적인 두 동작 가운데 하나를 고른다. 관계와 논리 수업의 윤년 조건은 bool 값을 만들고, 조건문은 그것을 프로그램의 행동으로 바꾼다.

C++ 프로그램
#include <iostream>#include <print>​int main(){    int year{};    std::cin >> year;    const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0};    if (isLeap) {        std::println("{}년은 윤년이다: 2월은 29일이다.", year);    } else {        std::println("{}년은 윤년이 아니다: 2월은 28일이다.", year);    }}
프로그램 입력
1900
출력
1900년은 윤년이 아니다: 2월은 28일이다.

isLeap 상수처럼 조건에 이름을 붙이면, 무엇을 판단하는지와 그 판단으로 무엇을 하는지가 나뉜다.

중괄호는 언제나

C++의 문법은 블록에 문이 하나뿐일 때 중괄호를 생략하도록 허락한다. if (r < 0) r = r + b; 문은 올바르다. 그러나 중괄호가 없으면 조건문은 바로 뒤의 문 하나만 지배하고, 읽는 사람이 지배된다고 믿는 범위는 들여쓰기에 달려 있는데, 컴파일러는 들여쓰기를 무시한다. 그렇다면 나중의 어떤 수정(두 번째 문을 더하거나 첫 번째 문을 주석으로 만드는 것)도 텍스트에 드러나지 않은 채 의미를 바꿀 수 있다. 여기서 강좌의 규칙이 따라 나온다. if 문이나 else 문의 블록에는, 문이 하나뿐이라도 언제나 중괄호를 붙인다.

모든 오류 가운데 가장 짧은 오류가 이 규칙을 뒷받침한다. 괄호 뒤의 세미콜론은 빈 문이고, 조건문은 그 빈 문을 지배한다. 그 뒤의 블록은 더 이상 if 문에 속하지 않으며 언제나 실행된다.

C++ 프로그램
#include <print>​int main(){    const int stock{12};    if (stock == 0);    {        std::println("상품 품절");    }    std::println("남은 수량: {}", stock);}
컴파일러 경고
main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body]    6 |     if (stock == 0);      |                    ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]    6 |     if (stock == 0);      |     ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’    7 |     {      |     ^
출력
상품 품절남은 수량: 12

아직 12개가 남아 있는데도 프로그램은 상품이 품절이라고 알린다. GCC는 경고 두 개를 낸다. -Wempty-body 옵션은 빈 본문을 중괄호로 감싸라고 권하고, -Wmisleading-indentation 옵션은 블록이 마치 if 문의 지배를 받는 것처럼 들여쓰기되어 있다고 지적한다. 또 하나의 고전적인 오류, 곧 if (x == 0) 대신 쓴 if (x = 0) 문은 컴파일, 실행, 설정 수업에서 보았다. 대입의 값은 0이고, 그것이 false 값으로 변환되며, -Wparentheses 옵션이 경고한다. 두 경우 모두 프로그램은 컴파일된다. 경고가 없다면 오류는 눈에 띄지 않고 지나갈 것이다.

중괄호에는 결과가 하나 더 있다. if 문의 블록 안에서 선언한 변수는 그 안에서만 존재한다. 한 분기에서 계산한 값을 조건문 뒤에서 쓴다면, 나머지 예의 r 변수처럼 변수를 조건문 앞에서 선언한다. 유효 범위는 매개변수와 유효 범위 수업에서 공부한다.

조건 연산자

조건문의 두 분기가 값을 고르기만 할 때, C++는 더 짧은 형태를 제공한다. 조건 ? a : b 식은 조건이 참이면 a 값, 거짓이면 b 값을 가지며, 선택된 요소만 계산한다. 문인 if 문과 달리 조건 연산자는 식이다. 값과 자료형을 가지며, 어떤 식이든 놓일 수 있는 곳에 놓일 수 있다. 예컨대 상수의 초기화가 그런 곳인데, if 문으로는 const 한정자를 포기하지 않고는 이것을 할 수 없다.

C++ 프로그램
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    const int larger{a > b ? a : b};    std::println("큰 값: {}", larger);    std::println("{} {}", b, b == 1 ? "단위뿐" : "단위");    const auto mixed{a > b ? a : 0.5};    std::println("공통 자료형: {:.1f}", mixed);    std::println("괄호 없음: {}", 10 + a > b ? 1 : 0);    std::println("괄호 있음: {}", 10 + (a > b ? 1 : 0));}
프로그램 입력
7 3
출력
큰 값: 73 단위공통 자료형: 7.0괄호 없음: 1괄호 있음: 11

처음 두 줄은 코드를 분명하게 하는 쓰임을 보여 준다. 더 큰 값을 고르는 것과 수에 맞추어 말을 고르는 것인데, 문 전체를 두 분기에서 되풀이하지 않아도 된다. 셋째 줄은 어느 분기가 선택되든 식의 자료형은 하나뿐임을 보여 준다. 두 결과의 자료형이 다르면 산술 연산 수업의 일반 산술 변환으로 공통 자료형으로 변환된다. 여기서는 int 타입과 double 타입에서 double 타입이 나오고, 선택된 7은 7.0으로 저장된다. 마지막 두 줄은 우선순위를 보여 준다. 조건 연산자는 우선순위가 가장 낮은 연산자에 속하므로, 괄호가 없으면 조건은 10 + a > b 식이 되고 값은 11이 아니라 1이 된다. 조건 연산자가 더 큰 식의 일부가 될 때는 괄호로 감싼다.

값을 고르는 것 이상을 요구하는 순간 이 연산자는 코드를 흐리게 만든다. a > b ? (a > c ? a : c) : (b > c ? b : c) 식처럼 중첩하면, 읽기 쉽게 해 주는 중괄호도 없이 중첩 조건문을 머릿속에서 다시 짜 맞추게 만든다. ok ? std::println("예") : std::println("아니요") 식처럼 부수 효과를 위해 쓰면, 그것은 변장한 if 문이다. 강좌의 규칙은 이 연산자를 두 짧은 값 가운데 하나를 고를 때만, 중첩하지 않고 쓰는 것이다. 나머지는 모두 if 문으로 쓴다.