본문 바로가기

Software/C/C++

[ C++.STL] 12장: string

12.1 string 추상(abstraction)

string은 인덱싱이 가능한 문자들의 시퀀스이다. 비록, string이 vector의 서브클래스는 아니지만, 5장에서 설명한 대부분의 vector 연산들을 string에 적용할 수 있다. 게다가, string은 vector 연산뿐만 아니라 유용하고 강력한 고수준 연산들을 추가로 제공한다.
표준 라이브러리의 string은 실제로는 basic_string 템플릿 클래스이다. 템플릿 인자는 string 컨테이너를 구성하는 문자의 타입을 나타낸다. 이렇게 함으로써, 표준 라이브러리는 일반적으로 많이 쓰이는 8비트 아스키 문자들뿐만 아니라 16비트 광폭 문자(wide character)들과 같은 것들도 다룰 수 있는 기능을 제공한다. stringwstring(광폭 string) 데이터 타입은 다음과 같이 typedef를 거친 basic_string들이다.
   typedef basic_string<char> string;
   typedef basic_string<wchar_t> wstring;
string과 wstring
이 이후로는 string에 대해서만 설명하겠지만, 여기서 설명하는 모든 연산들은 wstring에도 똑같이 적용된다.
방금 말했듯이, string은 여러면에서 문자 vector와 비슷하다. vector와 마찬가지로, string에도 두가지 종류의 사이즈가 있다. 하나는 string이 실제로 담고 있는 문자들의 갯수를 나타낸다. 다른 하나는 버퍼를 새로 할당하지 않고 string에 저장할 수 있는 문자의 최대갯수를 나타내며, capacity라고 칭한다. vector에서처럼, string의 capacity도 동적으로 변하는 양이다. string 연산을 수행하는 도중에 string에 저장된 문자의 갯수가 string의 capacity를 초과하게 되면, 내부적으로 버퍼를 새로 할당하여 string의 capacity를 증가시킨다. 이때 모든 과정들은 겉으로 드러나지 않으며, 프로그래머가 관여할 필요도 없다.

12.1.1 Include 화일

string을 사용하는 프로그램들은 string 헤더 화일을 포함해야 한다.
   #include <string>
 

12.2 string 연산

이 절에서는 string을 생성하거나 조작하는데 사용되는 표준 라이브러리 연산들을 살펴본다.

12.2.1 string의 선언과 초기화

string을 선언하는 가장 간단한 형태는 단순히 변수 이름만을 지정하거나, string의 초기값을 같이 제공하는 방법이 있다. 이 형태는 9.3.2절에 주어진 그래프 예제에서 가장 많이 사용한 방법이다. 복사 생성자는 다른 string 값을 초기값으로 사용한다.
   string s1;                       // 변수 이름만 지정
   string s2("a string");           // 초기값 제공
   string s3 = "initial value";     // 초기값 제공
   string s4(s3);                   // 복사 생성자 이용
이러한 경우에는 capacity는 처음에는 저장된 문자의 갯수와 정확히 같다. 명시적으로 초기 capacity를 지정할 수 있는 생성자가 있으며, 또한 capacity 값을 초기화하고 같은 문자를 여러개 포함하도록 string을 초기화할 수 있다.
   string s6("small value", 100);   // 11개의 문자를 포함, 100개까지 담을 수 있음
   string s7(10, 'n');             // 10개의 개행문자로 초기화
반복자를 이용한 초기화
한쌍의 반복자를 사용하여 컨테이너를 초기화할 수 있다는 것은 컨테이너를 선언할 때 사용한 템플릿 인자와는 무관한 별도의 템플릿 인자를 사용하여 템플릿 멤버 함수를 선언할 수 있는 컴파일러가 지원되어야 한다는 것을 의미한다.
마지막으로, 다른 컨테이너 클래스에서처럼 string도 한쌍의 반복자를 사용하여 초기화할 수 있다. 반복자가 가리키는 시퀀스는 적절한 타입의 원소들을 가지고 있어야 한다.
   string s8(aList.begin(), aList.end());

12.2.2 사이즈(size)와 용량(capacity) 고치기

vector 데이터 타입과 마찬가지로, string의 현재 사이즈는 size() 멤버 함수를 통해 알아볼 수 있고, 현재 capacity는 capacity()를 사용한다. capacity는 reserve() 멤버 함수를 사용하여 변경할 수 있고, 필요하면 용적을 조절하여 string이 인자로 명시한 만큼의 문자를 담도록 할 수 있다. max_size() 멤버 함수는 할당할 수 있는 가장 큰 string 사이즈를 반환한다. 대개 이값은 사용가능한 메모리 양에 의해 제한된다.
   cout << s6.size() << endl;
   cout << s6.capacity() << endl;
   s6.reserve(200);                    // capacity를 200으로 바꾼다
   cout << s6.capacity() << endl;
   cout << s6.max_size() << endl;
length() 멤버함수는 size() 멤버함수와 동일하다. resize() 멤버 함수는 string의 사이즈를 변경할 수 있고, 이 과정에서 string의 끝부분이 잘릴 수도 있고, 새로 문자들이 첨가될 수도 있다. resize()의 두번째 인자는 새로 추가되는 문자를 명시할 수 있으며, 생략도 가능하다.
   s7.resize(15, 't');                // 맨 끝에 탭 문자들을 추가하여 사이즈를 늘린다
   cout << s7.length() << endl;        // 사이즈는 이제 15
empty() 멤버 함수는 string에 문자가 없을 때 참이 되며, size()를 0이랑 비교하는 것보다 일반적으로 빠르다.
   if (s7.empty()) 
      cout << "string is empty" << endl;

12.2.3 대입(assign), 덧붙이기(append), 교환(swap)

string 변수에 다른 string을 대입할 수가 있고, C 스타일의 문자 배열이나 개별 문자도 대입이 가능하다.
   s1 = s2;
   s2 = "a new value";
   s3 = 'x';
+= 연산자도 이들 세가지 형태의 인자들과 함께 사용이 가능하며, 우변값이 좌변의 끝에 덧붙인다.
   s3 += "yz";                  // s3는 이제 "xyz"
더 일반적인 형태의 함수로 assign()append() 멤버 함수가 있다. 이들은 첫번째 인자로 주어진 string의 일부분만 append되도록 하는데, 두번째 인자로 명시된 위치에서 세번째 인자로 주어진 갯수만큼을 append한다.
   s4.assign(s2, 0, 3);         // s2의 처음 3 문자를 s4에 대입한다
   s4.append(s5, 2, 3);         // 2, 3, 4번 위치에 있는 문자들을 덧붙인다
덧셈 연산자 +는 두개의 string을 연결할 때 사용한다. + 연산자는 왼쪽 인자의 복사본을 만들고, 이 복사본에 오른쪽 인자를 덧붙인다.
   cout << (s2 + s3) << endl;   // s2와 s3을 덧붙인 것을 출력
표준 라이브러리의 모든 컨테이너들과 마찬가지로, 두 string의 내용은 swap()을 사용하여 서로 바꿀 수 있다.
   s5.swap(s4);                 // s4와 s5를 바꾼다

12.2.4 문자 접근

string에 담긴 개별적인 문자들은 첨자 연산을 사용하여 접근하거나 대입한다. at() 멤버함수는 인자로 주어진 값이 size()보다 크거나 같으면 out_of_range 예외(exception)가 throw된다는 것을 제외하곤 첨자연산과 동일하다.
   cout << s4[2] << endl;        // s4의 2번 위치의 문자를 출력
   s4[2] = 'x';                  // 2번 위치의 문자를 변경
   cout << s4.at(2) << endl;     // 변경된 값을 출력
c_str() 멤버 함수는 널문자로 끝나는 문자 배열을 리턴하고, 이 배열에 담긴 문자들은 string에 담긴 문자들과 동일하다. 이는 예전의 C 스타일의 문자배열을 가리키는 포인터를 필요로하는 함수와 함께 string을 사용할 수 있도록 해준다. 게다가, c_str()가 리턴하는 값은, 재할당이 일어날 수 있는 연산(append()insert())을 수행한 뒤에, 유효하지 않을 수가 있다. data() 멤버 함수는 string을 구성하는 문자 버퍼에 대한 포인터를 반환한다.
   char d[256];
   strcpy(d, s4.c_str());        // s4를 배열 d에 복사

12.2.5 반복자

begin()end() 멤버 함수는 string의 처음과 끝을 가리키는 임의접근 반복자를 리턴한다. 반복자가 가리키는 값들은 각각의 문자들이다. rbegin()rend()는 역방향 반복자(?)를 리턴한다.
반복자의 무효화
appendinsert와 같이 내부 string 버퍼의 재할당을 초래할 수 있는 연산을 수행한 뒤에는 반복자가 가리키는 값이 유효하다고 보장할 수 없다.

12.2.6 삽입, 삭제, 치환(replacement)

string의 멤버 함수인 insert()erase()는 vector 함수 insert()erase()와 비슷하다. vector와 같이 반복자들을 인자로 넘겨주고, 이들 인자가 가리키는 구간을 삽입하고 삭제한다. replace() 함수는 erase()insert()를 합쳐 놓은 것과 같으며, 특정 구간을 새로운 값으로 치환한다.
   s2.insert(s2.begin()+2, aList.begin(), aList.end());
   s2.erase(s2.begin()+3, s2.begin()+5);
   s2.replace(s2.begin()+3, s2.begin()+6, s3.begin(), s3.end());
추가로, 반복자로 구현하지 않은 함수들도 있는데, insert 멤버 함수는 위치와 string을 인자로 취하여, string을 주어진 위치에 삽입한다. erase() 함수는 두개의 정수 인자로 위치와 길이를 취하고 명시된 문자들을 삭제한다. replace() 함수는 string과 길이(생략가능)와 더불어 두개의 정수 인자를 취하여, 명시한 구간을 string으로 대치한다. 만약에 길이가 명시적으로 주어지면, string의 앞쪽부터 주어진 길이만큼에 해당하는 string으로 대치한다.
   s3.insert(3, "abc");      // 3번 위치 뒤에 "abc" 삽입
   s3.erase(4, 2);           // 4, 5번 위치 삭제
   s3.replace(4, 2, "pqr");  // 4, 5번 위치를 "pqr"로 치환

12.2.7 복사와 서브string

copy() 멤버 함수는 substring을 만들어 이를 첫번째 인자로 주어진 곳에 대입한다. substring을 가리키는 구간은 처음 위치만 명시하거나, 위치와 길이를 명시한다.
   s3.copy(s4, 2);           // s3의 2번 위치부터 끝까지를 s4에 복사
   s5.copy(s4, 2, 3);        // s5의 2, 3, 4번 위치를 s4에 복사
substr() 멤버 함수는 현재 string의 일부분을 나타내는 string을 리턴한다. 구간은 처음위치로만 명시하거나, 위치와 길이로 명시한다.
   cout << s4.substr(3) << endl;       // 3번 위치부터 끝까지 출력
   cout << s4.substr(3, 2) << endl;    // 3, 4번 위치 출력

12.2.8 비교

string의 비교
비록 compare() 함수가 있긴 하지만, compare() 멤버함수를 직접 호출하는 경우는 거의 없을 것이다. 대신에, 비교 연산자를 사용하는 것이 더 일반적이다. 어차피 이 비교 연산자에서도 compare()를 사용한다.
compare() 멤버 함수는 수신자와 인자 string간의 사전적인 비교를 수행하는데 사용된다. 다른 함수와 비슷하게, 처음위치만 명시하거나 처음위치와 길이를 같이 명시할 수 있다. 사전식 배열을에 대해서는 13.6.5절을 참고한다. 수진자가 인자보다 사전배열상으로 더 작으면 음수를 리턴하고, 같으면 0을, 크면 양수를 리턴한다.
관계 연산자나 상등연산들(<, <=, >=, >, ==, !=)은 모두 비교 멤버 함수를 사용하여 정의된다. 임의의 두 string간이나, string과 C 스타일의 character literal(?)간에도 비교가 가능하다.

12.2.9 검색

find() 멤버 함수는 현 string에서 인자로 주어진 string이 처음으로 나타나는 곳을 찾아낸다. 정수값을 두번째 인자로 하여 검색의 시작점을 명시할 수도 있다(생략 가능하다). 함수가 일치부분을 찾으면, 현 string에서의 일치부분의 시작위치를 리턴한다. 못찾으면, string의 합당한 첨자를 벗어나는 값을 리턴한다. rfind() 함수가 비슷하지만, 끝에서부터 거꾸로 스캔한다.
   s1 = "mississippi";
   cout << s1.find("ss") << endl;              // 2를 리턴
   cout << s1.find("ss", 3) << endl;           // 5를 리턴
   cout << s1.rfind("ss") << endl;             // 5를 리턴
   cout << s1.rfind("ss", 4) << endl;          // 2를 리턴
find_first_of(), find_last_of(), find_first_not_of(), find_last_not_of()는 인자로 주어진 string을
일반적인 문자열로 보지 않고 문자의 집합으로 본다. 다른 함수들처럼, 하나 또는 두개의 정수 인자들(생략가능)을 사용하여
문자열의 서브스트링을 명시할 수 있다. 이들 함수는 인자로 주어진 문자집합에 속하는(또는 속하지 않는)
첫번째(또는 마지막) 문자를 찾는다.
원하는 문자를 찾게 되면 주어진 문자의 위치를 리턴하고, 찾지 못하면 첨자의 유효범위를 벗어나는 값을 리턴한다.
   i = s2.find_first_of("aeiou");            // 첫번째 모음 찾기
   j = s2.find_first_not_of("aeiou", i);     // 다음번 자음 찾기
 
 
 

12.3 예제 함수 - 텍스트 라인을 단어들로 쪼개기

이 절에서는 텍스트 한 줄을 여러개의 단어들로 쪼개는 함수를 하나 정의한다. 이를 통해, 앞에서 설명한 string 함수들을 응용해 보자. 이 함수는 9.3.3절의 concordance 예제에서도 사용했던 함수이다.
이 함수는 세개의 인자가 필요하다. 처음 두개는 string인데, 첫번째 인자는 텍스트 라인 한줄을 담고 있고 두번째 인자는 단어들을 구분하는 문자인 구분자들을 담고 있다. 세번째 인자는 string의 list로, 라인으로부터 추출한 단어들이 담기게 된다. 즉, 세번째 인자가 이 함수의 수행 결과를 담게 된다.
void split(string& text, string& separators, list<string>& words)
{
   int n = text.length();
   int start, stop;

   start = text.find_first_not_of(separators);
   while ((start >= 0) && (start < n)) {
      stop = text.find_first_of(separators, start);
      if ((stop < 0) || (stop > n)) stop = n;
      words.push_back(text.substr(start, stop - start));
      start = text.find_first_not_of(separators, stop+1);
   }
}
이 함수는 먼저 구분자가 아닌 첫번째 문자를 찾는 일부터 시작한다. while 루프내에서는 그 뒤에 나타나는 구분자에 속하는 문자를 찾는다. 만약 구분자를 찾지 못하면 string의 끝을 사용한다. 이들 둘간의 차가 한 단어를 구성하고, substring 연산으로 복사하여 세번째 인자로 삽입한다. 그리고나서, 다음 단어의 첫번째 위치를 찾고, 다음번 루프를 반복한다. 'start' 변수가 string의 범위를 벗어나면 수행을 마친다.