
이름 있는 상수 (Named constants)
- 식별자와 연결된 상수 값입니다.
- 때때로 심볼릭 상수(symbolic constants)라고도 불립니다.
리터럴 상수 (Literal constants)
식별자와 연결되지 않은 상수 값입니다.
const 키워드를 붙이면 됩니다.const 키워드를 사용하여 상수로 만들 수 있습니다.const 로 만들지 않습니다. const 객체를 반환하는 것은 별 의미가 없습니다. 어차피 파괴될 임시 복사본이기 때문입니다. const 값을 반환하면 이동 시맨틱과 관련된 특정 컴파일러 최적화를 방해하여 성능 저하를 일으킬 수 있습니다.const 변수는 값이 변하지 않는다는 것을 알기 때문에 값이 실제로 변하는지, 어떤 값으로 변하는지, 그 값이 올바른지 걱정할 필요가 없습니다.#include <iostream>
#define MY_NAME "Alex"
int main()
{
std::cout << "My name is: " << MY_NAME << '\n';
return 0;
}
MY_NAME을 Alex로 대체합니다.MY_NAME은 이름이고 대체 텍스트는 리터럴 상수의 문자열 리터럴이므로, 이는 이름 있는 상수의 한 형태가 됩니다.적어도 세 가지의 문제가 있습니다.
#defined 되면, 해당 파일의 나머지 부분에서 나오는 모든 매크로 이름이 치환됩니다.const 키워드가 바꾸는 것은 수정 가능 여부 입니다.const를 const 타입 한정자(const type qualifier) 라고 합니다.const volatilevolatile 한정자는 객체의 값이 언제든지(컴파일러가 예측할 수 없는 방식으로) 변경될 수 있음을 컴파일러에게 알리는 데 사용됩니다.const와 volatile 한정자를 종종 cv-qualifiers(cv-한정자)라고 부릅니다.
| 데이터 타입 | 접미사 | 의미 |
|---|---|---|
| 정수형 | u 또는 U | unsigned int |
| 정수형 | l 또는 L | long |
| 정수형 | ul, uL, Ul, UL, lu, lU, Lu, LU | unsigned long |
| 정수형 | ll 또는 LL | long long |
| 정수형 | ull, uLL, Ull, ULL, llu, llU, LLu, LLU | unsigned long long |
| 정수형 | z 또는 Z | std::size_t의 부호 있는(signed) 버전 (C++23) |
| 정수형 | uz, uZ, Uz, UZ, zu, zU, Zu, ZU | std::size_t (C++23) |
| 부동 소수점 | f 또는 F | float |
| 부동 소수점 | l 또는 L | long double |
| 문자열 | s | std::string |
| 문자열 | sv | std::string_view |
C 문자열또는 C 스타일 문자열이라고 불립니다.C 스타일 문자열 리터럴은 프로그램 시작 시 생성되어 프로그램 전체 실행 기간 동안 존재함이 보장되는 const 객체입니다.std::string과 std::string_view 리터럴은 임시 객체를 생성합니다.
0을 접두사로 붙여야 합니다.int x{ 012 };
int x{ 0xF };
| 16진 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2진수 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
0011 1010 0111 1111 1001 1000 0010 0110을 가진 32비트 정수를 생각해 봅시다.3A7F 9826으로 표현할 수 있으며, 이는 훨씬 간결합니다.#include <iostream>
int main()
{
int bin{}; // 16비트 int라고 가정
bin = 0x0001; // 변수에 2진수 0000 0000 0000 0001 할당
bin = 0x0002; // 변수에 2진수 0000 0000 0000 0010 할당
bin = 0x0004; // 변수에 2진수 0000 0000 0000 0100 할당
bin = 0x0008; // 변수에 2진수 0000 0000 0000 1000 할당
bin = 0x0010; // 변수에 2진수 0000 0000 0001 0000 할당
bin = 0x0020; // 변수에 2진수 0000 0000 0010 0000 할당
bin = 0x0040; // 변수에 2진수 0000 0000 0100 0000 할당
bin = 0x0080; // 변수에 2진수 0000 0000 1000 0000 할당
bin = 0x00FF; // 변수에 2진수 0000 0000 1111 1111 할당
bin = 0x00B3; // 변수에 2진수 0000 0000 1011 0011 할당
bin = 0xF770; // 변수에 2진수 1111 0111 0111 0000 할당
return 0;
}
0b 접두사를 사용하여 직접 2진수 리터럴을 사용할 수 있습니다.#include <iostream>
int main()
{
int bin{}; // 16비트 int라고 가정
bin = 0b1; // 변수에 2진수 0000 0000 0000 0001 할당
bin = 0b11; // 변수에 2진수 0000 0000 0000 0011 할당
bin = 0b1010; // 변수에 2진수 0000 0000 0000 1010 할당
bin = 0b11110000; // 변수에 2진수 0000 0000 1111 0000 할당
return 0;
}
' 를 자릿수 구분자로 사용하는 기능이 추가되었습니다.int bin { 0b1011'0010 };
long value { 2'132'673'462 };
std::dec std::oct std::hex 입출력 조정자를 사용하여 출력 형식을 변경할 수 있습니다.std::dec 10진수std::oct 8진수std::hex 16진수#include <iostream>
int main()
{
int x{ 12 };
std::cout << x << '\n'; // 10진수 (기본값)
std::cout << std::hex << x << '\n'; // 16진수
std::cout << x << '\n'; // 이제 16진수로 출력됨
std::cout << std::oct << x << '\n'; // 8진수
std::cout << std::dec << x << '\n'; // 다시 10진수로 복귀
std::cout << x << '\n'; // 10진수
return 0;
}
실행 결과:
12
c
c
14
12
12
std::cout에는 2진수 출력 기능이 내장되어 있지 않기 때문에, 2진수로 값을 출력하는 것은 조금 더 어렵습니다.std::bitset이라는 타입이 있습니다.std::bitset을 사용하려면 std::bitset 변수를 정의하고 저장할 비트 수를 알려주어야 합니다.컴파일 시간 상수(compile-time constant)
프로그램을 실행하기 전, 즉 컴파일(번역)하는 순간에 값이 이미 확정되어 있는 상수를 의미합니다.
std::bitset은 정수형 값(10진수, 8진수, 16진수, 2진수 등 모든 형식 포함)으로 초기화할 수 있습니다.
#include <bitset> // std::bitset 사용을 위해
#include <iostream>
int main()
{
// std::bitset<8>은 8비트를 저장하겠다는 의미입니다
std::bitset<8> bin1{ 0b1100'0101 }; // 2진수 1100 0101에 대한 2진수 리터럴
std::bitset<8> bin2{ 0xC5 }; // 2진수 1100 0101에 대한 16진수 리터럴
std::cout << bin1 << '\n' << bin2 << '\n';
std::cout << std::bitset<4>{ 0b1010 } << '\n'; // 임시 std::bitset을 생성하고 출력
return 0;
}
실행 결과:
11000101
11000101
1010
// 위 코드에서 다음 줄은:
std::cout << std::bitset<4>{ 0b1010 } << '\n';
// 임시 std::bitset 객체를 생성해서 바로 출력하는 코드
#include <format> // C++20#include <print> // C++23#include <format> // C++20
#include <iostream>
#include <print> // C++23
int main()
{
std::cout << std::format("{:b}\n", 0b1010); // C++20, {:b}는 인자를 2진수로 출력
std::cout << std::format("{:#b}\n", 0b1010); // C++20, {:#b}는 인자를 0b 접두사 포함 2진수로 출력
std::println("{:b} {:#b}", 0b1010, 0b1010); // C++23, 두 인자를 포맷팅하여 출력
return 0;
}
실행 결과:
1010
0b1010
1010 0b1010

- 어떤 종류의 최적화는 일반적으로 수작업으로 이루어집니다.
- 프로파일러(profiler)라고 불리는 프로그램을 사용하여 프로그램의 다양한 부분이 실행되는 데 걸리는 시간을 확인하고, 어떤 부분이 전체 성능에 영향을 미치는지 파악할 수 있습니다.
- 그러면 프로그래머는 이러한 성능 문제를 완화할 방법을 찾을 수 있습니다.
- 수동 최적화는 시간이 오래 걸리기 때문에, 프로그래머들은 보통 큰 영향을 미치는 고수준의 개선(더 성능이 좋은 알고리즘 선택, 데이터 저장 및 접근 방식 최적화, 리소스 사용량 감소, 작업 병렬화 등)에 집중합니다.
- 다른 종류의 최적화는 자동으로 수행될 수 있습니다.
- 다른 프로그램을 최적화하는 프로그램을 최적화기(optimizer, 옵티마이저)라고 합니다.
- 현대 C++ 컴파일러들은 최적화 컴파일러(optimizing compiler)입니다.
- 즉, 컴파일 과정의 일부로 프로그램을 자동으로 최적화할 수 있는 능력이 있습니다.
- 전처리기와 마찬가지로, 이러한 최적화는 소스 코드 파일을 직접 수정하는 것이 아니라, 컴파일 과정의 일부로서 투명하게 적용됩니다.
as-if 규칙은 컴파일러가 프로그램의 관찰 가능한 동작(observable behavior)에 영향을 주지 않는 한, 더 최적화된 코드를 생성하기 위해 프로그램을 원하는 대로 수정할 수 있다는 규칙입니다.#include <iostream>
int main()
{
std::cout << 3 + 4 << '\n';
return 0;
}
3 + 4는 전체 표현식 std::cout << 3 + 4 << '\n';의 부분 표현식입니다. std::cout << 7 << '\n';으로 최적화할 수 있습니다.컴파일 시간 상수(compile-time constant)런타임 상수(runtime constant)컴파일 시간 상수(compile-time constant)
- 컴파일 시간에 그 값을 알 수 있는 상수입니다.
리터럴 (Literals)초기화 식(initializer)이 컴파일 시간 상수인 상수 객체
런타임 상수(runtime constant)
- 런타임 컨텍스트에서 값이 결정되는 상수입니다.
상수 함수 매개변수초기화 식이 비상수이거나 런타임 상수인 상수 객체
#include <iostream>
int five()
{
return 5;
}
int pass(const int x) // x는 런타임 상수입니다
{
return x;
}
int main()
{
// 다음은 상수가 아닙니다(non-constants):
[[maybe_unused]] int a{ 5 };
// 다음은 컴파일 시간 상수입니다:
[[maybe_unused]] const int b{ 5 };
[[maybe_unused]] const double c{ 1.2 };
[[maybe_unused]] const int d{ b }; // b는 컴파일 시간 상수입니다
// 다음은 런타임 상수입니다:
[[maybe_unused]] const int e{ a }; // a는 비-상수(non-const)입니다
[[maybe_unused]] const int f{ e }; // e는 런타임 상수입니다
[[maybe_unused]] const int g{ five() }; // 반환 값은 런타임이 될 때까지 알 수 없습니다
[[maybe_unused]] const int h{ pass(5) };// 반환 값은 런타임이 될 때까지 알 수 없습니다
return 0;
}
as-if 규칙에 따라 최적화 목적을 위해 컴파일 시간에 평가될 수 있습니다.상수 표현식
컴파일러가 코드를 번역할 때 "아, 이건 굳이 나중에 실행할 때까지 기다릴 필요 없이 내가 지금 바로 계산해서 결과값으로 덮어써도 되겠다"라고 판단할 수 있는 것들입니다.
- 리터럴 (
5,1.2) 값 자체가 고정되어 있으니 당연히 미리 알 수 있습니다.- 리터럴끼리의 연산 (
3 + 4) 컴파일러가 미리 계산해서 그냥 7로 바꿔버립니다. 프로그램 실행 속도가 미세하게 빨라지겠죠.constexpr변수/함수 프로그래머가 아예 컴파일러에게 "이건 무조건 컴파일할 때 미리 계산해 놔!"라고 도장을 찍어둔 것입니다.const int x { 5 }; C++의 오래된 규칙 때문에, 정수형(int 등)에 한해서만 const를 상수 표현식으로 특별 대우해 줍니다.
비상수 / 런타임 표현식
컴파일러가 코드를 보면서 "음... 이건 프로그램이 실제로 켜지고 돌아가 봐야 값을 알 수 있겠군. 지금은 계산 포기!"라고 선언하는 것들입니다.
- 일반 변수 (
int x = 5;) 언제 코드가 개입해서 값을 바꿀지 모르기 때문에 컴파일러가 섣불리 미리 계산할 수 없습니다.std::cout << "hello"화면에 글자를 띄우는 행위는 프로그램이 '실행'되어 모니터와 운영체제와 소통해야만 일어날 수 있는 일입니다.new,delete메모리를 동적으로 빌리고 반납하는 것은 프로그램이 실행 중에 운영체제에 요청해야 하는 작업입니다const double d { 1.2 };(const 비정수형) 앞서 말한const int는 봐주면서const double은 안 봐주는 것은 순전히 과거 C++의 역사적인 잔재(관습) 때문입니다. 실수형이나 다른 타입은 복잡성 때문에 과거 컴파일러들이 미리 계산하는 걸 지원하지 않았기 때문입니다.
const 처음에 값을 한 번 넣었으면, 프로그램이 끝날 때까지 절대 못 바꾼다! 눈으로 보기만 해라!" (값이 언제 정해졌든 상관없이, 일단 정해지면 자물쇠를 채워버립니다.)상수 표현식(constant expression) 컴파일러가 코드를 쓱 보고 '아, 이건 굳이 실행 안 해봐도 답이 뭔지 딱 알지' 하고 미리 계산해 둘 수 있는 수식.constexpr 컴파일러야, 프로그램 실행될 때까지 미루지 말고, 무조건 지금 당장(컴파일할 때) 계산해서 완벽한 고정값(상수 표현식)으로 박아버려!| 구분 | 의미 | 값이 컴파일 타임에 확정? | 대표 용도 |
|---|---|---|---|
| const | 수정 금지(불변) | 아닐 수도 있고, 맞을 수도 있음 | API 안정성, const-correctness |
| 상수 표현식 | 컴파일 타임에 계산 가능한 “식” | 예 | 배열 크기, 템플릿 인자, case, static_assert |
| constexpr | 상수 표현식이 될 수 있게 강제/보장 | 예 (변수는 강제) | 컴파일 타임 계산, 성능/안전성, 템플릿 |
- const를 사용하는 것만으로는 해당 변수가 상수 표현식에서 사용 가능한지 즉각적으로 알기 어렵습니다.
const int d { someVar }; // d가 상수 표현식에 사용 가능한지 불분명함 const int e { getValue() }; // e가 상수 표현식에 사용 가능한지 불분명함
- const는 컴파일러에게 "이 변수는 반드시 상수 표현식에서 사용 가능해야 한다"고 알리는(그리고 그렇지 않을 경우 컴파일을 중단시키는) 방법을 제공하지 않습니다. 대신, 조건이 맞지 않으면 조용히 런타임 표현식에서만 쓸 수 있는 변수를 생성해 버립니다.
ㅤ- 컴파일 타임 상수 변수를 만들기 위해 const를 사용하는 것은 정수형이 아닌 변수에는 적용되지 않습니다. 하지만 정수형이 아닌 변수도 컴파일 타임 상수로 만들고 싶을 때가 많습니다.
constexpr 변수는 항상 컴파일 타임 상수입니다.const 객체의 값이 초기화 이후에 변경될 수 없음을 의미합니다.constexpr 객체가 상수 표현식에서 사용될 수 있음을 의미합니다.constexpr 변수는 암시적으로 const입니다.const 변수는 암시적으로 constexpr이 아닙니다const와 달리 constexpr은 객체의 타입에 포함되지 않습니다.constexpr int로 정의된 변수의 실제 타입은 const int입니다constexpr이 객체에 암시적으로 const를 부여하기 때문입니다.모범 사례 (Best practice)
- 초기값이 상수 표현식인 모든 상수 변수는
constexpr로 선언하십시오.- 초기값이 상수 표현식이 아닌(즉, 런타임 상수인) 모든 상수 변수는
const로 선언하십시오.
| 용어 (Term) | 정의 (한국어) |
|---|---|
| 컴파일 타임 상수 (Compile-time constant) | 컴파일 타임에 값이 반드시 알려져야 하는 값 또는 수정 불가능한 객체 (예: 리터럴, constexpr 변수) |
| constexpr (Constexpr) | 객체를 컴파일 타임 상수로 선언하는 키워드(그리고 컴파일 타임에 평가될 수 있는 함수를 선언). 비공식적으로는 “상수 표현식(constant expression)”의 약어처럼 쓰이기도 함 |
| 상수 표현식 (Constant expression) | 컴파일 타임 상수와 컴파일 타임 평가를 지원하는 연산자/함수만 포함하는 표현식(즉, 컴파일 타임에 계산 가능한 식) |
| 런타임 표현식 (Runtime expression) | 상수 표현식이 아닌 표현식(즉, 실행 중에 계산되는 식) |
| 런타임 상수 (Runtime constant) | 값이 바뀌지 않는(수정 불가능한) 값/객체이지만, 컴파일 타임 상수는 아닌 것(값이 런타임에 확정되는 상수) |
constexpr 함수는 입력값과 사용되는 위치가 완벽한 상수 조건을 갖추면 컴파일할 때 미리 계산되고, 조건이 하나라도 어긋나서 런타임 변수가 섞여 들어오면 일반 함수처럼 유연하게 런타임에 실행되는 함수입니다.무조건 컴파일 타임에 실행되는 경우 (사전 계산)
- 다음 두 가지 조건이 모두 맞으면, 컴파일러는 무조건 실행 전에 계산을 끝내버립니다.
- 함수에 넘겨주는 재료(인자)들이 전부 컴파일 타임에 알 수 있는 값(상수 표현식)이어야 합니다.
- 함수의 결과값이 반드시 컴파일 타임에 필요한 곳(예: constexpr 변수에 값을 넣을 때, 배열의 크기를 정할 때)에 쓰여야 합니다.
일반 함수처럼 런타임에 실행되는 경우 (실시간 계산)
- 함수에 넘겨주는 재료(인자)가 런타임에 변하는 일반 변수이거나, 굳이 컴파일 타임에 계산을 강제하지 않는 일반적인 상황에서 쓰이면, 이 함수는 평범한 일반 함수처럼 프로그램 실행 중에(런타임에) 작동합니다.
#include <iostream>
int max(int x, int y) // 이것은 non-constexpr 함수입니다.
{
if (x > y)
return x;
else
return y;
}
constexpr int cmax(int x, int y) // 이것은 constexpr 함수입니다.
{
if (x > y)
return x;
else
return y;
}
int main()
{
int m1{ max(5, 6) }; // 가능
const int m2{ max(5, 6) }; // 가능
constexpr int m3{ max(5, 6) }; // 컴파일 에러: max(5, 6)은 상수 표현식이 아님
int m4{ cmax(5, 6) }; // 가능: 컴파일 타임 또는 런타임에 평가될 수 있음
const int m5{ cmax(5, 6) }; // 가능: 컴파일 타임 또는 런타임에 평가될 수 있음
constexpr int m6{ cmax(5, 6) }; // 가능: 반드시 컴파일 타임에 평가되어야 함
return 0;
}

std::getline()은 두 개의 인자가 필요합니다. std::cin 문자열 변수int main()
{
std::cout << "Enter your full name: ";
std::string name{};
std::getline(std::cin >> std::ws, name); // 텍스트 한 줄 전체를 name으로 읽음
std::cout << "Enter your favorite color: ";
std::string color{};
std::getline(std::cin >> std::ws, color); // 텍스트 한 줄 전체를 color로 읽음
std::cout << "Your name is " << name << " and your favorite color is " << color << '\n';
return 0;
}
std::ws입니다.std::string에 문자가 몇 개 있는지 알고 싶다면, std::string 객체에 길이를 물어볼 수 있습니다.int main()
{
std::string name{ "Alex" };
std::cout << name << " has " << name.length() << " characters\n";
return 0;
}
length(name)과 같이 묻는 대신 name.length()라고 쓴다는 점을 주목하세요.std::string::length()는 unsigned 전용 타입(size_t)을 반환하므로, 이를 부호가 있는 일반 int 변수에 그냥 대입하면 타입 불일치로 인해 컴파일러 경고가 발생합니다.static_cast<int>()를 사용하여 "내가 의도한 변환이다"라고 명시적으로 타입을 바꿔주는 것입니다.int length { static_cast<int>(name.length()) };
std::ssize() 함수를 사용하여 std::string의 길이를 크기가 큰 부호 있는 정수형으로 얻을 수 있습니다.std::cout << name << " has " << std::ssize(name) << " characters\n";
std::string이 초기화될 때마다, 초기화에 사용된 문자열의 복사본이 만들어집니다.std::string이 함수에 값으로 전달(passed by value)되면, std::string 함수 매개변수는 인자로 인스턴스화되고 초기화되어야 합니다. 이는 비용이 많이 드는 복사를 초래합니다.s 접미사를 사용하여 std::string 타입의 문자열 리터럴을 만들 수 있습니다.s는 반드시 소문자여야 합니다.int main()
{
using namespace std::string_literals; // s 접미사에 쉽게 접근하기 위해
std::cout << "foo\n"; // 접미사가 없으면 C 스타일 문자열 리터럴
std::cout << "goo\n"s; // s 접미사가 있으면 std::string 리터럴
return 0;
}
std::string의 초기화(또는 복사) 비용이 비싸다는 문제를 해결하기 위해, C++17에서는 std::string_view를 도입했습니다.std::string_view는 <string_view> 헤더에 존재 합니다.std::string_view는 기존 문자열에 대해 복사본을 만들지 않고 읽기 전용(read-only) 접근을 제공합니다.""으로 감싼 문자열 리터럴은 기본적으로 C 스타일 문자열 리터럴입니다."" 문자열 리터럴 뒤에 sv 접미사를 사용하여 std::string_view 타입의 리터럴을 만들 수 있습니다.sv는 반드시 소문자여야 합니다.int main()
{
using namespace std::string_literals; // s 접미사 접근용
using namespace std::string_view_literals; // sv 접미사 접근용
std::cout << "foo\n"; // 접미사가 없으면 C 스타일 문자열 리터럴
std::cout << "goo\n"s; // s 접미사는 std::string 리터럴
std::cout << "moo\n"sv; // sv 접미사는 std::string_view 리터럴
return 0;
}