문자열 토크나이징이란 문자열을 원하는 문장 혹을 단어를 기준으로 나누는 작업을 뜻합니다.
그 예로 "I am a boy"를 " "(빈칸)을 기준으로 나누어보면, "I", "am", "a", "boy"로 나눌 수 있습니다.
문자열 토크나이징은 그 활용도가 높아 여러 언어에서 해당 기능을 기본 유틸 함수로 제공해주고 있습니다.
C++ 또한 strtok() 메서드를 통해 해당 기능을 제공하지만 다른 언어에 비해 사용하기 조금 복잡한 면이 있습니다.
사용이 어려운 가장 큰 이유는 string 타입이 stl이 아닌 단순한 container이기에 C에서 제공하는 방식을 사용하기 때문입니다.
사용 과정
사용 과정은 다음과 같다
- string을 char [] 자료형으로 변형시켜 준다. (c_str() 사용)
- char []을 strtok()으로 잘라낸다.
- 잘라낸 char []을 string으로 변환시킨다. (2 - 3 과정을 반복)
예제 코드로 더 자세히 살펴보겠습니다.
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
using namespace std;
int main()
{
string s = "I am a boy"; //자르고자 하는 문자열
char str_buffer[1000];
strcpy(str_buffer, s.c_str()); //char[]로 자료형 변경 후 배열에 저장
vector<string> result; //토크나이징 된 결과가 담길 vector
char *tok = strtok(str_buffer, " "); // 첫번째 인자는 자를 문자열, 두번째 인자는 기준이 되는 문자열을 넣어준다.
//자르는 기준으로 문자(' ')가 아닌 문자열(" ")을 넣어주어야한다.
while (tok != nullptr)
{
result.push_back(string(tok));
tok = strtok(nullptr, " "); //이어서 문자열을 자르도록 nullptr을 넣어준다.
}
for(int i = 0; i < result.size(); i++)
{
cout << result[i] << endl;
}
// 출력결과:
// I
// am
// a
// boy
return 0;
}
strtok에 문자열이 아닌 nullptr을 사용할 경우 문자열을 이어서 자를 수 있습니다.
이는 해당 유틸 함수 내 정적으로 할당되어 있는 lastToken 변수를 통해 내부적으로 문자열을 관리하고 있기 때문에 가능합니다.
만약 한 개의 문자열을 토크나이징 하던 도중에 strtok 함수에 다른 문자열을 넣을 경우 lastToken에 기존의 문자열이 대체됩니다.
따라서 문자열 토크나이징을 병렬적으로 실행한다면 예상과 다른 결과물이 나올 수 있습니다.
strtok의 작동 원리
strtok의 작동원리를 살펴보기 위해 내부 코드를 열어보면 아래와 같습니다.
/*
이 소스는
http://research.microsoft.com/en-us/um/redmond/projects/invisible/src/crt/strtok.c.htm
에서 가져왔습니다.
*/
char *__cdecl strtok(char *s1, const char *delimit) {
static char *lastToken = NULL; /* UNSAFE SHARED STATE! */
char *tmp; /* Skip leading delimiters if new string. */
if (s1 == NULL) {
s1 = lastToken;
if (s1 == NULL) /* End of story? */
return NULL;
} else {
s1 += strspn(s1, delimit);
} /* Find end of segment */
tmp = strpbrk(s1, delimit);
if (tmp) { /* Found another delimiter, split string and save state. */
*tmp = '\0';
lastToken = tmp + 1;
} else { /* Last segment, remember that. */
lastToken = NULL;
}
return s1;
}
strtok() 메서드의 작동원리를 이해하기 앞서 내부에서 사용되는 strspn(), strpbrk() 함수의 기능을 먼저 살펴보겠습니다.
strspn() 메서드는 문자열에서 다른 문자열에 포함되어 있는 문자들을 검색합니다.
이때 첫 번째로 일치하는 문자에 도달하기까지 읽어 들인 문자의 개수를 리턴합니다.
strpbrk() 메서드는 기준 문자(delimit)들 중 문자열(s1)의 문자들과 첫 번째로 일치하는 문자를 가리킵니다.
만약 str1의 널 문자 이전까지 일치하는 것이 없다면 NULL을 리턴합니다.
strtok()에서는 strcspn()을 통해 문자열 조각의 시작 위치를 찾고 strpbrk()을 통해 이번 문자열 조각의 마지막 위치를 찾습니다.
이 과정에서 마지막 위치에 문자를 '\n'로 변경하므로 기존 문자열의 값이 변경됩니다.
주의사항
strtok() 메서드 내부에서 사용되는 strspn(), strpbrk() 모두 delimiter를 판단할 때 전부와 일치하는지 확인하지 않기 때문에
strtok() 함수가 의도한 것과 다른 결과물을 보여줄 가능성이 있습니다.
의도와 다른 결과물이 나오는 사례를 코드를 통해 살펴보겠습니다.
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
using namespace std;
int main()
{
string s = "I am a boy, I am a girl";
char str_buffer[1000];
strcpy(str_buffer, s.c_str());
vector<string> result;
char *tok = strtok(str_buffer, "am");
while (tok != nullptr)
{
result.push_back(string(tok));
tok = strtok(nullptr, "am");
}
for(int i = 0; i < result.size(); i++)
{
cout << result[i] << endl;
}
return 0;
}
위 코드는 "am"을 기준으로 문자열을 잘라내고자 합니다.
하지만 실행 결과는 다음과 같이 의도와는 다른 형태를 출력합니다.
I
boy, I
girl
아래와 같이 문자열을 자르기 위해 "am"만을 이용하지 않고 부분적으로 일치하는 "a"를 사용했지 때문입니다.
I (am)
(a)
boy, I (am)
(a)
girl
strtok() 메서드는 간편하게 문자열을 원하는 단위를 기준으로 잘라낼수 있습니다.
하지만 delimiter를 판단하는 과정에서 문자열 자체를 사용하지 않기에 delimiter를 단어 단위로 사용하는 경우 주의가 필요합니다.
'Language & FrameWork > C++' 카테고리의 다른 글
[C++] std::advance(),std::distance() 임의 접근 생성자 (0) | 2020.05.04 |
---|---|
& 참조형 변수 (0) | 2020.03.22 |
[C++11]emplace 함수 (0) | 2020.03.17 |
[C++] 메모리 관리 방법 정리 (0) | 2020.03.17 |