요약
문을 차례로 늘어놓기만 하는 프로그램은 데이터가 무엇이든 언제나 같은 일을 한다. 데이터에 반응하려면 판단을 내려야 한다. 이 수업은 C++의 조건문을 공부한다. 조건에 따라 두 길 가운데 하나를 고르는 if 문과 else 문, 여러 길 가운데 하나를 고르며 조건의 순서가 각 분기가 맡는 경우를 정하는 else if 연쇄, 중첩 조건문과 매달린 else 문의 함정, 그리고 이름 붙은 이산적인 값 가운데 하나를 고르는 switch 문과 enum class 열거형이다. 마지막 경우에는 빠진 경우가 있거나 어떤 경우가 그 뒤의 경우로 떨어지면 컴파일러가 경고한다. 이 모두를 하나로 묶는 개념은 경우 나누기이다. 올바른 조건문은 입력의 집합을, 서로 겹치지 않으면서 그 집합을 빠짐없이 덮는 경우들로 나누고, 각 경우는 그 내부의 테스트 하나와 각 경계에서의 테스트 하나로 확인한다.
학습 목표
- 명세의 모든 경우를 다루는 단순 조건문, 연쇄 조건문, 중첩 조건문을 쓴다.
if문과else if문의 연쇄에서 경우들이 겹치지 않고 빠짐없음을 확인하고, 경우마다 테스트 입력을 하나씩 설계한다.- 이산적인 값에
enum class열거형과 함께switch문을 쓰고, 경우가 빠졌을 때 컴파일러가 무엇을 경고하는지 설명한다. - 매달린
else문과switch문의 폴스루를 찾아내고 바로잡는다.
if와 else
조건문의 형태
문제에서 알고리즘으로 수업은 모든 알고리즘을 짓는 세 형태, 곧 순차, 조건문, 반복을 소개했다. 지금까지 이 강좌의 프로그램은, 1단원에서 미리 쓴 몇몇 if 문을 빼면, 모두 순차였다. 이 수업은 두 번째 형태를 C++로 쓴다. 조건문은 조건, 곧 관계와 논리 수업에서 다룬 것과 같은 bool 타입의 식을 계산하여, 조건이 참이면 한 블록을, 거짓이면 다른 블록을 실행한다.
if (조건) { // 조건이 참일 때 실행된다} else { // 조건이 거짓일 때 실행된다}조건은 언제나 괄호 안에 쓴다. else 부분은 생략할 수 있다. 생략하면 조건이 거짓일 때 아무것도 하지 않고, 프로그램은 조건문 뒤의 문으로 넘어간다. 어느 쪽이든 두 길은 끝에서 다시 만나므로, 조건문은 밖에서 보면 입구 하나와 출구 하나를 가진 하나의 문이며, 1.1강이 요구한 그대로이다.
산술 연산 수업은 a % b 식이 피제수의 부호를 가지므로 , 일 때 그 값이 의 값이 되는 반면, 결코 음수가 되지 않는 유클리드 나머지는 1이라는 것을 보였고, 이를 안전하게 고치는 방법은 나머지가 음수일 때만 의 값을 더하는 것이라고 예고했다. 프로그램은 임을 가정한다.
#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 변수는 더 이상 상수가 아니다. 그림은 두 가능한 길을 따라간다.
증명
실행은
r변수가 이미 계산된 상태로 조건문에 이르고, 맨 먼저r < 0조건을 계산한다. 마름모는 길이 갈라지는 프로그램 안의 유일한 지점이다.조건이 참이면, 예컨대 입력이 , 2일 때처럼,
if블록이 실행되어r변수의 값이 에서 1로 바뀐다.거짓이면, 예컨대 입력이 7, 2일 때처럼, 블록은 통째로 건너뛰고
r변수는 값을 유지한다.두 길은 같은 지점에서 만나고, 거기서부터 프로그램은 두 경우 모두 똑같이 진행한다. 결과를 쓰는 문은 언제나 실행된다.
else 문이 있으면 조건문은 서로 배타적인 두 동작 가운데 하나를 고른다. 관계와 논리 수업의 윤년 조건은 bool 값을 만들고, 조건문은 그것을 프로그램의 행동으로 바꾼다.
#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); }}19001900년은 윤년이 아니다: 2월은 28일이다.isLeap 상수처럼 조건에 이름을 붙이면, 무엇을 판단하는지와 그 판단으로 무엇을 하는지가 나뉜다.
중괄호는 언제나
C++의 문법은 블록에 문이 하나뿐일 때 중괄호를 생략하도록 허락한다. if (r < 0) r = r + b; 문은 올바르다. 그러나 중괄호가 없으면 조건문은 바로 뒤의 문 하나만 지배하고, 읽는 사람이 지배된다고 믿는 범위는 들여쓰기에 달려 있는데, 컴파일러는 들여쓰기를 무시한다. 그렇다면 나중의 어떤 수정(두 번째 문을 더하거나 첫 번째 문을 주석으로 만드는 것)도 텍스트에 드러나지 않은 채 의미를 바꿀 수 있다. 여기서 강좌의 규칙이 따라 나온다. if 문이나 else 문의 블록에는, 문이 하나뿐이라도 언제나 중괄호를 붙인다.
모든 오류 가운데 가장 짧은 오류가 이 규칙을 뒷받침한다. 괄호 뒤의 세미콜론은 빈 문이고, 조건문은 그 빈 문을 지배한다. 그 뒤의 블록은 더 이상 if 문에 속하지 않으며 언제나 실행된다.
#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 한정자를 포기하지 않고는 이것을 할 수 없다.
#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 문으로 쓴다.
콘텐츠를 불러오는 중…