std::string: 스스로 관리되는 텍스트
이전 페이지에서는 스마트 포인터가 어떻게 객체가 리소스를 소유하고 자동으로 정리하게 해 주는지 살펴봤습니다. std::string은 바로 그 개념을 텍스트에 적용한 것입니다. 문자 버퍼를 소유하고, 무언가를 덧붙이면 늘리고, 문자열이 스코프를 벗어나면 해제합니다. new를 호출할 일도, 바이트를 셀 일도, 빠진 널 종료 문자를 걱정할 일도 없습니다.
사용하려면 <string>을 인클루드하세요. 이 클래스는 std 네임스페이스에 있습니다.
이 + 연산자는 실제로 일을 합니다. 두 조각을 담을 만큼 큰 새 버퍼를 할당하고 거기에 복사해 넣죠. 생 char*라면 strcpy와 strcat에 손을 뻗으며 버퍼가 충분히 크기를 바라야 합니다. std::string은 그 부류의 실수를 통째로 사라지게 합니다.
문자열 만들기와 합치기
+로 연결할 수 있지만, 문자열을 제자리에서 키우는 핵심 일꾼은 +=입니다. 매번 완전히 새 문자열을 만드는 대신 기존 버퍼에 덧붙이므로, 반복문 안에서는 이것이 중요합니다.
흔히 겪는 의외의 상황: +는 적어도 한쪽 피연산자가 이미 std::string이어야 합니다. 두 문자열 리터럴은 그저 const char*이므로 "a" + "b"는 컴파일되지 않습니다 - 두 생 포인터에 대한 operator+가 없기 때문입니다. 먼저 둘 중 하나를 문자열로 만드세요.
string s = "a" + "b"; // error: can't add two const char*
string s = string("a") + "b"; // fine - left side is a std::string
string s = "a"s + "b"; // fine in C++14+, the "s" literal suffix
마지막 형태는 <string>의 s 접미사(using namespace std::string_literals;)를 사용한다는 점에 주목하세요. 이것은 리터럴을 곧바로 std::string으로 바꿔 줍니다.
문자열 내부에 접근하기
std::string은 char의 컨테이너처럼 동작하므로, 인덱싱하고, 순회하고, 일부 조각을 요청할 수 있습니다.
substr(pos, len)은 원본에서 복사한 완전히 새로운 문자열을 반환하며, 원본을 수정하지 않습니다. 경계에 주의하세요. 5글자 문자열에서 word[10]은 **정의되지 않은 동작**입니다. 예외를 던지지 않고 그냥 쓰레기 값을 읽습니다. std::out_of_range를 던지는 검사된 접근을 원한다면 word[10] 대신 word.at(10)을 사용하세요.
또 하나의 고전적 함정: .size()는 부호 없는 타입(size_t)을 반환합니다. for (size_t i = word.size() - 1; i >= 0; --i) 같은 역방향 반복문은 결코 끝나지 않습니다. 부호 없는 i는 절대 0 아래로 내려갈 수 없어 한 바퀴 돌아 거대한 수가 되기 때문입니다. 문자열을 거꾸로 훑어야 할 때는 부호 있는 인덱스를 쓰거나 조건을 재구성하세요.
검색과 치환
find는 부분 문자열이나 문자를 찾아 시작 인덱스를 반환합니다. 대상이 없으면 특별한 상수 std::string::npos를 반환합니다. find가 -1을 반환한다고 가정하지 말고 항상 그것과 비교하세요.
텍스트를 제자리에서 바꾸려면, replace(pos, len, text)가 한 구간을 새 내용(길이가 달라도 됨)으로 교체하고, insert/erase가 조각을 추가하거나 제거합니다.
문자열과 숫자
텍스트와 숫자는 저절로 변환되지 않습니다 - "42"는 정수 42가 아니라 세 개의 문자입니다. 표준 라이브러리는 양방향 변환 함수를 제공합니다. 텍스트를 숫자로 파싱하려면 stoi, stod 등을, 반대 방향으로 가려면 to_string을 사용하세요.
여기 함정이 두 가지 있습니다. 첫째, stoi("abc")는 std::invalid_argument를 던지므로 신뢰할 수 없는 입력은 try/catch로 보호하세요. 둘째, to_string(3.99)는 3.990000 전체를 내놓습니다 - 정밀도와 서식을 제어해야 한다면, 그것은 문자열 스트림(string streams)의 일이며, 바로 그곳이 다음에 향할 곳입니다.
try {
int n = std::stoi(userInput);
} catch (const std::invalid_argument&) {
std::cout << "That wasn't a number.\n";
}
다음: 입력과 출력
지금까지 줄곧 cout으로 문자열을 출력해 왔지만, 그것을 사용자로부터 다시 읽어 들이는 데에는 나름의 의외성이 있습니다 - cin >> name은 첫 공백에서 멈추므로 "Ada Lovelace" 같은 한 줄 전체에는 대신 std::getline이 필요합니다. 다음 페이지는 C++의 입력과 출력을 제대로 다룹니다. 스트림 연산자, 줄 전체 읽기, 그리고 남은 개행에 걸려 넘어지지 않으면서 >>와 getline을 섞어 쓰는 법입니다.
자주 묻는 질문
C++에서 std::string과 char*의 차이는 무엇인가요?
std::string은 자신의 메모리를 소유하고 관리하며, 필요에 따라 늘어나고, 자동으로 정리되는 클래스입니다. char*는 끝에 '\0' 종료 문자가 붙은, 문자에 대한 생 포인터에 불과합니다. 버퍼, 길이, 수명을 모두 직접 관리해야 하죠. 거의 모든 경우에 std::string을 우선 사용하세요. 버퍼 오버플로와 댕글링 포인터 같은 버그 부류 전체를 없애 줍니다.
C++에서 문자열의 길이는 어떻게 구하나요?
std::string에서 .size() 또는 그 별칭인 .length()를 호출하세요. name.size()는 문자 수를 size_t로 반환합니다. 둘은 같은 값을 반환하지만, 다른 모든 STL 컨테이너와 일관되기 때문에 size()가 더 흔한 방식입니다.
C++에서 문자열을 숫자로 어떻게 변환하나요?
int에는 std::stoi, double에는 std::stod, 그리고 비슷한 stol, stof 등을 사용하세요. 예: int n = std::stoi("42");. 텍스트가 숫자가 아니면 이 함수들은 std::invalid_argument를 던지므로, 입력을 신뢰할 수 없을 때는 try/catch로 감싸세요.