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

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

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

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

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

        stringVariable.operation(argumentList)
예를 들어 string1string2std::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 객체인 str1str2 두 문자열의 내용을 대소문자를 구별하여 전통적으로 비교하는 것이면 충분하다.

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

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

5.2: std::string 참조

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

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

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

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

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

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

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

인덱스를 계산하는 모든 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"sstd::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> 헤더를 먼저 포함시켜야 사용할 수 있다.