제 5 장: `string' 데이터 유형

C++는 흔한 문제들에 대하여 다양한 해결책을 제공한다. 이 편의기능들은 대부분 표준 템플릿 라이브러리에 포함되거나 또는 총칭 알고리즘으로 구현되어 있다 (제 19장).

C++ 프로그래머가 끊임없이 개발하는 편의기능 중에서 텍스트를 조작하는 기능은 거의 string이라고 부르는 라이브러리에 포함되어 있다. C 프로그래밍 언어는 기본적인 문자열을 지원한다. C의 NTBS를 기반으로 그 위에 수 많은 코드가 구축되어 왔다 (NTBS (널-종료 바이트 문자열, 또한 NTB 문자열도) 문자열 연속으로서 가장 마지막 원소의 값이 0이다. 즉, 종료 널 문자로 정의되어 있다. 연속열에서 값을 0으로 가지는 다른 문자는 없다.).

C++는 텍스트를 처리하기 위하여 std::string 유형을 제공한다. C++에서는 NTB 문자열을 조작하는 전통적인 C 라이브러리 함수보다는 string 객체를 사용한다. C 프로그램에서 많은 문제들은 버퍼 범람과 경계 에러 그리고 할당 문제 때문에 야기되며 추적해 들어가 보면 대부분 전통적인 C 문자열 라이브러리 함수를 부적절하게 사용했기 때문임을 알 수 있다. C++ 문자열 객체를 사용하면 이런 문제들을 대부분 피할 수 있다.

실제로 string 객체는 클래스 유형의 변수이며 그런 의미에서 cin과 cout 같은 스트림 객체와 비교된다. 이 절은 string 유형의 객체를 다룬다. 그 정의와 사용법에 초점을 둔다. string 객체를 사용할 때 멤버 함수 구문을 사용하는 것이 보통이다.

        stringVariable.operation(argumentList)
예를 들어 string1과 string2가 std::string 유형의 변수이면
        string1.compare(string2)
두 문자열을 비교할 수 있다.

일반적인 멤버 함수 말고도 string 클래스는 연산자(operator)도 광범위하게 제공한다. 예를 들어 할당 연산자(=)와 비교 연산자(==)가 있다. 연산자는 코드를 이해하기 쉽게 해 주고 비교용 멤버 함수보다 더 많이 사용된다. 다음과 같이 작성하는 것보다는

    if (string1.compare(string2) == 0)
다음과 같이 사용하는 것이 보통이다.
    if (string1 == string2)

string 유형의 객체를 정의하고 사용하려면 소스에 <string> 헤더를 포함해야 한다. 단순히 문자열 유형을 선언(declare)하는 정도라면 iosfwd 헤더를 포함해도 된다.

std::string외에도 다음과 같은 문자열 유형이 정의되어 있다.

5.1: 문자열 연산

문자열 연산 중에 문자열 안의 인덱스를 돌려주는 연산이 있다. 적절한 인덱스를 발견하지 못하면 string::npos 값을 돌려준다. 이 값은 string::size_type 유형의 상징적 값으로서 (실용적 목적으로) unsigned int이다.

string 객체를 인자로 받는 모든 string 멤버는 char const * (NTBS) 인자도 받는다. 이것은 string 객체를 받는 연산자들에도 적용된다.

어떤 string 멤버는 반복자(iterator)를 사용한다. 반복자는 18.2절에 정식으로 소개한다. 반복자를 사용하는 멤버 함수는 다음 절에 나열한다 (5.2). 그러나 반복자 개념 자체는 이 장에 더 이상 다루지 않는다.

string 클래스는 많은 멤버와 연산자를 지원한다. 간략하게 그의 능력을 이 절에 개관해 보고 다음 절에서 더 자세하게 다루겠다. 요약해 말하면 C++가 문자열을 처리하는 능력은 대단히 뛰어나기 때문에 C 라이브러리에 의존하여 텍스트를 처리해야 할 이유가 거의 없다. C++ 문자열은 필요한 모든 메모리 관리를 처리하고 그리하여 C 프로그램에서 문제의 근원이 되는 메모리 관련 문제를 뿌리뽑을 수 있다. 그렇지만 string에는 그에 상응하는 대가가 따른다. 문자열 클래스의 광범위한 능력 때문에 오히려 메모리 문제가 더 심각해질 수도 있다. 그의 모든 특징을 배우고 익히기가 어려울 뿐더러 결국 예상한 모든 것이 다 갖추어져 있는 것도 아니라는 사실을 알게 될 것이다. 예를 들어 std::string은 대소문자를 구별해 비교하지 못한다. 생각보다 구현하기도 그렇게 쉽지 않다. 분명히 있기는 하지만 약간 은밀한 것도 있어서 이 시점에서 거기에 파고 들기에는 좀 이른 감이 있다. 대신에 C의 표준 라이브러리가 유용한 기능을 제공한다는 사실을 깨닫기를 바란다. 우리는 이미 그 한계를 잘 알고 있고 함정을 잘 피할 수도 있다. 그래서 지금 당장은 다음과 같이 std::string 객체인 str1과 str2 두 문자열의 내용을 대소문자를 구별하여 전통적으로 비교하는 것이면 충분하다.

    strcasecmp(str1.c_str(), str2.c_str());

string 클래스는 다음 기능을 제공한다.

5.2: std::string 참조

이 절은 문자열 멤버와 문자열 관련 연산을 다룬다. 각각의 항은 문자열의 초기화와 반복자 그리고 연산자와 멤버 함수를 다룬다. 다음은 이 절에 사용되는 전문 용어이다.

opos와 apos 모두 기존의 상대 주소를 가리켜야 한다. 그렇지 않으면 예외가 발생한다 (제 10장). 대조적으로 an과 on은 실제 문자의 갯수를 초과해도 된다. 이 경우 가능한 문자만 고려할 뿐이다.

많은 멤버가 on과 an 그리고 apos에 대하여 기본값이 선언되어 있다. 어떤 멤버는 opos에도 기본값을 선언한다. 기본 상대주소 값은 0이다. on과 an의 기본값은 string::npos이며 이 값은 `요구한 문자의 갯수를 넘어 문자열의 끝에 도달함'이라고 이해하면 된다.

끝에서 연산을 시작하여 문자열 객체의 내용을 거꾸로 처리하는 멤버에 대하여 opos의 기본 값은 그 객체의 마지막 문자의 인덱스이고 on은 기본값으로 opos + 1과 동등하며 opos에서 끝나는 부문자열의 길이를 나타낸다.

아래에 제시하는 멤버 함수를 개관해 보면 이 모든 매개변수가 따로 언급하지 않는 한, 기본값을 받는다고 요약해도 좋을 것이다. 물론 함수가 기존의 인자들을 넘어서서 인자를 더 요구하면 기본 인자 값은 사용할 수 없다.

어떤 멤버 함수는 첫 인자가 char const * 유형이기를 기대한다. 그러나 그렇지 않다고 할지라도 매개변수가 std::string으로 정의되어 있다면 첫 인자는 언제든지 char const * 유형이 될 수 있다.

여러 멤버 함수가 반복자(iterator)를 받는다. 18.2절에 반복자(iterator)의 기술적 측면을 다루겠지만 이 시점에는 무시해도 읽는 흐름을 깨지 않는다. apos와 opos처럼 반복자도 문자열 객체의 내용 안에서 기존의 위치 또는 기존의 문자 범위를 가리켜야 한다.

인덱스를 계산하는 모든 string 멤버 함수는 실패하면 미리 정의된 string::npos를 돌려준다.

C++14 표준은 s라는 기호상수 접미사를 제공한다. 이 접미사는 ("hello world"와 같이) 문자열 기호상수가 사용될 때 std::string 상수라는 사실을 나타낸다. using namespace std를 선언한 후에, 꼭 찝어 말해 다음과 같이 using namespace std::literals::string_literals로 선언한 후에 사용할 수 있다. std::string 객체의 문맥에서 사용될 때는 문자열 기호상수 접미사는 거의 필요하지 않지만 auto 키워드와 함께 사용될 때는 편리하다. 예를 들어 auto str = "hello world"s는 std::string str을 정의하는 반면에 기호상수 접미사를 생략하면 char const *로 정의된다.

5.2.1: 초기화자

문자열 객체를 정의하고 나면 유효한 상태에 있다는 보장을 할 수 있다. 정의 시간에 문자열 객체는 다음 중 한 가지 방식으로 초기화된다. 다음 string 생성자를 사용할 수 있다. string(std::initializer_list<char>)으로 문자열을 생성하는 방법이 있다. 초기화 리스트(initializer_list)로 std::string 객체를 생성한다. string oops{ 1, 'x' }와 같이 지정하면 문자 두개를 원소로 가진 문자열을 구성한다. 아스키 코드로 하나는 1이고 다른 하나는 'x'이다. x 문자 한 개로 구성된 문자열이 전혀 아님을 주의하라. std::string(size_t count, char c)을 의도할 때 활괄호로 초기화하지 않도록 주의하라.

5.2.2: 반복자

반복자에 관한 자세한 내용은 18.2절을 참고하라. 반복자를 짧게 소개하면 반복자는 마치 포인터처럼 행위한다. 반복자는 포인터를 요구하는 상황에 자주 사용된다. 반복자는 한 쌍으로 한 범위의 개체를 정의한다. begin-반복자는 첫 개체를 가리키고 end-반복자는 범위를 벗어나 마지막 개체를 넘어선 곳을 가리킨다. 둘 사이의 차이는 반복자-범위에 있는 개체의 갯수와 같다.

반복자는 총칭 알고리즘의 문맥에서 중요한 역할을 한다 (제 19장). std::string 클래스에는 다음 반복자 유형이 정의되어 있다.

5.2.3: 연산자

string 객체는 멤버 함수로 조작할 수 있지만 연산자로도 조작할 수 있다. 연산자를 사용하면 코드 모습이 더 자연스럽다. 연산자를 사용할 수 있고 멤버 함수와 기능이 같을 경우는 사실상 언제나 연산자를 사용한다.

다음의 연산자들을 string 객체에 사용할 수 있다 (예제에서 `객체'와 `인자'는 기존의 std::string 객체를 가리킨다).

5.2.4: 멤버 함수

std::string 클래스는 많은 멤버 함수가 있다. 추가로 제공되는 비-멤버 함수도 문자열 클래스의 일부로 간주해야 한다. 이 함수들을 모두 아래에 알파벳 순서로 나열하였다.

상징적인 string::npos 값이 문자열 클래스에 정의되어 있다. 이 값은 멤버 함수가 문자열 오프셋 위치를 돌려줄 때 `발견하지 못한 인덱스'를 나타낸다. 예를 들어 문자 'x'가 들어 있지 않은 문자열 객체에 `object.find('x')'를 호출하면 (아래 참고) npos가 반환된다. 요청한 위치가 존재하지 않기 때문이다.

C 문자열에서 NTBS의 끝을 가리키는 마지막 0-바이트는 C++ 문자열의 일부분으로 간주되지 않는다. 그래서 C 문자열의 문자들을 담고 있는 문자열 객체에서 0을 찾으면 멤버 함수는 length()가 아니라 npos를 돌려준다.

다음은 문자열 객체에 작동하는 표준 함수들이다. 매개변수로 size_t를 언급하면 string::size_type 유형의 매개변수로 이해해도 된다. 그러나 기본 인자 값이 정의되어 있지 않을 경우는 제외한다. size_type 유형은 string::size_type로 읽어야 한다. size_type라고 지정하면 5.2절에 언급된 기본 인자 값이 적용된다. 인용부호를 붙인 함수는 모두 std::string 클래스의 멤버 함수이다. 단, 따로 언급하면 예외이다.

5.2.5: 변환 함수

std::string 객체를 생성하고 처리하는 문자열 변환 함수를 아래에 알파벳 순서로 나열하였다. 클래스가 있는 멤버 함수가 아니다. std 이름 공간에 자유 함수로 선언되어 있다. <string> 헤더를 먼저 포함시켜야 사용할 수 있다.