상수와 리터럴
const 상수와 정수·실수·문자·문자열 리터럴의 차이를 이해하고 값의 의미와 타입을 코드에 명확히 표현합니다.
이번 장에서는 이렇게 변하지 않는 값들을 좀 더 심도 있게 다루겠습니다.
바로 상수(Constants)와 리터럴(Literals)입니다.
상수와 리터럴은 프로그램 내에서 고정된 값을 표현하는 방식입니다.
이들을 정확히 이해하는 것은 코드의 가독성과 안정성을 높이는 데 매우 중요합니다.
상수(Constants)의 이해
상수는 이름, 타입, 범위를 함께 보며 값이 바뀌지 않아야 하는 의도를 확인합니다.
우리는 이미 const 키워드를 사용하여 상수를 선언하는 방법을 간략하게 살펴보았습니다.
이 절의 const 상수는 이름과 타입을 가진 객체이며, 초기화한 뒤 그 객체에 새 값을 대입할 수 없습니다. 초깃값을 실행 중 다른 변수나 함수 결과에서 얻는 경우도 있으므로, const라는 이유만으로 모든 값이 컴파일 시점에 결정된다고 가정하지 않습니다.
상수를 사용하는 주된 이유는 다음과 같습니다.
의미 부여 및 가독성 향상: 3.141592라는 숫자만 보면 그것이 원주율인지, 다른 어떤 값인지 알기 어렵습니다.
하지만 const double PI = 3.141592;라고 선언하면 PI라는 이름 자체가 그 숫자의 의미를 명확하게 전달하여 코드의 가독성을 높입니다.
오류 방지: 실수로 값을 변경하려는 시도를 컴파일 시점에서 방지할 수 있습니다.
예를 들어, 원주율 PI의 값을 실수로 변경하려 한다면 컴파일러가 오류를 발생시켜 알려줄 것입니다.
이는 프로그램의 안정성을 높이는 데 기여합니다.
유지보수 용이성: 만약 프로그램의 여러 곳에서 동일한 고정 값을 사용하고 있다면, 이 값을 상수로 선언해두고 사용하는 것이 좋습니다.
나중에 이 값을 변경해야 할 때, 상수의 정의만 한 번 변경하면 모든 사용처에 자동으로 반영되므로 유지보수가 매우 편리해집니다.
-
const키워드: 가장 일반적이고 권장되는 방법입니다.변수 선언 시 타입 앞에
const를 붙여 상수로 만듭니다.아래 기본 타입 const 객체는 정의할 때 초깃값이 필요합니다.
const 상수 선언 예시 const double PI = 3.1415926535; // 원주율 const int MAX_USERS = 100; // 최대 사용자 수 const char NEWLINE = '\n'; // 개행 문자const를 사용하면 컴파일러가 해당 값이 변경되지 않음을 알기 때문에 최적화를 수행하는 데도 도움이 될 수 있습니다. -
#define전처리기 지시자 (레거시 방식):#define은 C++보다는 C 언어에서 상수를 정의할 때 많이 사용되던 방식입니다.이는 전처리 단계에서 매크로 이름에 해당하는 토큰을 치환합니다. 같은 글자가 든 문자열 안까지 찾아 바꾸는 문자열 검색은 아닙니다.
#define 매크로 예시 #define PI_MACRO 3.1415926535 // 세미콜론을 붙이지 않습니다! #define MAX_BUFFER_SIZE 1024const와#define의 차이점- 타입 안전성:
const는 타입 정보를 가지므로 컴파일러가 타입 체크를 수행하여 오류를 방지할 수 있습니다. 매크로 이름 자체에는 C++ 타입이 없지만, 치환된 표현식은 이후 컴파일에서 타입 검사를 받습니다. - 디버깅:
const상수는 디버거에서 심볼 정보로 확인 가능하지만,#define매크로는 컴파일 전에 사라지므로 디버깅이 어렵습니다. - 스코프:
const상수는 변수와 동일하게 스코프(유효 범위)를 가질 수 있어 특정 블록이나 함수 내에서만 유효하게 만들 수 있습니다. 매크로는 C++ 블록 범위를 따르지 않고#undef되거나 전처리 번역 단위가 끝날 때까지 적용됩니다. - 메모리 사용:
const상수는 실제 메모리 공간을 차지할 수 있지만, 컴파일러 최적화에 따라 레지스터에 저장되거나 직접 값이 삽입되어 메모리를 차지하지 않을 수도 있습니다. 매크로 이름 자체는 객체가 아니지만, 치환 결과가 생성하는 객체나 데이터는 메모리를 사용할 수 있습니다. 이러한 이유로 현대 C++에서는#define을 사용하여 상수를 정의하는 대신const키워드를 사용하는 것을 강력히 권장합니다.
- 타입 안전성:
-
enum(열거형):enum은 주로 관련된 정수 값들을 의미 있는 이름으로 묶을 때 사용됩니다.상수의 집합을 정의하는 데 유용합니다.
enum 예시 enum Color { RED, GREEN, BLUE }; // RED는 0, GREEN은 1, BLUE는 2의 값을 가짐 (기본적으로 0부터 시작) enum State { IDLE = 0, RUNNING = 1, PAUSED = 2, STOPPED = 3 }; // 특정 값 지정 가능 // 사용 예시 Color myColor = RED; if (myColor == RED) { // ... }enum에 대해서는 나중에 더 자세히 다룰 기회가 있을 것입니다.
리터럴(Literals)의 이해
리터럴은 소스 코드에 직접적으로 표현되는 고정된 값 자체를 의미합니다.
소스에 10이나 "Hi"처럼 직접 쓴 표기입니다. 이 표기는 변수의 초깃값으로도 사용할 수 있습니다. int score = 10;에서는 score가 객체의 이름이고 10이 리터럴입니다.
리터럴은 특정 데이터 타입을 가집니다.
리터럴의 종류- 십진수: 우리가 가장 흔히 사용하는 형태입니다. (예:
10,123,5).-5는 정수 리터럴5에 단항-연산자를 적용한 식입니다 - 이진수 (Binary): C++14부터
0b또는0B접두사를 사용하여 이진수를 표현할 수 있습니다. (예:0b1010은 십진수 10) - 팔진수 (Octal):
0접두사를 사용하여 팔진수를 표현합니다. (예:012는 십진수 10) - 십육진수 (Hexadecimal):
0x또는0X접두사를 사용하여 십육진수를 표현합니다. (예:0xA는 십진수 10)
타입 접미사: 정수 리터럴 뒤에 특정 접미사를 붙여 타입을 명시할 수 있습니다.
U또는u:unsigned(부호 없는) 정수. (예:100U)L또는l:long정수. (예:100L)LL또는ll:long long정수. (예:100LL)UL,ull등 조합도 가능합니다.
int dec = 10;
int bin = 0b1010; // C++14
int oct = 012;
int hex = 0xA;
long long veryBig = 123456789012345LL;
unsigned int flags = 0xFFU; // 십육진수 255 (unsigned int)부동 소수점 리터럴 (Floating-Point Literals):
소수점이나 지수 표기(e 또는 E)를 포함한 숫자입니다.
기본적으로 double 타입으로 간주됩니다.
F또는f:float타입. (예:3.14F)L또는l:long double타입. (예:3.14L)
double dval = 3.14;
float fval = 3.14F;
long double ldval = 3.14L;
double sci_notation = 6.02e23; // 6.02 * 10^23문자 리터럴 (Character Literals)
작은따옴표('')로 묶인 단일 문자입니다.
기본적으로 char 타입입니다.
L:wchar_t(와이드 문자). (예:L'가')u:char16_t(UTF-16 문자). (예:u'글')U:char32_t(UTF-32 문자). (예:U'자')
이스케이프 시퀀스 (Escape Sequences): 특정 특수 문자는 백슬래시(\)와 함께 조합하여 표현합니다.
\n: 줄 바꿈 (개행)\t: 탭\\: 백슬래시 자체\': 작은따옴표\": 큰따옴표\0: 널 문자 (문자열의 끝을 나타내는 데 사용)
char ch = 'A';
char newline = '\n'; // 줄 바꿈 문자
char backslash = '\\'; // 백슬래시 문자문자열 리터럴 (String Literals)
큰따옴표("")로 묶인 일련의 문자 시퀀스입니다.
일반 문자열 리터럴 "Hi"는 끝의 널 문자를 포함한 const char[3] 배열입니다. H, i, \0의 세 코드 단위를 담으며, std::string 객체와는 별개입니다.
L:wchar_t문자열. (예:L"안녕하세요")u:char16_t문자열. (예:u"Hi")U:char32_t문자열. (예:U"Hello")R: 원시 문자열 (Raw String Literal). 이스케이프 시퀀스를 해석하지 않고 문자 그대로를 사용합니다. 파일 경로 등을 표현할 때 유용합니다. (예:R"(C:\new\folder)"는C:\new\folder그대로 해석)
const char* message = "Hello, C++!"; // C-스타일 문자열
std::string name = "나 혼자 C++"; // C++ 표준 라이브러리의 string 타입
// 원시 문자열 리터럴
const char* path = R"(C:\Users\Admin\Documents\my_file.txt)";
std::cout << path << std::endl; // 출력: C:\Users\Admin\Documents\my_file.txtstd::string은 C++ 표준 라이브러리에서 제공하는 문자열 클래스로, C-스타일 문자열보다 유연하고 안전한 문자열 처리를 제공합니다.
이에 대해서는 나중에 별도의 장에서 자세히 다룰 것입니다.
논리 리터럴 (Boolean Literals) 논리 값을 직접 표현하는 리터럴은 두 가지뿐입니다.
true: 참false: 거짓
bool isComplete = true;
bool isValid = false;nullptr: C++11부터 도입된 포인터 리터럴입니다. 아무것도 가리키지 않는 널 포인터를 명시적으로 나타냅니다. C에서는NULL이나0을 사용하기도 했지만,nullptr은 타입 안전성을 제공하여 더 바람직합니다.
int* ptr = nullptr; // 아무것도 가리키지 않는 정수형 포인터리터럴 상수 (Literal Constants)
리터럴은 그 자체로 상수입니다.
10, 3.14, 'A', "Hello"와 같은 값들은 프로그램 내에서 고정된 값을 가지며 변경할 수 없습니다.
이들은 이름이 없는 상수라고 볼 수 있습니다.
우리가 변수를 선언하고 const 키워드로 상수를 만드는 것은 이렇게 이름 없는 리터럴에 의미 있는 이름을 부여하여 코드의 가독성과 유지보수성을 높이기 위함입니다.
#include <iostream>
#include <string> // std::string을 사용하기 위해 포함
int main() {
// 상수 선언 (이름 있는 상수)
const int PROGRAM_VERSION = 2;
const double DISCOUNT_RATE = 0.15; // 15% 할인
const std::string WELCOME_MESSAGE = "환영합니다!";
// 리터럴 직접 사용 (이름 없는 상수)
int score = 95; // 95는 정수 리터럴
double tax = score * 0.05; // 0.05는 부동 소수점 리터럴
std::cout << WELCOME_MESSAGE << std::endl;
std::cout << "프로그램 버전: " << PROGRAM_VERSION << std::endl;
std::cout << "할인율: " << DISCOUNT_RATE * 100 << "%" << std::endl;
std::cout << "원래 점수: " << score << std::endl;
std::cout << "세금: " << tax << std::endl;
// 문자 리터럴과 이스케이프 시퀀스
char tabChar = '\t';
std::cout << "이것은" << tabChar << "탭 문자입니다." << std::endl;
std::cout << "큰따옴표(\")와 백슬래시(\\)를 사용할 수 있습니다." << std::endl;
return 0;
}앞에서 다룬 const, 매크로, 열거형의 이름·타입·범위 차이를 비교합니다.
C++ · NAMED VALUES
이름 있는 객체·전처리 치환·열거형은 역할이 다르다
이름을 붙인다는 공통점보다 타입, 범위, 검증 책임을 기준으로 선택합니다.
타입과 범위가 필요한 값
const int MAX_LIVES = 3;const 객체는 타입과 C++ 범위를 가집니다. 초기값은 실행 중 계산된 값일 수도 있어, 항상 컴파일 시점 상수를 뜻하지 않습니다.
전처리 치환 이름
#define MAX_BUFFER_SIZE 1024
사용 전에 텍스트를 치환합니다. C++ 이름 범위를 따르지 않으며, 치환된 C++ 표현식에는 타입 검사가 적용됩니다.
관련 상태에 이름 붙이기
enum Color {
RED, GREEN, BLUE
};열거형과 열거자 RED=0, GREEN=1, BLUE=2를 선언합니다. 외부 입력이 유효한 상태인지는 따로 검증합니다.
객체의 불변성에는 const, 관련 상태 집합에는 enum을 검토합니다. 전처리 치환을 C++의 타입·범위를 가진 객체처럼 취급하지 않습니다.
다음 그림은 리터럴 중 정수, 부동소수점, 문자·문자열, nullptr의 표기 기준을 묶습니다. 논리 리터럴 true와 false는 앞의 예제처럼 bool 값을 나타냅니다.
C++ · LITERAL TYPES
리터럴의 표기가 타입과 값 표현을 정한다
정수·실수·문자·문자열·논리·null 포인터를 실제 표기와 함께 구분합니다.
정수
10 → int · 10u → unsigned int
진법·접미사에 해당하는 후보 중 값을 표현하는 첫 타입을 선택합니다. 모든 정수 리터럴이 int인 것은 아닙니다.
부동 소수점
3.14 → double · 3.14f → float
L 접미사는 long double입니다. 리터럴의 타입과 저장할 객체 타입은 별도로 확인합니다.
문자
'A' → char · u'A' → char16_t
prefix가 문자 타입과 인코딩을 정합니다. 여기서는 한 코드 단위로 표현되는 문자 예시를 비교합니다.
문자열
"Hi" → const char[3]
끝의 null 문자를 포함한 배열이며 정적 저장 기간을 가집니다. raw 표기는 이스케이프 해석 방식을 바꾸며 std::string 객체를 만들지는 않습니다.
논리
true · false → bool
조건 상태를 표현하는 두 리터럴입니다. 정수 1·0으로 변환할 수 있다는 사실과 고유한 타입을 구분합니다.
null 포인터
nullptr → std::nullptr_t
포인터 타입 자체가 아닌 고유한 타입입니다. 포인터로 변환하면 해당 타입의 null 포인터 값을 만듭니다.
-5는 리터럴 5에 단항 -를 적용한 식입니다. 문자·문자열 prefix와 정수의 진법·접미사·범위를 함께 확인합니다.
예시 같은 글자가 보여도 문자와 문자열은 크기와 타입이 다릅니다. 다음 완전한 프로그램은 문자열 리터럴의 끝 문자까지 포함해 크기를 확인합니다.
#include <iostream>
int main() {
std::cout << sizeof('A') << ' ' << sizeof("A") << ' '
<< sizeof("Hi") << '\n';
}출력은 1 2 3입니다. C++의 'A'는 char 한 칸이고, "A"와 "Hi"는 끝의 \0까지 포함한 배열입니다. 여기서 크기의 단위는 C++ 바이트입니다. 출력되는 글자의 개수와 저장 공간의 크기를 같은 값으로 읽지 마세요. 이 예의 기본 문자와 달리 한글 등은 인코딩에 따라 여러 코드 단위를 사용할 수 있습니다.
표기를 먼저 판단한다.
012와 12의 값은 같은가? sizeof("")는 0인가 1인가? 3.14F를 double 변수에 넣으면 처음부터 double 리터럴을 사용한 것과 항상 같은 정밀도인가? 앞의 접두사·접미사·끝 문자를 근거로 답한 뒤 확인합니다.
표기별 근거 확인
012는 8진수이므로 십진수 10이고 12와 다릅니다. 빈 문자열도 끝의 널 문자 한 칸이 있어 크기는 1입니다. 3.14F는 먼저 float 값이 되므로 이후 double에 저장해도 그때 잃은 정밀도가 복원되지 않습니다. 실제 저장 표현은 구현에 따라 확인하지만 접미사가 정한 최초 타입은 구분해야 합니다.
문자열 배열과 끝 문자, raw 표기의 규칙은 C++ 작업 초안의 문자열 리터럴 규칙을 참고할 수 있습니다. 이 절은 현재 예시에 필요한 규칙만 사용하며 모든 리터럴 접미사나 인코딩 조합을 다루지는 않습니다.