C-style 문자열
C나 C++을 시작하게 되면, 대부분 이런 예문을 보게 됩니다.
#include <iostream>
int main(){
std::cout << "Hello, world!"; // C-style 문자열
return 0;
}
이때, 위의 "Hello, world!"가 C-style 문자열입니다.
이것을 정확하게 정의하자면,
C-style 문자열은 문자열 타입을 가진 리터럴( literal )이다
라고 할 수 있습니다.
여기서, 문자열 타입이란 문자( char ) 배열을 말합니다.
그리고, 리터럴( literal )은 값을 직접 나타내는 프로그램 요소입니다.
아래의 예제들을 통해 리터럴의 개념을 알 수 있습니다.
#include <math.h>
int main(){
int variable = 10; // integer literal
const int answer = 42; // integer literal
double d = sin(108.87); // floating point literal passed to sin function
bool b = true; // boolean literal
char c = 'A'; // char literal
}
첫 번째 줄의 10이 int 타입 리터럴이고, variable은 int 타입 변수입니다.
마찬가지로, 마지막 줄의 'A'가 char 타입 리터럴이고, c는 char 타입 변수입니다.
참고로, 가리키는 객체가 없다는 의미를 가진 nullptr 리터럴도 있습니다.
nullptr에 관한 내용은 여기에서 볼 수 있습니다.
그런데, 가끔 기존의 코드를 보면, 아래와 같은 문장을 만날 때가 있습니다.
#include <iostream>
#include <cstring>
int main(){
char str[] = "Apple3";
const char* pstr1 = str;
const char* pstr2 = "Apple4"; // ??
int ret = strcmp(pstr1, pstr2);
// ...
return 0;
}
여기서, 첫 번째 포인터의 초기화는 자연스럽습니다.
문자열 변수 str의 주소를 pstr1의 포인터에 대입하고 있으니까요.
( 배열명은 배열의 주소를 가리키고 있습니다. )
그런데, 두 번째 포인터의 초기화는 왜 컴파일러가 오류를 발생시키지 않았을까요?
만약, 다음과 같은 선언을 했을 때는 오류가 발생하는데 말입니다.
int A = 10;
int* pInt = &10; // int 타입 리터럴 10의 주소 대입. error !!
C-style 문자열의 두 가지 특징
첫 번째, C-style 문자열은 암시적인 null 종료 문자( null terminator, '\0' )를 갖고 있습니다.
C와 C++ 에선 이 종료 문자를 통해, 문자열이 종료되었다는 것을 알 수 있습니다.
그래서, 다음의 문자열의 크기는 13이 아니라, 14입니다.
#include <iostream>
#include <cstring>
using namespace std;
int main(){
//char str[13] = "Hello, world!"; // 배열 크기가 작으므로 오류 !!
char str[14] = "Hello, world!"; // null 종료 문자를 위해 공간이 필요
int len = strlen( str);
cout << "string length: " << len << endl;
return 0;
}
▼출력
string length: 13
위의 결과가 13으로 나온 이유는 strlen 함수가 '\0'을 제외한 문자의 개수를 반환하기 때문입니다.
하지만, 컴파일러는 배열 크기로 14를 사용해야 한다고 말하고 있습니다.
두 번째, C-style 문자열은 다른 종류의 리터럴( literal )과 달리, 프로그램의 시작부터 종료까지 존재하는 상수 객체입니다.
이것을 다른 말로 하면, 변수와 마찬가지로, 메모리 주소를 가진 객체라는 뜻이 됩니다.
그래서, 다음의 pstr2 선언이 문제를 발생하지 않은 것입니다.
#include <iostream>
using namespace std::string_literals; // for string 리터럴
using namespace std::string_view_literals; // for string_view 리터럴
int main(){
char str[] = "Apple3";
char* pstr1 = str;
pstr1[5] = '2'; // Apple2로 변경 가능
char* pstr2 = "Apple4"; // 상수 객체
//pstr2[5] = '1'; // 상수 객체의 값을 바꿀 수 없음. error !!
std::string cstr = "Apple5"s;
std::string_view cstrView = "Apples6"sv; // C++ 17 이후
}
하지만, pstr2를 char* 타입 포인터로 선언해, 컴파일이 되었다 하더라도, 상수 객체인 "Apple4" 문자열의 값을 변경할 수 없습니다.
따라서, C-style 문자열을 포인터를 통해 사용하고 싶으면, const char* 타입으로 선언하는 것이 안전합니다.
( 컴파일러 옵션에 따라, const char* 타입의 포인터만 사용해야 하는 경우도 있습니다. )
참고로, 아래의 "Apple5"s는 std::string 타입의 리터럴입니다.
그리고, "Apple6"sv는 C++17에서 도입된 std::string_view 타입의 리터럴입니다.
( 이때, 접미어인 s와 sv는 소문자를 사용해야 합니다. )
이 두 객체는 일시적인 객체로, C-style 문자열과 달리, 문장을 벗어나면 바로 파괴됩니다.
string_view에 관한 내용은 여기서 볼 수 있습니다.
'C, C++ > C++ 언어' 카테고리의 다른 글
[C++] 생성자와 소멸자에서의 예외( exception ) 처리 (0) | 2024.08.24 |
---|---|
[C++] 오류 처리를 위한 throw와 try, catch의 동작 방식 (0) | 2024.08.24 |
[C++] nullptr 리터럴( literal )과 NULL의 차이점 (0) | 2024.08.07 |
[C++] dynamic_cast를 통한 실시간 형 변형( type conversion ) (0) | 2024.08.06 |
[C++] 명시적인 C-style cast와 static_cast의 차이점 (0) | 2024.08.06 |