int foo(int x, int y)
{
int z{ y };
if (x > y)
{
z = x;
}
return z;
}
foo(1, 0)으로 호출하면 함수 내의 모든 구문이 빠짐없이 실행됩니다.isLowerVowel() 함수를 살펴볼게요.bool isLowerVowel(char c)
{
switch (c) // 구문 1
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
return true; // 구문 2
default:
return false; // 구문 3
}
}
if 문에는 두 개의 분기가 있어요. 조건이 참일 때 실행되는 분기와, 거짓일 때 실행되는 분기입니다.switch 문은 더 많은 분기를 가질 수 있습니다.0 1 2 테스트라고도 불리는데요.0번 1번 2번 반복될 때 제대로 작동하는지 확인해야 한다는 규칙입니다.2번 반복되는 경우에 잘 작동한다면, 2보다 큰 모든 반복 횟수에 대해서도 올바르게 작동해야 하니까요.#include <iostream>
void spam(int timesToPrint)
{
for (int count{ 0 }; count < timesToPrint; ++count)
std::cout << "Spam! ";
}
0번 반복을 테스트하는 spam(0)1번 반복을 테스트하는 spam(1) 2번 반복을 테스트하는 spam(2)spam(2)가 제대로 작동한다면, n이 2보다 큰 spam(n)도 당연히 잘 작동할 거예요.0이나 음수로도 테스트해 보는 것이 아주 좋은 생각입니다.정수(Integer)
- 정수의 경우, 함수가
음수0양수를 어떻게 처리하는지 반드시 확인하세요.- 또한 상황에 따라 오버플로우 문제도 꼭 체크해야 합니다.
ㅤ
부동 소수점 숫자(Floating point number)- 부동 소수점 숫자의 경우, 정밀도 문제를 가진 값들을 함수가 어떻게 처리하는지 고려해 보세요.
- 테스트하기에 좋은
double자료형 값으로는 예상보다 약간 큰 숫자를 테스트하기 위한0.1과-0.1- 그리고 예상보다 약간 작은 숫자를 테스트하기 위한
0.7과-0.7이 있습니다.
ㅤ
문자열(String)- 문자열의 경우, 함수가
빈 문자열영숫자 문자열공백이 포함된 문자열(앞, 뒤, 중간 포함)- 그리고
오직 공백으로만 이루어진 문자열을 어떻게 처리하는지 확인하세요.
ㅤ
포인터(Pointer)- 만약 함수가 포인터를 취한다면,
nullptr도 잊지 말고 꼭 테스트해 보세요.

#include <iostream>
int main()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
if (x >= 5) // 앗, operator> 대신 operator>=를 사용했네요
std::cout << x << " is greater than 5\n";
return 0;
}
5를 입력하면 조건식 x >= 5가 참으로 평가되므로, 연결된 명령문이 실행되어 버립니다. 5는 5보다 크지 않은데 말이죠.Enter an integer: 5
5 is greater than 5
for 루프를 사용한 또 다른 예를 살펴볼까요?#include <iostream>
int main()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
// 앗, operator< 대신 operator>를 사용했네요
for (int count{ 1 }; count > x; ++count)
{
std::cout << count << ' ';
}
std::cout << '\n';
return 0;
}
1부터 사용자가 입력한 숫자 사이의 모든 숫자를 출력해야 해요. Enter an integer: 5
for 루프에 진입할 때 count > x가 거짓이 되기 때문에 루프가 단 한 번도 반복되지 않기 때문이랍니다.1 2 3 4 5를 출력하기를 원했어요. <= 대신 < 사용), 루프가 의도한 것보다 한 번 덜 실행되어 1 2 3 4만 출력하게 됩니다.#include <iostream>
int main()
{
for (int count{ 1 }; count < 5; ++count)
{
std::cout << count << ' ';
}
std::cout << '\n';
return 0;
}
#include <iostream>
int main()
{
int x{ 5 };
int y{ 7 };
if (!x > y) // 앗: 연산자 우선순위 문제가 발생했어요
std::cout << x << " is not greater than " << y << '\n';
else
std::cout << x << " is greater than " << y << '\n';
return 0;
}
!)가 비교 연산자(>)보다 우선순위가 더 높기 때문에,(!x) > y처럼 평가된답니다.5 is greater than 7
논리 OR와 논리 AND를 섞어 쓸 때도 발생할 수 있어요.논리 AND가 논리 OR보다 우선순위가 높습니다.) #include <iostream>
int main()
{
float f{ 0.123456789f };
std::cout << f << '\n';
return 0;
}
0.123457
== 연산자와 != 연산자를 사용하는 것이 미세한 반올림 오차로 인해 얼마나 문제의 소지가 있는지(그리고 어떻게 대처해야 하는지) 이야기했었죠. 다음이 그 예입니다.#include <iostream>
int main()
{
double d{ 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 }; // 1.0이 되어야 정상이죠
if (d == 1.0)
std::cout << "equal\n";
else
std::cout << "not equal\n";
return 0;
}
not equal

- 함수가 반환될 때 호출된 함수가 실제로는 실패했는데도, 프로그래머는 성공했다고 가정할 수 있습니다.
- 프로그램이 입력(사용자 또는 파일로부터)을 받을 때 입력값이 실제로는 잘못되었음에도,
프로그래머는 입력 형식이 올바르고 의미상으로 유효하다고 가정할 수 있습니다.- 함수가 호출되었을 때 전달된 인수(Arguments)가 실제로는 유효하지 않은데도, 프로그래머는 의미상 유효할 것이라고 가정할 수 있습니다.
- 함수 내부에서 오류 처리하기
- 호출자(Caller)에게 오류를 넘겨서 처리하게 하기
- 프로그램 중단하기
- 예외(Exception) 던지기
// y가 0일 경우, 아무런 경고 없이 조용히 실패(종료)합니다.
void printIntDivision(int x, int y)
{
if (y != 0)
std::cout << x / y;
}
y에 유효하지 않은 값 0을 전달하면, 우리는 나눗셈 결과를 출력해 달라는 요청을 그냥 무시해 버립니다. void printIntDivision(int x, int y)
{
if (y != 0)
std::cout << x / y;
else
std::cout << "Error: Could not divide by zero\n"; // 오류: 0으로 나눌 수 없습니다.
}
int doIntDivision(int x, int y)
{
return x / y;
}
y가 0이라면 우리는 어떻게 해야 할까요? 이 함수는 반드시 어떤 값이든 반환해야 하므로, 프로그램 로직을 그냥 건너뛸 수는 없습니다. 그렇다고 사용자에게 y의 새 값을 입력하라고 요청해서도 안 됩니다. 이 함수는 순수한 '계산용 함수'이기 때문에, 여기에 입력 루틴을 집어넣는 것은 이 함수를 호출하는 프로그램의 성격에 맞을 수도 있고 안 맞을 수도 있기 때문이죠.void라면, 이를 성공이나 실패를 나타내는 bool 타입으로 바꿀 수 있습니다. bool printIntDivision(int x, int y)
{
if (y == 0)
{
std::cout << "Error: could not divide by zero\n";
return false; // 실패했음을 알립니다.
}
std::cout << x / y;
return true; // 성공했음을 알립니다.
}
main() 함수 안에 있거나 main()에서 직접 호출된 함수 안에 있다면,main()이 0이 아닌 상태 코드를 반환하게 하는 것이 가장 좋습니다.main()까지 끝까지 전달하는 것이 불편하거나 불가능할 수도 있습니다. std::exit()와 같은 중단 문을 사용할 수 있습니다.double doIntDivision(int x, int y)
{
if (y == 0)
{
std::cout << "Error: Could not divide by zero\n";
std::exit(1); // 프로그램을 즉시 종료하고 상태 코드 1을 반환합니다.
}
return x / y;
}

std::cin을 사용하여 사용자에게 텍스트 입력을 요청해 왔습니다.std::cin을 통해 유효하지 않은 텍스트를 입력하는 구체적인 사례들을 살펴보고,operator>>가 작동하는 방식을 간단히 정리한 것입니다.
- 가장 먼저, 입력 버퍼 의 맨 앞에 있는 선행 공백(스페이스, 탭, 줄바꿈 문자 등)이 제거됩니다.
이 과정을 통해 이전 입력에서 버퍼에 남아있던 처리되지 않은 줄바꿈 문자들이 지워지게 돼요.
ㅤ- 만약 입력 버퍼가 비어 있다면
operator>>는 사용자가 새로운 데이터를 입력할 때까지 기다립니다.
사용자가 입력을 마치면 다시 선행 공백을 제거해요.
ㅤ- 그런 다음
operator>>는 줄바꿈 문자를 만나거나,
혹은 저장하려는 변수 타입에 맞지 않는 유효하지 않은 문자를 만날 때까지 연속된 문자들을 최대한 많이 추출(Extract)합니다.
ㅤ- 추출 결과는 다음과 같이 나뉩니다.
- 위 3번 단계에서 문자가 하나라도 추출되었다면, 추출 성공입니다. 추출된 문자는 적절한 값으로 변환되어 변수에 할당(대입)됩니다.
- 만약 3번 단계에서 아무 문자도 추출하지 못했다면, 추출 실패입니다. (C++11 기준) 입력을 받으려던 객체에는 0이라는 값이 할당되며,
std::cin의 상태가 초기화될 때까지 이후의 모든 추출 시도는 즉시 실패하게 됩니다.
입력 중 검사 (Inline: 사용자가 타이핑할 때)
- 애초에 사용자가 잘못된 입력을 타이핑하지 못하도록 막는 방식이에요.
입력 후 검사 (Post-entry: 사용자가 타이핑을 마친 후)
- 사용자가 문자열(String) 형태로 원하는 것을 마음껏 입력하게 한 다음, 그 문자열이 올바른지 검증해요.
- 만약 올바르다면 문자열을 최종 변수 형식으로 변환합니다.
- 사용자가 무엇이든 입력하게 두고,
std::cin과operator>>가 입력을 추출하도록 시도한 뒤에 오류 상황을 처리합니다.
std::cin은 첫 번째 공백 문자를 만나면 추출을 멈춘다는 점을 기억해 주세요.std::cin과 추출 연산자에게 이 어려운 작업을 맡기는 방식을 씁니다.std::cin과 operator>>가 처리를 시도하고,
std::abort를 통해).<cassert> 헤더에 있는 assert 전처리기 매크로를 통해 구현됩니다.#include <cassert> // assert()를 사용하기 위해 포함
#include <cmath> // std::sqrt를 사용하기 위해 포함
#include <iostream>
double calculateTimeUntilObjectHitsGround(double initialHeight, double gravity)
{
assert(gravity > 0.0); // 양의 중력이 없으면 물체는 땅에 닿지 않습니다.
if (initialHeight <= 0.0)
{
// 물체는 이미 땅에 있거나 묻혀 있습니다.
return 0.0;
}
return std::sqrt((2.0 * initialHeight) / gravity);
}
int main()
{
std::cout << "Took " << calculateTimeUntilObjectHitsGround(100.0, -9.8) << " second(s)\n";
return 0;
}
assert 매크로는 조건을 검사할 때마다 아주 약간의 성능 비용이 발생해요. NDEBUG라는 전처리기 매크로가 정의되어 있으면, assert 매크로가 비활성화되는 것입니다.NDEBUG를 기본적으로 설정해 둡니다.#include 문구 이전에 별도의 줄에 #define NDEBUG 또는 #undef NDEBUG 중 하나를 배치하세요.#define NDEBUG // 단언문을 비활성화합니다 (반드시 모든 #include 이전에 위치해야 함)
#include <cassert>
#include <iostream>
int main()
{
assert(false); // 이 번역 단위에서는 단언문이 비활성화되었으므로 작동하지 않습니다.
std::cout << "Hello, world!\n";
return 0;
}
static_assert라고 불리는 또 다른 유형의 단언문도 존재합니다.assert가 실행 시간에 검사하는 것과 달리, static_assert는 컴파일 타임에 검사되는 단언문이에요. static_assert가 실패하면 컴파일 오류가 발생하여 아예 프로그램이 만들어지지 않게 됩니다.<cassert> 헤더에 선언되어 있는 assert 매크로와는 다르게, static_assert는 C++의 키워드입니다. static_assert는 다음과 같은 형태를 가집니다.static_assert(조건, 진단_메시지)
static_assert를 사용하는 예제를 살펴볼까요?static_assert(sizeof(long) == 8, "long must be 8 bytes");
static_assert(sizeof(int) >= 4, "int must be at least 4 bytes");
int main()
{
return 0;
}
단언문(Assertions)은 절대 일어나서는 안 되는 일에 대한 가정을 문서화하여, 개발 과정 중에 발생하는 프로그래밍 오류를 감지하는 데 사용됩니다. 만약 단언문에서 설정한 조건이 깨졌다면, 그건 100% 프로그래머의 잘못입니다. 또한 단언문은 오류로부터의 복구를 허용하지 않아요. (애초에 일어나선 안 될 일이 일어났으니 복구할 필요도 없는 셈이죠.)
단언문은 주로 릴리스 빌드에서 컴파일 시 제거되므로 성능 걱정 없이 아주 넉넉하게 많이 넣으셔도 좋습니다.
오류 처리(Error handling)는 릴리스 빌드에서 (아무리 드물더라도) 실제로 발생할 수 있는 상황들을 우아하게 처리해야 할 때 사용됩니다. 이러한 문제들은 복구가 가능한 문제일 수도 있고(프로그램이 계속 실행될 수 있음), 복구가 불가능한 문제일 수도 있습니다(프로그램을 종료해야 하지만, 적어도 친절한 오류 메시지를 보여주고 자원들이 제대로 정리되도록 보장할 수 있음). 오류 감지와 처리는 런타임 성능 비용과 개발 시간 비용이 모두 발생합니다.