본문 바로가기

std::string 클래스

std::string의 값·길이·용량 계약과 연산자·멤버 함수·C API 경계를 익힙니다.

앞 글의 C 스타일 문자열은 배열의 용량, 끝의 널 문자, 복사할 곳의 남은 공간을 코드를 쓰는 사람이 직접 맞춰야 했습니다. 표준 라이브러리의 std::string은 이 일을 객체가 맡아, 글자를 덧붙이면 필요한 만큼 저장 공간을 늘리고 길이도 스스로 기억합니다.

이 글은 std::string이 관리하는 길이와 용량, 만드는 방법, 대입과 연결과 비교, 인덱스 접근, 검색과 편집, 한 줄 입력을 받는 std::getline, C API에 넘길 때의 주의점을 봅니다. 사용하려면 <string> 헤더를 포함합니다.

std::string이 문자 시퀀스를 값으로 소유하면서 size, capacity, 연속 저장, 끝 널 문자를 관리하고 축소와 포인터 무효화 경계를 갖는다는 객체 모델

C++ · STRING OBJECT CONTRACT

문자열의 값·길이·용량과 관찰자 수명은 별개다

std::string은 문자 시퀀스를 값으로 소유하고 길이와 저장 공간을 관리합니다. 현재 문자 수, 재할당 여유, 끝 널 문자는 서로 다른 계약입니다.

std::string이 자신의 값과 길이를 소유하는 논리 모델부모 문자열 소유 영역 안에 공개 상태 영역과 연속 문자 범위 영역이 있다. Ada의 size는3이며 capacity는3이상으로만 표시하고 구체적인 구현 용량은 고정하지 않는다. 문자범위에는 A,d,a와 data()[3]의끝널이있고 끝널은size에 포함되지 않는다. 이는 공개계약의 논리적 포함 관계이며 실제 객체 필드,버퍼가 객체 안이나 밖에 놓이는 내부저장전략 또는 작은문자열최적화를 단정하지 않는다.std::string name = "Ada"공개 상태size() = 3capacity() >= 3연속 문자 · 끝 널0A1d2a3\0객체의 논리 계약을 표시실제 내부 구조·주소 배치 아님
std::string이 자신의 값과 길이를 소유하는 논리 모델부모 문자열 소유 영역 안에 공개 상태 영역과 연속 문자 범위 영역이 있다. Ada의 size는3이며 capacity는3이상으로만 표시하고 구체적인 구현 용량은 고정하지 않는다. 문자범위에는 A,d,a와 data()[3]의끝널이있고 끝널은size에 포함되지 않는다. 이는 공개계약의 논리적 포함 관계이며 실제 객체 필드,버퍼가 객체 안이나 밖에 놓이는 내부저장전략 또는 작은문자열최적화를 단정하지 않는다.std::string name = "Ada"공개 상태size() = 3capacity() >= 3연속 문자 · 끝 널0A1d2a3\0객체의 논리 계약을 표시실제 내부 구조·주소는구현을 따름
값 · value

문자 시퀀스를 객체가 소유한다

복사하면 별도의 문자열 값을 얻습니다. C++11 이후 문자들은 연속 저장되지만 내부 표현과 작은 문자열 최적화는 구현 세부사항입니다.

길이 · size

현재 문자 개수를 센다

size()와 length()는 같은 값을 반환합니다. 끝 널 문자는 이 개수에 포함되지 않습니다.

용량 · capacity

재할당 전 저장 여유를 나타낸다

항상 현재 길이 이상이지만 길이와 같을 필요는 없습니다. 객체가 짧아져도 자동 축소를 보장하지 않습니다.

연속 범위 뒤에 끝 널 문자가 있다

std::string name = "Ada";

name.size()        // 3
name.capacity()    // 3 이상
name.data()[3]     // '\0'

일반 문자 인덱스는 0부터 2까지입니다. 현행 draft에서 3은 끝 널 위치이며, 그 값을 char() 이외로 바꾸면 undefined behavior입니다.

길이 감소와 용량 감소는 별개다

name.reserve(80);
name.clear();

// size() == 0
// capacity()는 0일 필요 없음
// 비구속 요청
name.shrink_to_fit();

현행 draft와 C++20 이후 reserve(n)은 더 작은 용량으로 줄이지 않습니다. 현행 shrink_to_fit()은 용량을 늘리지 않는 비구속 요청이며, 실제 재할당하면 모든 원소 관찰자가 무효화됩니다.

내장 널도 값의 일부

객체 길이와 C 문자열 길이가 다를 수 있다

const char bytes[]{
    'A', '\0', 'B'
};
std::string packet(bytes, 3);

packet.size()  // 3

널 종료만 아는 C 함수는 첫 널에서 멈춥니다. 포인터와 길이를 함께 받는 API인지 확인합니다.

주소를 장기 보관하지 않는다

변경 연산은 기존 관찰자를 무효화할 수 있다

대입·추가·삽입·삭제·교체와 비상수 표준 라이브러리 호출 뒤에는 원소 포인터, 참조와 반복자의 유효성을 다시 판단합니다.

값의 소유권은 객체에 있고, 내부 주소의 수명은 별도 계약입니다. 현행 draft에서 shrink_to_fit()이 재할당하지 않으면 기존 관찰자는 그대로 유효합니다.

읽는 순서: 소유한 문자 값 → 현재 size() → 재할당 여유 capacity() → 끝 널과 관찰자 수명. 구현의 저장 전략은 추측하지 않습니다.

길이, 용량, 끝의 널 문자

std::string은 개수가 바뀔 수 있는 char의 나열을 값으로 소유합니다. 위 그림처럼 std::string name = "Ada";에는 세 가지를 구별해야 합니다.

size()와 length()는 지금 들어 있는 글자 수인 3을 돌려주며, 두 함수는 같은 값입니다. capacity()는 저장 공간을 다시 할당하지 않고 담을 수 있는 글자 수로, 언제나 size() 이상이지만 정확한 값은 구현이 정합니다. 글자는 메모리에 이어서 저장되고 마지막 글자 뒤인 data()[size()]에는 널 문자가 있지만, 이 널 문자는 size()에 들어가지 않습니다.

여기서 글자 수는 char의 개수입니다. 영문자는 한 글자가 char 하나이지만, GCC가 쓰는 UTF-8에서 한글 한 글자는 char 세 개이므로 std::string("한").size()는 3입니다. 이 글의 길이와 인덱스는 모두 char 단위로 계산합니다.

용량은 길이를 따라 자동으로 줄지 않습니다. clear()나 erase()로 글자를 지우면 size()는 줄지만 capacity()는 그대로일 수 있습니다. 앞으로 들어올 글자 수를 안다면 reserve(n)으로 미리 공간을 확보해 재할당 횟수를 줄일 수 있고, C++20부터 reserve는 용량을 늘리기만 합니다. 줄이고 싶을 때 쓰는 shrink_to_fit()은 요청일 뿐이어서 실제로 줄어든다는 보장이 없습니다.

더 깊이: 짧은 문자열은 객체 안에 담깁니다

주요 표준 라이브러리는 짧은 문자열을 힙에 할당하지 않고 std::string 객체 안의 버퍼에 바로 담으며, 이를 짧은 문자열 최적화(SSO, small string optimization)라고 부릅니다. GCC의 libstdc++는 객체 안에 char 16개짜리 버퍼를 두어 널 문자를 뺀 15글자까지 담고, Microsoft의 MSVC STL도 char 기준으로 15글자까지를 객체 안에 둡니다. 그래서 두 구현에서는 빈 문자열의 capacity()가 15이고, 그보다 긴 내용을 담을 때 처음으로 메모리를 할당합니다. 이 수치는 표준이 정한 것이 아니므로 코드가 기대서는 안 되지만, 짧은 문자열을 만들고 복사하는 데 할당이 필요 없다는 점은 성능을 볼 때 알아 둘 만합니다.

예전 libstdc++의 std::string은 다른 방식을 썼습니다. 복사할 때 버퍼를 함께 쓰다가 한쪽을 바꿀 때에야 실제로 복사하는 쓸 때 복사(COW, copy-on-write)입니다. C++11 표준이 이런 문자열을 허용하지 않게 되자 libstdc++는 GCC 5.1에서 std::string과 std::list의 구현을 바꾼 새 ABI를 도입했고, 두 구현을 _GLIBCXX_USE_CXX11_ABI 매크로로 고를 수 있게 했습니다. libstdc++의 이중 ABI 문서에 따르면, 링크 오류에 std::__cxx11이나 [abi:cxx11]이 들어간 이름이 보이면 대개 이 매크로를 서로 다른 값으로 컴파일한 목적 파일이 섞인 것입니다.

만들기

std::string은 빈 값, 문자열 리터럴, 다른 문자열, 같은 글자의 반복, 다른 문자열의 일부로 만들 수 있습니다. 아래 다섯 줄이 그 순서이며, 줄 끝의 주석은 각 변수에 들어가는 글자를 보여 줍니다. 괄호 안에 수를 넘기는 생성자는 인자의 순서가 뜻을 정하므로, s4의 (5, 'A')는 글자 수가 먼저이고 글자가 나중입니다.

string_construct.cpp
#include <string>

std::string s1;               // ""
std::string s2 = "Hello C++"; // 리터럴의 글자를 복사
std::string s3 = s2;          // 값 복사
std::string s4(5, 'A');       // "AAAAA"
std::string s5(s2, 6, 3);     // 6번 위치부터 3글자, "C++"

s3는 s2와 같은 글자를 가진 별도의 값이어서, 나중에 s3를 바꿔도 s2는 바뀌지 않습니다. s5는 "Hello C++"에서 인덱스 6의 C부터 세 글자를 복사합니다.

const char* 하나를 받는 생성자는 첫 널 문자 앞까지만 복사합니다. 데이터 안에 널 문자가 들어 있거나 길이를 따로 알고 있다면 길이를 함께 넘기는 생성자를 씁니다.

string_from_bytes.cpp
#include <cstring>
#include <iostream>
#include <string>

int main() {
    const char raw[]{'A', '\0', 'B', '\0'};
    std::string packet(raw, 3);
    std::string copied = packet;
    packet.at(2) = 'C';

    std::cout << packet.size() << ' ' << std::strlen(packet.c_str())
              << ' ' << copied.at(2) << ' ' << packet.at(2) << '\n';
    std::cout << std::boolalpha << (packet.capacity() >= packet.size()) << '\n';
}
3 1 B C
true

packet은 A, 널 문자, B의 세 글자를 소유하므로 size()는 3입니다. 하지만 널 문자로 끝을 찾는 strlen은 A 다음의 널 문자에서 멈춰 1을 돌려줍니다. copied는 독립된 값이어서 packet의 마지막 글자를 C로 바꿔도 B를 유지하며, capacity()의 구체적인 값은 구현마다 다르므로 size() 이상인지만 출력했습니다.

대입, 연결, 비교

std::string은 기본 타입처럼 =로 대입하고, +로 이어 붙인 새 값을 만들고, +=로 자기 뒤에 덧붙이며, ==와 <로 비교합니다. 아래 코드는 +로 만든 sentence와 +=로 바꾼 greeting을 따로 출력하므로, 새 값을 만드는 연산과 원래 값을 바꾸는 연산을 출력에서 구별할 수 있습니다. 첫 줄의 sentence.size()는 이어 붙인 결과의 글자 수입니다.

string_operators.cpp
#include <iostream>
#include <string>

int main() {
    std::string greeting = "Hello";
    std::string target = "World";

    std::string sentence = greeting + ", " + target + "!";
    greeting += " everyone";

    std::cout << sentence << ' ' << sentence.size() << '\n';
    std::cout << greeting << '\n';
    std::cout << std::boolalpha << (target == "World") << ' '
              << (std::string("apple") < std::string("banana")) << '\n';
}
Hello, World! 13
Hello everyone
true true

==는 C 스타일 문자열의 포인터와 달리 주소가 아니라 글자의 나열을 비교하므로, target == "World"는 참입니다. <는 앞 글자부터 차례로 비교해 a가 b보다 앞서므로 참이며, compare() 멤버 함수도 같은 순서로 음수, 0, 양수를 돌려줍니다. 이 순서는 각 글자를 unsigned char로 바꾼 값의 순서이므로 대문자가 소문자보다 앞서고, 한국어 사전순이나 대소문자를 무시하는 정렬과는 다릅니다.

+를 쓸 때는 피연산자 중 하나가 std::string이어야 합니다. "Hello" + " World"는 두 문자열 리터럴, 즉 두 포인터를 더하려는 식이어서 컴파일 오류입니다. 연산자 우선순위에서 본 대로 +는 왼쪽부터 묶이므로, greeting + ", " + target은 (greeting + ", ") + target이 되어 매 단계에 std::string이 있습니다.

인덱스와 at()

글자는 []나 at()으로 인덱스를 주어 읽고 씁니다. 올바른 인덱스는 0부터 size() - 1까지입니다.

string_index.cpp
std::string word = "Example";

word[0] = 'e';       // 인덱스가 올바르다는 것을 아는 경우
char c = word.at(2); // 범위를 검사한 뒤 'a'

[]는 배열처럼 범위를 검사하지 않으며, size()보다 큰 인덱스는 정의되지 않은 동작입니다. word[word.size()]는 끝의 널 문자를 가리키지만 다른 값으로 바꾸면 안 되므로, 글자를 다룰 때는 i < size()를 지킵니다. at(i)는 i가 size() 이상이면 std::out_of_range 예외를 던지므로, 사용자 입력처럼 믿을 수 없는 인덱스에는 at()을 쓰거나 미리 검사합니다. 첫 글자와 마지막 글자를 돌려주는 front()와 back()은 문자열이 비어 있지 않을 때만 씁니다.

검색과 편집

멤버 함수는 상태를 알려 주는 것, 새 값을 돌려주는 것, 원래 문자열을 바꾸는 것으로 나누어 읽으면 헷갈리지 않습니다.

std::string의 대입, 연결, 비교, 인덱스, 검색, 부분 문자열, 편집과 C API 변환을 원본 변경 여부, 실패 결과, 범위 검사, 포인터 수명으로 구분하는 선택표

C++ · STRING OPERATION LEDGER

문자열 연산은 새 값·원본 변경·빌린 포인터를 구분한다

함수 이름보다 먼저 결과가 새 값인지, 원본 변경인지, 잠시 빌린 포인터인지 확인합니다. 인덱스·검색 실패·재할당·C API precondition을 연산과 함께 읽어야 합니다.

std::string 연산을 고르는 여섯 질문
목적과 대표 표현 유지할 계약
대입

=, +, +=

+는 결과 값을 만들고 =·+=는 왼쪽 객체를 바꿉니다.
비교

==, compare()

주소가 아닌 문자 시퀀스를 비교합니다. compare()는 음수·0·양수만 해석하며, 순서는 문자 traits 기반 lexicographical 비교입니다.
접근

[], at()

일반 문자 위치는 pos < size()입니다. 현행 draft에서 pos == size()는 끝 널 위치이고, at()은 이 위치부터 out_of_range를 던집니다.
검색

find(), substr()

검색 실패는 npos입니다. substr()는 새 값을 반환하고 pos > size()이면 예외를 던집니다.
편집

insert(), erase(), replace()

객체를 바꾸며 기존 포인터·참조·반복자가 무효화될 수 있습니다.
C API

c_str(), data(), 포인터+길이

C++11 이후 c_str()와 data()는 같은 배열 시작을 가리킵니다. 내장 널·길이·쓰기 가능 여부를 API와 함께 명시합니다.

새 값인가, 원본 변경인가

auto joined = left + right;
left += right;

첫 줄은 결과 값을 만들고, 둘째 줄은 left를 바꿉니다.

위치를 증명했는가

text[pos]      // pos < size()
text.at(pos)   // 검사 후 접근

find()의 결과는 npos인지 먼저 확인합니다. at()은 pos ≥ size()에서 예외를 던집니다. []의 끝 널 위치에는 다른 문자를 쓰지 않습니다.

내용이 같은가, 어느 쪽이 앞서는가

left == right
left.compare(right)

compare()의 음수·0·양수만 해석합니다. 문자 traits에 따른 순서이며 자연어 locale 정렬은 아닙니다.

부분 값과 원본 편집

substr(pos, count)는 새 값을 만들고 pos > size()에서 예외를 던집니다. insert()·erase()·replace()는 원본을 바꾸며 기존 관찰자를 무효화할 수 있습니다.

C API가 무엇을 받는가

널 종료 읽기 전용인지, 포인터와 길이를 함께 받는지, 쓰기 버퍼인지 구분합니다. C++11 이후 c_str()와 data()는 같은 배열 시작을 가리키며, c_str()는 읽기 전용 경계입니다.

검색 뒤 편집

실패 sentinel과 인덱스를 먼저 확인한다

std::string text =
    "Learn C++ today.";
auto pos = text.find("C++");

if (pos != std::string::npos) {
    auto topic =
        text.substr(pos, 3);
}

text.replace(10, 5, "safely");

결과: Learn C++ safely.

짧은 C 경계

필요한 순간에 포인터를 얻는다

void legacy_read_only(const char*);

std::string message = "ready";
legacy_read_only(message.c_str());

문자열을 바꾸거나 비상수 표준 라이브러리 호출에 넘긴 뒤에는 기존 포인터를 재사용하지 않고 다시 얻습니다.

명시적 길이

내장 널은 길이와 함께 보존한다

const char bytes[]{
    'A', '\0', 'B'
};
std::string packet(bytes, 3);

널 종료 생성자와 C 함수는 첫 널에서 멈춥니다. 값 전체가 필요하면 읽을 수 있는 범위의 길이를 함께 전달합니다.

쓰기 버퍼 아님

c_str()에 쓰지 않는다

반환형은 const char*입니다. 출력용 C API에는 별도의 writable 범위와 호출 후 실제 문자열 길이를 반영하는 계약이 필요합니다.

C++17 이후 비상수 data()로는 [0, size())의 문자를 쓸 수 있지만, 끝 널을 다른 값으로 바꾸거나 용량만 확보한 영역을 문자열 크기처럼 취급하면 안 됩니다.

선택 순서: 소유 값인지 → 조회·새 값·원본 변경인지 → 위치와 실패 결과가 유효한지 → 관찰자 무효화가 가능한지 → C API의 포인터·길이·쓰기 계약이 무엇인지.

하는 일대표 함수결과
길이, 빈 문자열 확인size(), empty()원래 문자열은 그대로
검색find(needle, pos)찾은 위치, 없으면 std::string::npos
부분 문자열substr(pos, count)새 문자열, pos > size()이면 예외
뒤에 덧붙이기+=, append(), push_back()원래 문자열을 바꿈
중간 편집insert(), erase(), replace()원래 문자열을 바꿈
모두 지우기clear()size()는 0, 용량은 그대로일 수 있음
find_and_replace.cpp
#include <iostream>
#include <string>

int main() {
    std::string text = "Learn C++ today.";

    const auto pos = text.find("C++");
    if (pos != std::string::npos) {
        std::cout << pos << ' ' << text.substr(pos, 3) << '\n';
    }

    text.replace(10, 5, "safely");
    std::cout << text << '\n';
}
6 C++
Learn C++ safely.

"C++"는 인덱스 6에서 시작하므로 find는 6을 돌려주고, substr(6, 3)은 그 위치의 세 글자로 새 문자열을 만듭니다. replace(10, 5, "safely")는 인덱스 10부터 다섯 글자인 today를 safely로 바꾸며, 바꾸는 글자 수가 달라도 문자열의 길이가 알맞게 바뀝니다.

find가 찾지 못했을 때 돌려주는 npos는 std::string::size_type의 가장 큰 값이어서, 확인하지 않고 인덱스로 쓰면 범위를 벗어납니다. 그래서 결과를 쓰기 전에 항상 npos와 비교합니다.

한 줄 입력과 std::getline

std::cin >> s는 공백 앞까지 단어 하나만 읽으므로, 공백이 들어간 이름이나 문장을 받으려면 std::getline(std::cin, s)로 줄바꿈 앞까지 한 줄을 읽습니다. 줄바꿈은 입력에서 꺼내 버리고 문자열에는 넣지 않으며, 길이는 std::string이 알아서 늘리므로 앞 글의 고정 배열처럼 넘칠 걱정이 없습니다.

read_name.cpp
#include <iostream>
#include <string>

int main() {
    int age{};
    std::string name;

    std::cin >> age;
    std::getline(std::cin >> std::ws, name);

    std::cout << name << " (" << age << ")\n";
}

이 코드는 나이를 >>로, 이름을 getline으로 읽습니다. 두 읽기 사이에 끼운 std::cin >> std::ws가 왜 필요한지가 이 예제에서 볼 부분입니다. 입력이 두 줄 30과 Kim Min이면 출력은 다음과 같습니다.

Kim Min (30)

std::cin >> age는 30만 읽고 그 뒤의 줄바꿈은 입력에 남겨 둡니다. 여기서 바로 std::getline(std::cin, name)을 부르면 남은 줄바꿈 앞의 빈 줄을 읽어 name이 빈 문자열이 됩니다. std::cin >> std::ws는 앞에 남은 공백과 줄바꿈을 건너뛰므로, 다음 줄의 Kim Min을 읽게 됩니다. 빈 줄도 의미 있는 입력이라면 std::ws 대신 while과 do-while 반복문에서 본 ignore()로 첫 줄의 나머지만 버립니다.

std::getline은 읽기에 성공하면 참으로 해석되므로, while (std::getline(std::cin, line))처럼 쓰면 입력이 끝날 때까지 한 줄씩 읽을 수 있습니다. 한 줄 전체가 정수인지 검사하려면 이렇게 줄을 읽은 뒤 따로 변환해 확인합니다.

C API에 넘기기

읽기 전용 const char*를 받는 C API에는 c_str()을 넘깁니다. c_str()과 data()는 널 문자로 끝나는 내부 글자 배열의 시작을 가리킵니다.

c_api.cpp
#include <string>

void legacy_read_only(const char* text);

void send() {
    std::string message = "ready";
    legacy_read_only(message.c_str());
}

이 포인터는 message가 살아 있고 바뀌지 않는 동안에만 쓸 수 있습니다. 글자를 덧붙이거나 지우거나 다른 값을 대입하면 내부 저장 공간이 옮겨 갈 수 있으므로, 포인터를 저장해 두지 말고 필요할 때 다시 얻습니다. 또 packet처럼 중간에 널 문자가 있는 문자열을 C 함수에 넘기면 C 함수는 첫 널 문자까지만 보므로, 길이도 함께 받는 API인지 확인합니다.

c_str()이 돌려준 포인터로 글자를 쓰면 안 됩니다. C++17부터는 const가 아닌 문자열의 data()가 쓰기 가능한 char*를 돌려주지만, 이때도 size() 범위 안의 글자만 바꿀 수 있고 끝의 널 문자는 건드리지 않습니다. 길이가 바뀌는 결과를 써 주는 C API라면, 호출 전에 resize()로 공간을 마련하고 호출 뒤 실제 길이에 맞게 다시 줄이는 식으로 따로 처리합니다.

정리

  • std::string은 글자의 나열을 값으로 소유하며, size()는 글자 수, capacity()는 재할당 없이 담을 수 있는 수이고, 끝의 널 문자는 size()에 들어가지 않습니다.
  • 길이는 char 단위이므로 UTF-8의 한글 한 글자는 3으로 셉니다.
  • =, +, +=, ==, <를 기본 타입처럼 쓰지만, +의 두 피연산자가 모두 문자열 리터럴이면 컴파일 오류입니다.
  • []는 범위를 검사하지 않고, at()은 범위 밖에서 std::out_of_range를 던지며, find의 결과는 npos인지 먼저 확인합니다.
  • 공백이 있는 한 줄은 std::getline으로 읽고, >> 뒤에 남은 줄바꿈은 std::ws나 ignore()로 처리합니다.
  • c_str()의 포인터는 문자열이 바뀌거나 사라지면 쓸 수 없으므로 보관하지 않습니다.

연습 문제

1. 길이를 주지 않은 생성자

string_from_bytes.cpp의 생성자만 std::string packet(raw);로 바꾸면 만든 직후의 size()는 무엇이고, 이어지는 packet.at(2) = 'C';는 어떻게 되나요?

해설 보기

길이를 받지 않는 생성자는 첫 널 문자 앞의 A만 복사하므로 size()는 1입니다. 인덱스 2는 범위 밖이어서 at(2)가 std::out_of_range 예외를 던지고, 예외를 잡지 않았으므로 출력 문장에 도달하지 못한 채 프로그램이 끝납니다.

2. 남은 줄바꿈

read_name.cpp에서 std::cin >> std::ws를 빼고 std::getline(std::cin, name);으로 바꾸면, 같은 두 줄 입력에 무엇이 출력되나요?

해설 보기

std::cin >> age가 30 뒤의 줄바꿈을 남기므로, getline은 그 줄바꿈 앞의 빈 줄을 읽어 name이 빈 문자열이 됩니다. 출력은 (30)이고, 둘째 줄의 Kim Min은 읽히지 않은 채 남습니다.

3. 컴파일되는 연결

std::string s = "C";일 때 "A" + "B", s + "!" + "?", "!" + "?" + s 가운데 컴파일되는 것은 무엇인가요?

해설 보기

s + "!" + "?"만 컴파일됩니다. +는 왼쪽부터 묶이므로 (s + "!") + "?"가 되어 두 단계 모두 std::string이 있고, 결과는 "C!?"입니다. "A" + "B"와 "!" + "?" + s는 먼저 두 리터럴끼리 더하게 되어 컴파일 오류이며, std::string("!") + "?" + s처럼 첫 피연산자를 std::string으로 만들면 됩니다.

이것으로 5장의 배열과 문자열을 마칩니다. 다음 장의 함수의 정의와 호출에서는 지금까지 main 안에 모두 적었던 코드를 이름 있는 단위로 나누고, 값을 넘겨 결과를 돌려받는 함수를 다룹니다.