| 분류 (Category) | 의미 (Meaning) | C++ 구현 형태 (Implemented in C++ by) |
|---|---|---|
| 조건문 (Conditional statements) | 특정 조건(Condition)이 충족될 때만 일련의 코드를 실행하도록 합니다. | if, else, switch |
| 점프 (Jumps) | CPU에게 다른 위치에 있는 구문부터 실행을 시작하도록 지시합니다. | goto, break, continue |
| 함수 호출 (Function calls) | 다른 위치로 점프하여 실행한 뒤, 다시 원래 위치로 돌아옵니다. | 함수 호출, return |
| 반복문 (Loops) | 특정 조건이 충족될 때까지, 일련의 코드를 0번 이상 반복해서 실행합니다. | while, do-while, for, ranged-for |
| 중단 (Halts) | 프로그램을 완전히 종료시킵니다. | std::exit(), std::abort() |
| 예외 (Exceptions) | 오류 처리(Error handling)를 위해 특별히 설계된 흐름 제어 구조입니다. | try, throw, catch |

constexpr if문의 가장 큰 특징은 조건식이 프로그램 실행 중이 아니라 컴파일 타임에 평가된다는 점이에요.constexpr 조건식이 true로 평가되면, if-else문 전체 구조가 '참일 때 실행되는 문장'으로 완전히 대체됩니다.if-else문 전체가 '거짓일 때 실행되는 문장'으로 대체되거나,else문이 아예 없는 경우에는 아무것도 없는 상태로 깔끔하게 사라지게 됩니다.constexpr if문을 사용하는 방법은 아주 간단해요. if 키워드 바로 뒤에 constexpr 키워드를 붙여주기만 하면 됩니다.#include <iostream>
int main()
{
constexpr double gravity{ 9.8 };
if constexpr (gravity == 9.8) // 이제 constexpr if를 사용합니다.
std::cout << "Gravity is normal.\n";
else
std::cout << "We are not on Earth.\n";
return 0;
}
std::cout << "Gravity is normal.\n";라는 단 하나의 문장만 남겨두게 됩니다.if문보다 constexpr if문을 사용하는 것을 강력히 권장합니다.int main()
{
constexpr double gravity{ 9.8 };
std::cout << "Gravity is normal.\n";
return 0;
}

switch 문이라는 대안적인 조건문을 제공한답니다.#include <iostream>
void printDigitName(int x)
{
switch (x)
{
case 1:
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
int main()
{
printDigitName(2);
std::cout << '\n';
return 0;
}
switch 문의 기본 원리는 아주 간단해요. switch 문을 시작할 때는 switch 키워드를 사용하고, 그 뒤에 평가하고 싶은 조건 표현식을 괄호 안에 넣어줍니다.case 키워드를 사용해서 선언하고, 그 뒤에 상수 표현식이 따라옵니다. #include <iostream>
void printDigitName(int x)
{
switch (x) // x를 평가하여 값 2를 얻어냅니다
{
case 1:
std::cout << "One";
return;
case 2: // 이곳의 case 문과 일치하네요!
std::cout << "Two"; // 그래서 여기서부터 실행이 시작됩니다
return; // 그리고 함수를 호출했던 곳으로 돌아갑니다(return)
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
int main()
{
printDigitName(2);
std::cout << '\n';
return 0;
}
default 키워드를 사용해서 선언합니다. #include <iostream>
void printDigitName(int x)
{
switch (x) // x를 평가하여 값 5를 얻어냅니다
{
case 1:
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default: // 어떤 case 라벨과도 일치하지 않으므로
std::cout << "Unknown"; // 여기서부터 실행이 시작됩니다
return; // 그리고 함수를 호출했던 곳으로 돌아갑니다(return)
}
}
int main()
{
printDigitName(5);
std::cout << '\n';
return 0;
}
return 문을 사용했어요. 하지만 return 문은 함수 전체를 빠져나가게 만든다는 특징이 있죠.break 문은 컴파일러에게 "switch 문 내부의 실행이 끝났으니, switch 블록 다음 문장부터 실행을 계속해라"라고 알려주는 역할을 합니다. switch 문만 깔끔하게 빠져나올 수 있게 해주는 것이죠!switch문이 case 라벨이나 선택 사항인 default 라벨과 일치하면, 일치하는 라벨 바로 다음 명령문부터 실행이 시작됩니다. break return과 같은 종료 조건 중 하나가 발생할 때까지 실행은 순차적으로 계속 진행돼요.case 레이블이 나타나는 것은 종료 조건이 아니라는 것입니다.break나 return이 없으면 실행 흐름은 그다음 case들로 계속 넘쳐흘러 가게(overflow) 됩니다.[[fallthrough]]라는 새로운 속성이 추가되었습니다.[[fallthrough]] 속성은 널 명령문을 수식하여 폴스루가 의도적임을 나타냅니다.#include <iostream>
int main()
{
switch (2)
{
case 1:
std::cout << 1 << '\n';
break;
case 2:
std::cout << 2 << '\n'; // 여기서부터 실행이 시작됩니다.
[[fallthrough]]; // 의도된 폴스루 -- 널 명령문을 나타내는 세미콜론(;)에 주목하세요.
case 3:
std::cout << 3 << '\n'; // 이 줄도 실행됩니다.
break;
}
return 0;
}
case마다 암시적인 블록이 따로 만들어지지 않아요.case 1과 default 레이블 사이에 있는 두 개의 명령문은 case 1에 속한 개별 블록이 아니라,switch (1)
{
case 1: // 암시적 블록을 생성하지 않습니다.
foo(); // 이 부분은 case 1의 암시적 블록이 아니라, 스위치 전체 스코프의 일부입니다.
break; // 이 부분은 case 1의 암시적 블록이 아니라, 스위치 전체 스코프의 일부입니다.
default:
std::cout << "default case\n";
break;
}
case 레이블 이전이든 이후이든 변수를 선언하거나 정의할 수 있습니다. switch (1)
{
int a; // 허용됨: case 레이블 이전의 정의는 허용됩니다.
int b{ 5 }; // 오류(Illegal): case 레이블 이전의 초기화는 허용되지 않습니다.
case 1:
int y; // 허용되지만 나쁜 관행: case 내부의 정의는 허용됩니다.
y = 4; // 허용됨: 값을 할당(Assignment)하는 것은 허용됩니다.
break;
case 2:
int z{ 4 }; // 오류(Illegal): 뒤에 다른 case가 존재한다면 초기화는 허용되지 않습니다.
y = 5; // 허용됨: y는 위(case 1)에서 선언되었으므로 여기서도 사용할 수 있습니다.
break;
case 3:
break;
}
y는 case 1에서 정의되었지만, case 2에서도 문제없이 사용되었습니다. switch의 모든 case는 같은 스코프라서, 한 case에서 선언한 변수는 다른 case에서도 보입니다.case로 점프하면서 초기화를 건너뛸 수 있는 위치에서는 금지됩니다.{} 블록을 만들어 그 안에서 해야 안전합니다.switch (1)
{
case 1:
{ // 여기에 명시적인 블록을 추가한 것을 확인하세요.
int x{ 4 }; // 허용됨: case 내부의 명시적 블록 안에서는 변수를 초기화할 수 있습니다.
std::cout << x;
break;
}
default:
std::cout << "default case\n";
break;
}

if 문이나 switch 문과는 다르게 무조건 점프는 프로그램의 실행 흐름을 코드의 다른 위치로 곧바로 건너뛰게 만들어 줍니다.goto 문을 통해 구현됩니다. #include <iostream>
#include <cmath> // sqrt() 함수를 사용하기 위해 포함합니다.
int main()
{
double x{};
tryAgain: // 이것이 바로 구문 레이블(statement label)입니다.
std::cout << "Enter a non-negative number: ";
std::cin >> x;
if (x < 0.0)
goto tryAgain; // 이것이 바로 goto 문입니다.
std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
return 0;
}
goto 문과 그에 연결된 구문 레이블은 반드시 같은 함수 안에 있어야 합니다.int main()
{
goto skip; // 오류: 이 점프는 허용되지 않습니다. 왜냐하면...
int x { 5 }; // 이 초기화된 변수가 'skip' 구문 레이블 위치에서도 여전히 범위 내에 있기 때문입니다.
skip:
x += 3; // 만약 x가 초기화되지 않았다면 이 코드는 대체 어떻게 평가될까요?
return 0;
}

while 문은 C++이 제공하는 세 가지 종류의 반복문 중 가장 단순한 형태이며, if 문과 아주 비슷한 구조를 가지고 있습니다.while (조건식)
명령문;
while이라는 키워드를 사용해서 선언해요. #include <iostream>
int main()
{
unsigned int count{ 10 }; // 주의: unsigned (부호 없는 정수)로 선언했습니다.
// 10부터 0까지 카운트다운 합니다.
while (count >= 0)
{
if (count == 0)
{
std::cout << "blastoff!";
}
else
{
std::cout << count << ' ';
}
--count;
}
std::cout << '\n';
return 0;
}
10 9 8 7 6 5 4 3 2 1 blastoff!라고 잘 출력합니다. count에 오버플로우가 발생하면서, 4294967295부터 다시 카운트다운을 시작해 버립니다.
for (init-statement; condition; end-expression)
statement;
for 문은 크게 3단계로 나뉘어 실행됩니다.
++ 감소시키는 -- 코드가 들어갑니다.for 문의 각 부분이 실행되는 순서를 정확히 기억해 두는 것이 매우 중요합니다.
1. 초기화 명령문 (Init-statement)
2. 조건식 (Condition) (만약 여기서 거짓이 나오면 반복문은 바로 끝납니다.)
3. 반복문 본문 (Loop body)
4. 끝 표현식 (End-expression) (실행 후 다시 2번 조건식으로 돌아갑니다.)
#include <iostream>
int main()
{
for (int i{ 1 }; i <= 10; ++i)
std::cout << i << ' ';
std::cout << '\n';
return 0;
}

while 루프 do-while 루프 for 루프(반복문) 혹은 switch 문을 즉시 종료시키는 역할을 합니다. switch 문의 문맥에서 break는 보통 각 case의 끝에 사용되어 해당 case가 끝났음을 알립니다. case로 의도치 않게 넘어가 버리는 폴스루 현상을 방지할 수 있어요.#include <iostream>
void printMath(int x, int y, char ch)
{
switch (ch)
{
case '+':
std::cout << x << " + " << y << " = " << x + y << '\n';
break; // 다음 case로 넘어가지 않도록(fall-through) 방지해요
case '-':
std::cout << x << " - " << y << " = " << x - y << '\n';
break; // 다음 case로 넘어가지 않도록 방지해요
case '*':
std::cout << x << " * " << y << " = " << x * y << '\n';
break; // 다음 case로 넘어가지 않도록 방지해요
case '/':
std::cout << x << " / " << y << " = " << x / y << '\n';
break;
}
}
int main()
{
printMath(2, 3, '+');
return 0;
}
break 문을 사용하면 루프를 일찍 종료할 수 있습니다. #include <iostream>
int main()
{
int sum{ 0 };
// 사용자가 최대 10개의 숫자를 입력할 수 있도록 허용해요
for (int count{ 0 }; count < 10; ++count)
{
std::cout << "Enter a number to add, or 0 to exit: ";
int num{};
std::cin >> num;
// 사용자가 0을 입력하면 루프를 종료해요
if (num == 0)
break; // 지금 바로 루프를 빠져나갑니다
// 그렇지 않으면 입력한 숫자를 합계에 더해요
sum += num;
}
// break로 루프를 빠져나오면 여기서부터 실행이 계속됩니다
std::cout << "The sum of all the numbers you entered is: " << sum << '\n';
return 0;
}
break 문은 현재 실행 중인 switch나 반복문만 종료시키고, 그 바로 바깥쪽의 다음 코드를 계속 실행합니다.return 문은 해당 루프가 들어있는 함수 전체를 완전히 종료시키고, 그 함수를 호출했던 위치로 되돌아갑니다.continue 문은 전체 루프를 끝내지 않으면서도, 현재 진행 중인 반복(iteration, 루프의 한 사이클)만 깔끔하게 종료하고 다음 반복으로 넘어가게 해주는 편리한 방법이에요.#include <iostream>
int main()
{
for (int count{ 0 }; count < 10; ++count)
{
// 만약 숫자가 4로 나누어 떨어지면 이번 반복은 건너뜁니다
if ((count % 4) == 0)
continue; // 다음 반복(iteration)으로 이동해요
// 숫자가 4로 나누어 떨어지지 않으면 계속 진행합니다
std::cout << count << '\n';
// continue 문이 실행되면 바로 이 위치(루프의 끝)로 점프하게 됩니다
}
return 0;
}
이 프로그램은 0부터 9까지의 숫자 중 4로 나누어 떨어지지 않는 숫자만 출력합니다:
1
2
3
5
6
7
9

main() 함수의 반환값 상태 코드(Status code)를 인자로 삼아 std::exit()라는 특수한 함수가 호출된답니다. std::exit()는 과연 무엇일까요?std::exit()는 프로그램을 정상 종료시키는 함수예요. 0이 아닌 상태 코드를 반환하겠지만,std::exit()는 여러 가지 정리(Cleanup) 작업을 수행해요.std::exit()에 전달된 인자를 상태 코드로 사용하여 운영체제(OS)에 제어권을 돌려준답니다.main() 함수가 끝난 뒤에 std::exit()가 암시적으로 자동 호출되긴 하지만, 프로그램이 원래 끝나야 할 시점보다 일찍 프로그램을 멈추기 위해 명시적으로 직접 호출할 수도 있어요. 이렇게 사용할 때는 반드시 <cstdlib> 헤더를 포함(Include)해야 합니다.#include <cstdlib> // std::exit()를 사용하기 위해 포함
#include <iostream>
void cleanup()
{
// 필요한 모든 종류의 정리 작업을 수행하는 코드를 여기에 작성합니다
std::cout << "cleanup!\n";
}
int main()
{
std::cout << 1 << '\n';
cleanup();
std::exit(0); // 프로그램을 종료하고 운영체제에 상태 코드 0을 반환합니다.
// 아래의 명령문들은 프로그램이 이미 종료되었으므로 절대 실행되지 않습니다.
std::cout << 2 << '\n';
return 0;
}
main() 함수 안에서 std::exit()를 호출했지만, 사실 std::exit()는 어떤 함수에서든 호출할 수 있고, 호출된 그 시점에서 즉시 프로그램을 종료시킨답니다.std::exit()를 직접 호출할 때 아주 중요한 주의 사항이 하나 있어요. std::exit()는 현재 함수나 호출 스택 위쪽의 어떤 지역 변수도 정리하지 않는다는 점이에요. std::exit()를 호출하는 것은 꽤 위험할 수 있습니다.std::exit()는 프로그램을 즉시 종료시키기 때문에, 종료되기 전에 수동으로 어떤 정리 작업을 하고 싶을 수 있어요. cleanup()이라는 함수를 호출했어요. exit()를 호출할 때마다 매번 수동으로 정리 함수 부르는 것을 기억해야 한다면, 프로그래머에게 큰 부담이 되고 오류가 생기기 딱 좋은 환경이 됩니다.std::atexit()라는 함수를 제공합니다. std::exit()를 통해 프로그램이 종료될 때 자동으로 호출될 함수를 미리 지정해 둘 수 있어요.#include <cstdlib> // std::exit()를 사용하기 위해 포함
#include <iostream>
void cleanup()
{
// 필요한 모든 종류의 정리 작업을 수행하는 코드를 여기에 작성합니다
std::cout << "cleanup!\n";
}
int main()
{
// std::exit()가 호출될 때 자동으로 호출되도록 cleanup()을 등록합니다.
std::atexit(cleanup); // 참고: 지금 당장 cleanup() 함수를 실행하는 것이 아니기 때문에 괄호를 빼고 'cleanup'이라고만 적습니다.
std::cout << 1 << '\n';
std::exit(0); // 프로그램을 종료하고 운영체제에 상태 코드 0을 반환합니다.
// 아래의 명령문들은 절대 실행되지 않습니다.
std::cout << 2 << '\n';
return 0;
}
cleanup() 함수를 인자로 넘겨줄 때 괄호를 붙인 cleanup()(이것은 함수를 즉시 호출해 버립니다)이 아니라, 함수의 이름인 cleanup만 사용했다는 점입니다.std::atexit()와 이 정리 함수에 대해 몇 가지 알아둘 점이 있습니다.
main()함수가 끝날 때 암시적으로std::exit()가 호출되므로,return으로 프로그램이 끝나는 경우에도std::atexit()로 등록한 함수들이 똑같이 실행됩니다.
- 등록되는 함수는 매개변수가 없어야 하고, 반환값(Return value)도 없어야(void) 합니다.
- 원한다면
std::atexit()를 여러 번 써서 여러 개의 정리 함수를 등록할 수도 있어요.- 이때 함수들은 등록된 역순(Reverse order)으로 호출됩니다. (즉, 가장 마지막에 등록된 것이 제일 먼저 실행돼요.)
std::exit()를 호출하면 프로그램이 강제 종료될 수 있습니다.std::exit()를 호출한 스레드가 다른 스레드에서 여전히 사용 중일지 모르는 정적 객체들을 파괴해 버리기 때문이에요.std::exit() 및 std::atexit()와 비슷하게 작동하는 std::quick_exit()와 std::at_quick_exit()라는 또 다른 함수 쌍을 도입했습니다.std::quick_exit()는 프로그램을 정상적으로 종료하긴 하지만, 정적 객체들을 정리하지 않으며, 다른 종류의 정리 작업도 수행할지 안 할지 보장하지 않습니다. std::at_quick_exit()는 std::quick_exit()로 종료되는 프로그램에서 std::atexit()와 같은 역할을 수행합니다.std::abort() 함수는 프로그램을 비정상 종료(Abnormal termination)시킵니다. 0으로 나누려고 시도하면 비정상 종료가 일어납니다. std::abort()는 어떠한 정리 작업도 하지 않는다는 것입니다.#include <cstdlib> // std::abort()를 사용하기 위해 포함
#include <iostream>
int main()
{
std::cout << 1 << '\n';
std::abort();
// 아래의 명령문들은 절대 실행되지 않습니다.
std::cout << 2 << '\n';
return 0;
}
std::terminate() 함수는 일반적으로 예외(Exceptions)와 함께 사용됩니다. (예외에 대해서는 이후 챕터에서 자세히 다룰게요.) std::terminate를 명시적으로 호출할 수도 있지만, 대개는 발생한 예외가 제대로 처리(Handled)되지 않았을 때(그리고 기타 몇 가지 예외 관련 상황에서) 암시적으로 호출됩니다. std::terminate()는 std::abort()를 호출한답니다.
#include <iostream>
// 설명의 목적으로만 작성된 코드입니다. 실제로는 사용하지 마세요.
unsigned int LCG16() // 우리의 PRNG 함수
{
static unsigned int s_state{ 0 }; // 이 함수가 처음 호출될 때 한 번만 초기화됩니다.
// 다음 숫자를 생성합니다.
// 누군가가 시퀀스의 다음 숫자가 무엇일지 쉽게 예측하지 못하도록
// 큰 상수와 의도적인 오버플로우(overflow)를 사용하여 상태를 수정합니다.
s_state = 8253729 * s_state + 2396403; // 먼저 상태를 수정합니다.
return s_state % 32768; // 그런 다음 새로운 상태를 사용하여 시퀀스의 다음 숫자를 반환합니다.
}
int main()
{
// 100개의 난수를 출력합니다.
for (int count{ 1 }; count <= 100; ++count)
{
std::cout << LCG16() << '\t';
// 숫자를 10개 출력했다면, 새로운 줄로 넘어갑니다.
if (count % 10 == 0)
std::cout << '\n';
}
return 0;
}
이 프로그램의 결과는 다음과 같습니다.
8397 18528 17747 9126 28505 13420 32479 23218 21477 30328
20075 26558 20081 3716 13303 19146 24317 31888 12163 982
1417 16540 16655 4834 16917 23208 26779 30702 5281 19124
9767 13050 32045 4288 31155 17414 31673 11468 25407 11026
4165 7896 25291 26654 15057 26340 30807 31530 31581 1264
9187 25654 20969 30972 25967 9026 15989 17160 15611 14414
16641 25364 10887 9050 22925 22816 11795 25702 2073 9516
PRNG는 이 LCG16()과 비슷하게 작동해요. PRNG에 의해 생성된 "난수" 시퀀스는 사실 전혀 무작위가 아닙니다.LCG16() 역시 결정론적(Deterministic)이거든요. 0)이 주어지면, PRNG는 매번 똑같은 숫자 시퀀스를 생성해 냅니다. PRNG의 초기 상태를 설정하는 데 사용되는 값을 랜덤 시드(Random seed) 또는 줄여서 시드(Seed)라고 부릅니다. PRNG의 초기 상태를 설정했을 때, 우리는 이것을 "시드가 설정되었다(seeded)"라고 표현해요.#include <iostream>
unsigned int g_state{ 0 };
void seedPRNG(unsigned int seed)
{
g_state = seed;
}
// 설명의 목적으로만 작성된 코드입니다. 실제로는 사용하지 마세요.
unsigned int LCG16() // 우리의 PRNG 함수
{
// 누군가가 시퀀스의 다음 숫자가 무엇일지 쉽게 예측하지 못하도록
// 큰 상수와 의도적인 오버플로우를 사용하여 상태를 수정합니다.
g_state = 8253729 * g_state + 2396403; // 먼저 상태를 수정합니다.
return g_state % 32768; // 그런 다음 새로운 상태를 사용하여 시퀀스의 다음 숫자를 반환합니다.
}
void print10()
{
// 10개의 난수를 출력합니다.
for (int count{ 1 }; count <= 10; ++count)
{
std::cout << LCG16() << '\t';
}
std::cout << '\n';
}
int main()
{
unsigned int x {};
std::cout << "Enter a seed value(시드 값을 입력하세요): ";
std::cin >> x;
seedPRNG(x); // 우리의 PRNG에 시드를 설정합니다.
print10(); // 10개의 난수 값을 생성합니다.
return 0;
}
다음은 이 프로그램을 3번 실행해 본 결과입니다.
Enter a seed value: 7
10458 3853 16032 17299 10726 32153 19116 7455 242 549
Enter a seed value: 7
10458 3853 16032 17299 10726 32153 19116 7455 242 549
Enter a seed value: 9876
24071 18138 27917 23712 8595 18406 23449 26796 31519 7922
PRNG를 사용할 수는 없어요. PRNG가 생성할 수 있는 고유한 시퀀스의 이론적 최대 개수는 PRNG가 가진 상태의 비트 수에 의해 결정됩니다.128비트 상태를 가진 PRNG는 이론적으로 최대 2^128개의 고유한 출력 시퀀스를 생성할 수 있습니다.PRNG가 실제로 생성할 수 있는 고유한 출력 시퀀스의 수는 프로그램이 제공할 수 있는 고유한 시드 값의 개수에 의해 제한됩니다. PRNG에 충분한 비트의 양질의 시드 데이터가 제공되지 않는 경우, 우리는 이를 "시드가 부족하다(Underseeded)"라고 말합니다. PRNG는 품질이 어딘가 손상된 무작위 결과를 생성하기 시작할 수 있으며, 시드 부족 현상이 심할수록 결과의 품질은 더욱 나빠지게 됩니다.PRNG는 다음과 같은 문제를 보일 수 있어요.N번째 난수를 생성할 때, 특정 값은 아예 생성되지 않을 수 있습니다.7이나 13을 절대 생성하지 못합니다.<random> 헤더를 통해 접근할 수 있습니다. | 타입 이름 (Type name) | 제품군 (Family) | 주기 (Period) | 상태 크기 (State size) | 성능 (Performance) | 품질 (Quality) | 사용해야 할까요? |
|---|---|---|---|---|---|---|
| minstd_rand | 선형 합동 생성기 (Linear congruential generator) | 2³¹ | 4 bytes | 나쁨 (Bad) | 끔찍함 (Awful) | 아니요 (No) |
| minstd_rand0 | 선형 합동 생성기 (Linear congruential generator) | 2³¹ | 4 bytes | 나쁨 (Bad) | 끔찍함 (Awful) | 아니요 (No) |
| mt19937 | 메르센 트위스터 (Mersenne twister) | 2¹⁹⁹³⁷ | 2500 bytes | 무난함 (Decent) | 무난함 (Decent) | 아마도요 (다음 섹션 참고) |
| mt19937_64 | 메르센 트위스터 (Mersenne twister) | 2¹⁹⁹³⁷ | 2500 bytes | 무난함 (Decent) | 무난함 (Decent) | 아마도요 (다음 섹션 참고) |
| ranlux24 | 빼고 자리올림 (Subtract and carry) | 10¹⁷¹ | 96 bytes | 끔찍함 (Awful) | 좋음 (Good) | 아니요 (No) |
| ranlux48 | 빼고 자리올림 (Subtract and carry) | 10¹⁷¹ | 96 bytes | 끔찍함 (Awful) | 좋음 (Good) | 아니요 (No) |
| knuth_b | 셔플된 선형 합동 생성기 (Shuffled linear congruential generator) | 2³¹ | 1028 bytes | 끔찍함 (Awful) | 나쁨 (Bad) | 아니요 (No) |
| default_random_engine | 위 중 하나 (구현에 따라 다름) | 다양함 | 다양함 | ?? | ?? | 아니요 (No) |
| rand() | 선형 합동 생성기 (Linear congruential generator) | 2³¹ | 4 bytes | 나쁨 (Bad) | 끔찍함 (Awful) | 아니요 (No) |
knuth_b default_random_engine 또는 rand() (C 언어와의 호환성을 위해 제공되는 난수 생성기)를 사용할 이유는 전혀 없습니다.PRNG 기준으로 볼 때, 메르센 트위스터는 약간 구식이라는 점을 기억해 두시면 좋습니다. 624개의 생성된 숫자를 보고 나면 다음 결과를 예측할 수 있다는 점입니다. Xoshiro 제품군과 WyrandChacha 제품군PRNG는 이름이 멋질 뿐만 아니라, 아마도 모든 프로그래밍 언어를 통틀어 가장 인기 있는 PRNG일 것입니다. 오늘날의 기준으로는 조금 오래된 방식이긴 하지만, 일반적으로 훌륭한 결과물과 준수한 성능을 보여줍니다. mt19937 32비트 부호 없는 정수를 생성하는 메르센 트위스터입니다.mt19937_64 64비트 부호 없는 정수를 생성하는 메르센 트위스터입니다.#include <iostream>
#include <random> // std::mt19937을 사용하기 위해 포함합니다
int main()
{
std::mt19937 mt{}; // 32비트 메르센 트위스터 객체를 생성합니다
// 난수를 여러 개 출력해 봅니다
for (int count{ 1 }; count <= 40; ++count)
{
std::cout << mt() << '\t'; // 난수를 하나 생성합니다
// 5개의 숫자를 출력했다면, 새로운 줄로 넘어갑니다
if (count % 5 == 0)
std::cout << '\n';
}
return 0;
}
<random> 헤더를 포함했습니다. std::mt19937 mt라는 구문을 통해 32비트 메르센 트위스터 엔진을 생성했죠. mt()를 호출했습니다.32비트 PRNG는 0부터 4,294,967,295 사이의 난수를 생성하지만, 우리가 항상 이처럼 큰 범위의 숫자를 원하는 것은 아닙니다.1부터 6 사이의 난수를 생성하여 6면체 주사위 굴리기를 흉내 내고 싶을 것입니다. 7에서 11 사이의 피해를 주는 검을 가지고 있다면, 플레이어가 몬스터를 때릴 때마다 7에서 11 사이의 난수를 생성해야겠죠.PRNG 자체는 이런 기능을 할 수 없습니다. 오직 전체 범위의 숫자만 생성할 수 있죠. PRNG에서 출력된 숫자를 우리가 원하는 더 작은 범위의 값으로 변환해 주는 방법입니다.random 라이브러리에는 이를 도와주는 난수 분포(Random number distributions) 기능이 있습니다. PRNG의 출력을 다른 숫자 분포로 변환해 줍니다.random 라이브러리에는 여러 가지 난수 분포가 있지만, 통계 분석을 하지 않는 이상 대부분은 사용할 일이 없을 겁니다. X와 Y 사이에서 동일한 확률로 결과를 생성하는 난수 분포입니다.#include <iostream>
#include <random> // std::mt19937 및 std::uniform_int_distribution을 위해 포함합니다
int main()
{
std::mt19937 mt{};
// 1부터 6 사이의 숫자를 균등하게 생성하는 재사용 가능한 난수 생성기를 만듭니다
std::uniform_int_distribution die6{ 1, 6 }; // C++14의 경우, std::uniform_int_distribution<> die6{ 1, 6 }; 를 사용하세요
// 난수를 여러 개 출력해 봅니다
for (int count{ 1 }; count <= 40; ++count)
{
std::cout << die6(mt) << '\t'; // 여기서 주사위를 굴립니다
// 10개의 숫자를 출력했다면, 새로운 줄로 넘어갑니다
if (count % 10 == 0)
std::cout << '\n';
}
return 0;
}
이 코드는 다음과 같은 결과를 생성합니다:
3 1 3 6 5 2 6 6 1 2
2 6 1 1 6 1 4 5 2 5
6 2 6 2 1 3 5 4 5 6
1 4 2 3 1 2 2 6 2 1
1과 6 사이의 숫자를 생성하기 위해 균등 분포 변수 die6 를 만들었습니다. mt()를 호출하는 대신, 이제는 1과 6 사이의 값을 생성하기 위해 die6(mt)를 호출하고 있습니다.PRNG를 사용하여 그 시드로부터 고유한 유사 난수 시퀀스를 생성할 수 있습니다.std::time() 함수를 사용하여 PRNG의 시드를 설정해 왔으므로, 기존 코드에서 이런 방식을 많이 보실 수 있을 겁니다.<chrono> 라이브러리가 있습니다.#include <iostream>
#include <random> // std::mt19937을 위해 포함
#include <chrono> // std::chrono를 위해 포함
int main()
{
// steady_clock을 사용하여 메르센 트위스터의 시드를 설정합니다
std::mt19937 mt{ static_cast<std::mt19937::result_type>(
std::chrono::steady_clock::now().time_since_epoch().count()
) };
// 1부터 6 사이의 숫자를 균등하게 생성하는 재사용 가능한 난수 생성기
std::uniform_int_distribution die6{ 1, 6 };
// 난수 여러 개 출력
for (int count{ 1 }; count <= 40; ++count)
{
std::cout << die6(mt) << '\t'; // 여기서 주사위를 굴립니다
// 10개를 출력하면 줄바꿈
if (count % 10 == 0)
std::cout << '\n';
}
return 0;
}
<chrono>를 포함했습니다.