문자 리터럴(Character literal)
문자 리터럴(Character literal)
프로그램에서 문자 하나를 표현할 때 쓰는 것이 **문자 리터럴(character literal)**이에요. 리터럴 앞에 붙는 접두사에 따라 문자를 인코딩하는 방식이 달라져요. 여기서는 그 문법과 각 접두사별 동작을 살펴볼게요.
출처: cppreference
본문
문법
'c-char' |
(1) | |
u8'c-char' |
(2) | (C++17부터) |
u'c-char' |
(3) | (C++11부터) |
U'c-char' |
(4) | (C++11부터) |
L'c-char' |
(5) | |
'c-char-sequence' |
(6) | |
L'c-char-sequence' |
(7) | (C++23 이전) |
| 자리 | 의미 |
|---|---|
| c-char | basic-c-char이거나, 이스케이프 시퀀스에서 정의한 이스케이프 시퀀스이거나, 이스케이프 시퀀스에서 정의한 유니버설 문자 이름(universal character name) |
| basic-c-char | 기본 소스 문자 집합(C++23 이전)·번역 문자 집합(C++23부터)의 문자. 단 작은따옴표 ', 백슬래시 \, 줄바꿈(new-line) 문자는 제외 |
| c-char-sequence | 두 개 이상의 c-char |
인코딩할 수 없는 문자(Non-encodable characters)
c-char-sequence의 어떤 c-char도 넓은 문자 리터럴 인코딩(wide literal encoding)에서 단일 코드 유닛으로 인코딩될 수 없으면, 프로그램은 잘못된 형태(ill-formed)예요. (C++23 이전)
숫자 이스케이프 시퀀스(Numeric escape sequences)
문자의 값을 지정할 때는 숫자(8진수, 16진수) 이스케이프 시퀀스를 쓸 수 있어요.
문자 리터럴에 숫자 이스케이프 시퀀스가 하나만 있고, 그 이스케이프 시퀀스가 지정하는 값이 그 타입의 부호 없는 버전으로 표현 가능하면, 문자 리터럴은 (문자 타입으로 변환된 뒤) 지정된 값과 같은 값을 가져요. UTF-N 문자 리터럴은 타입으로 표현 가능한 어떤 값이라도 가질 수 있어요. 값이 유효한 Unicode 코드 포인트에 대응하지 않거나, 대응하는 코드 포인트가 UTF-N에서 단일 코드 유닛으로 표현되지 않더라도, 그 값을 가진 숫자 이스케이프 시퀀스로 지정할 수 있어요. 예를 들어 u8'\xff'는 잘 정의되고 char8_t(0xFF)와 같아요. (C++23부터)
일반 또는 넓은 문자 리터럴에 사용된 숫자 이스케이프 시퀀스가 지정하는 값이 각각 char 또는 wchar_t로 표현 불가능하면, 문자 리터럴의 값은 구현 정의예요. (C++23 이전)
c-char가 하나인 일반 또는 넓은 문자 리터럴에 사용된 숫자 이스케이프 시퀀스가 지정하는 값이, 각각 char 또는 wchar_t의 기반 타입 부호 없는 버전으로 표현 가능하면, 리터럴의 값은 그 부호 없는 정수 타입의 정수 값이고 지정된 값을 리터럴의 타입으로 변환한 것이에요. 그렇지 않으면 프로그램은 잘못된 형태예요. (C++23부터)
UTF-N 문자 리터럴에 사용된 숫자 이스케이프 시퀀스가 지정하는 값이 대응하는 char*N*_t로 표현 불가능하면, 문자 리터럴의 값은 구현 정의(C++17 이전)이고 프로그램은 잘못된 형태(C++17부터)예요. (C++11부터)
주의할 점(Notes)
다중 문자 리터럴(multicharacter literal)은 C가 B 프로그래밍 언어로부터 물려받았어요. C나 C++ 표준이 명시하지는 않지만, 대부분의 컴파일러(MSVC는 주목할 만한 예외)는 B에서 명시한 대로 다중 문자 리터럴을 구현해요. 즉, 리터럴의 각 char 값이 결과 정수의 연속된 바이트를 빅엔디언, 0으로 패딩된, 오른쪽 정렬 순서로 초기화해요. 예를 들어 '\1'의 값은 0x00000001이고 '\1\2\3\4'의 값은 0x01020304예요.
C에서는 'a'나 '\n' 같은 문자 상수가 char가 아니라 int 타입이에요.
예제
각 접두사가 문자를 몇 개의 코드 유닛으로 인코딩하는지 확인해 볼게요. 참고로 u8(UTF-8) 리터럴의 주석 처리가 된 줄들은, 그 문자들이 UTF-8에서 2~4개 코드 유닛으로 인코딩되기 때문에 char8_t 하나에 담을 수 없어서 주석 처리된 거예요.
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string_view>
template<typename CharT>
void dump(std::string_view s, const CharT c)
{
const uint8_t* data{reinterpret_cast<const uint8_t*>(&c)};
std::cout << s << " \t" << std::hex
<< std::uppercase << std::setfill('0');
for (auto i{0U}; i != sizeof(CharT); ++i)
std::cout << std::setw(2) << static_cast<unsigned>(data[i]) << ' ';
std::cout << '\n';
}
void print(std::string_view str = "") { std::cout << str << '\n'; }
int main()
{
print("Ordinary character literals:");
char c1 = 'a'; dump("'a'", c1);
char c2 = '\x2a'; dump("'*'", c2);
print("\n" "Ordinary multi-character literals:");
int mc1 = 'ab'; dump("'ab'", mc1); // 구현 정의
int mc2 = 'abc'; dump("'abc'", mc2); // 구현 정의
print("\n" "UTF-8 character literals:");
char8_t C1 = u8'a'; dump("u8'a'", C1);
// char8_t C2 = u8'¢'; dump("u8'¢'", C2); // error: ¢는 UTF-8 코드 유닛 2개로 매핑
// char8_t C3 = u8'猫'; dump("u8'猫'", C3); // error: 猫는 UTF-8 코드 유닛 3개로 매핑
// char8_t C4 = u8'🍌'; dump("u8'🍌'", C4); // error: 🍌는 UTF-8 코드 유닛 4개로 매핑
print("\n" "UTF-16 character literals:");
char16_t uc1 = u'a'; dump("u'a'", uc1);
char16_t uc2 = u'¢'; dump("u'¢'", uc2);
char16_t uc3 = u'猫'; dump("u'猫'", uc3);
// char16_t uc4 = u'🍌'; dump("u'🍌'", uc4); // error: 🍌는 UTF-16 코드 유닛 2개로 매핑
print("\n" "UTF-32 character literals:");
char32_t Uc1 = U'a'; dump("U'a'", Uc1);
char32_t Uc2 = U'¢'; dump("U'¢'", Uc2);
char32_t Uc3 = U'猫'; dump("U'猫'", Uc3);
char32_t Uc4 = U'🍌'; dump("U'🍌'", Uc4);
print("\n" "Wide character literals:");
wchar_t wc1 = L'a'; dump("L'a'", wc1);
wchar_t wc2 = L'¢'; dump("L'¢'", wc2);
wchar_t wc3 = L'猫'; dump("L'猫'", wc3);
wchar_t wc4 = L'🍌'; dump("L'🍌'", wc4); // C++23부터 Windows에서 지원 안 함
}
가능한 출력:
Ordinary character literals:
'a' 61
'*' 2A
Ordinary multi-character literals:
'ab' 62 61 00 00
'abc' 63 62 61 00
UTF-8 character literals:
u8'a' 61
UTF-16 character literals:
u'a' 61 00
u'¢' A2 00
u'猫' 2B 73
UTF-32 character literals:
U'a' 61 00 00 00
U'¢' A2 00 00 00
U'猫' 2B 73 00 00
U'🍌' 4C F3 01 00
Wide character literals:
L'a' 61 00 00 00
L'¢' A2 00 00 00
L'猫' 2B 73 00 00
L'🍌' 4C F3 01 00
결함 보고(Defect reports)
이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 912 | C++98 | 인코딩할 수 없는 일반 문자 리터럴이 명시되지 않았음 | 조건부 지원(conditionally-supported)으로 명시 |
| CWG 1024 | C++98 | 다중 문자 리터럴을 지원해야 했음 | 조건부 지원으로 만듦 |
| CWG 1656 | C++98 | 문자 리터럴의 숫자 이스케이프 시퀀스 의미가 불명확했음 | 명시됨 |
| P1854R4 | C++98 | 인코딩할 수 없는 문자 리터럴이 조건부 지원이었음 | 프로그램이 잘못된 형태 |
더 알아보기
- 사용자 정의 리터럴(user-defined literals, C++11): 사용자 정의 접미사를 가진 리터럴.
- 이스케이프 시퀀스와 유니버설 문자 이름: 특수 문자와 값을 지정하는 방법.