Skip to content

38. 문자열 토큰화


1. 키워드

  • strtok(String Tokenize)


2. 문자열 자르기

  • 이번에는 주어진 문자열을 자르는 방법을 알아보자.
  • 참고로 문자열 자르기는 포인터를 이용하는 방식이라 내용이 어려울 수 있다.


3. 문자를 기준으로 문자열 자르기

  • 먼저 특정 문자를 기준으로 문자열을 자르는 방법이다.
  • string.h 헤더 파일에 선언되어 있는 strtok 함수를 사용하면 문자열을 자를 수 있다.


strtok(대상문자열, 기준문자);
자른 문자열을 반환, 더이상 자를 문자열이 없으면 NULL을 반환
#define _CRT_SECURE_NO_WARNINGS // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strtok 함수가 선언된 헤더 파일

int main()
{
    char s1[30] = "The Little Prince"; // 크기가 30인 char 타입 배열을 선언하고 문자열 할당

    char *ptr = strtok(s1, " "); // " " 공백 문자를 기준으로 문자열을 자름, 포인터 반환

    while (ptr != NULL) // 자른 문자열이 나오지 않을 때까지 반복
    {
        printf("%s\n", ptr);     // 자른 문자열 출력
        ptr = strtok(NULL, " "); // 다음 문자열을 잘라서 포인터를 반환
    }

    return 0;
}

// The
// Little
// Prince


  • strtok 함수는 지정된 문자를 기준으로 문자열을 자른다.
  • 즉, strtok(s1, " ");와 같이 " "(공백 문자)를 넣어주면 공백으로 구분하여 문자열을 자른다.
  • 단, 기준 문자를 ' '로 묶으면 안 된다.
  • 특히 strtok 함수는 잘린 문자열을 한 번에 얻을 수 없어서 while 반복문으로 문자열을 계속 자르다가 문자열이 나오지 않으면 반복문을 끝내는 방식으로 사용한다.


char *ptr = strtok(s1, " "); // " " 공백 문자를 기준으로 문자열을 자름, 포인터 반환

while (ptr != NULL) // 자른 문자열이 나오지 않을 때까지 반복
{
    printf("%s\n", ptr);     // 자른 문자열 출력
    ptr = strtok(NULL, " "); // 다음 문자열을 잘라서 포인터를 반환
}


  • 여기서 while 반복문 안의 strtok 함수는 ptr = strtok(NULL, " ");처럼 자를 문자열 부분에 NULL을 넣어준다.
  • 자세히 설명하자면 NULL을 넣었을 때는 직전 strtok 함수에서 처리했던 문자열에서 잘린 문자열만큼 다음 문자로 이동한 뒤 다음 문자열을 자른다.
  • 만약 ptr = strtok(ptr, " ");처럼 잘린 문자열의 포인터를 다시 넣었을 때는 다음 문자로 이동하지 못하고 처음에 나오는 문자열만 계속 자르게 된다.


The
The
The
The
...


  • strtok 함수를 사용할 때는 처음에만 자를 문자열을 넣어주고, 그다음부터는 NULL을 넣어준다는 점을 기억하자.


  • 다음은 strtok 함수의 동작 순서이다.
  • 먼저 처음 호출되는 strtok" "를 찾아서 NULL로 채운 뒤 문자열의 첫 부분인 "The"를 자른다.


001


  • 이제 while 반복문 안의 strtokNULL을 넣어주어 앞에서 잘린 문자열만큼 다음 문자로 이동한다.
  • 그리고 다시 " "를 찾아서 NULL로 채운 뒤 "Little"을 자른다.
  • 하지만 아직 문자열 끝에 있는 NULL을 만나지 못했으므로 계속 반복한다.


002


  • 다시 strtokNULL을 넣어주어 앞에서 잘린 문자열만큼 다음 문자로 이동한다.
  • 이번에는 " "가 아닌 문자열 마지막의 NULL을 만났으므로 NULL을 그대로 두고 "Prince"를 반환한다.


003


  • 직전 strtok에서 " "를 만나지 못했으므로 더이상 자를 문자열이 없다.
  • 따라서 NULL을 반환하고 while 반복문을 끝낸다.


004


  • strtok 함수는 문자열을 새로 생성해서 반환하는 것이 아니라 자르는 부분을 NULL로 채운 뒤 잘린 문자열의 포인터를 반환한다.
  • 따라서 원본 문자열의 내용을 바꾸므로 사용에 주의해야 한다.


4. 문자열 포인터 자르기

  • 다음과 같이 문자열 포인터에 문자열 리터럴이 들어있어서 읽기 전용인 상태라면 strtok 함수를 사용할 수 없다.


char *s1 = "The Little Prince"; // 포인터에 문자열 리터럴 "The Little Prince"의 주소 저장

char *ptr = strtok(s1, " "); // 실행 에러

while (ptr != NULL)
{
   printf("%s\n", ptr);
   ptr = strtok(NULL, " ");
}


  • 문자열 포인터에 문자열 리터럴을 할당하는 대신 동적 메모리를 할당하고, 문자열을 복사하면 이 문제를 해결할 수 있다.


char *s1 = malloc(sizeof(char) * 30); // char 타입 30개 크기만큼 동적 메모리 할당

strcpy(s1, "The Little Prince"); // s1에 문자열 복사

char *ptr = strtok(s1, " "); // 동적 메모리에 들어있는 문자열은 자를 수 있음

while (ptr != NULL)
{
   printf("%s\n", ptr);
   ptr = strtok(NULL, " ");
}

free(s1); // 동적 메모리 해제


5. 날짜와 시간값 자르기

  • strtok 함수는 " "뿐만 아니라 다양한 특수 문자와 알파벳 영문자를 기준으로 문자열을 자를 수 있다.
  • 특히 기준 문자는 한 번에 여러 개를 지정할 수 있다.


  • 다음은 연-월-일T시:분:초 형식으로 된 문자열 "2015-06-10T15:32:19"을 잘라서 "2015", "06", "10", "15", "32", "19"를 각 줄에 출력한다.


#define _CRT_SECURE_NO_WARNINGS // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strtok 함수가 선언된 헤더 파일

int main()
{
    char s1[30] = "2015-06-10T15:32:19"; // 크기가 30인 char 타입 배열을 선언하고 문자열 할당

    char *ptr = strtok(s1, "-T:"); // -, T, 콜론을 기준으로 문자열을 자름, 포인터 반환

    while (ptr != NULL) // 자른 문자열이 나오지 않을 때까지 반복
    {
        printf("%s\n", ptr);       // 자른 문자열 출력
        ptr = strtok(NULL, "-T:"); // 다음 문자열을 잘라서 포인터를 반환
    }

    return 0;
}

// 2015
// 06
// 10
// 15
// 32
// 19


  • -, T, :(콜론)을 기준으로 문자열을 자르므로 "-T:"와 같이 기준 문자를 여러 개 넣었다.


6. 자른 문자열 보관하기

  • 지금까지 자른 문자열을 출력만 하고 끝냈다.
  • 하지만 실제로 프로그램을 작성할 때는 자른 문자열을 보관했다가 계속 사용하는 경우가 많다.
  • 예를 들어 문자열을 자른 뒤 다른 코드를 실행하고, 자른 문자열을 사용한다든지 자른 문자열을 또 다른 처리에 사용한다든지 하는 상황이 생길 수 있다.
  • 즉, 문자열을 자르는 while 반복문 안에서 모든 처리를 할 수 없는 상황에서는 자른 문자열을 보관할 필요가 없다.


  • 이번에는 문자열 포인터 배열에 자른 문자열을 보관해 보자.


#define _CRT_SECURE_NO_WARNINGS // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strtok 함수가 선언된 헤더 파일

int main()
{
    char s1[30] = "The Little Prince"; // 크기가 30인 char 타입 배열을 선언하고 문자열 할당
    char *sArr[10] = {
        // 크기가 10인 문자열 포인터 배열을 선언하고 NULL로 초기화
        NULL,
    };
    int i = 0; // 문자열 포인터 배열의 인덱스로 사용할 변수

    char *ptr = strtok(s1, " "); // 공백 문자열을 기준으로 문자열을 자름

    while (ptr != NULL) // 자른 문자열이 나오지 않을 때까지 반복
    {
        sArr[i] = ptr; // 문자열을 자른 뒤 메모리 주소를 문자열 포인터 배열에 저장
        i++;           // 인덱스 증가

        ptr = strtok(NULL, " "); // 다음 문자열을 잘라서 포인터를 반환
    }

    for (int i = 0; i < 10; i++)
    {
        if (sArr[i] != NULL) // 문자열 포인터 배열의 요소가 NULL이 아닐 때만
        {
            printf("%s\n", sArr[i]); // 문자열 포인터 배열에 인덱스로 접근하여 각 문자열 출력
        }
    }

    return 0;
}

// The
// Little
// Prince


  • 먼저 char *sArr[10] = {NULL,};과 같이 자른 문자열을 보관할 문자열 포인터 배열을 선언하고 NULL로 초기화했다.
  • 여기서 NULL 뒤에 ,(콤마)를 붙여주면 배열의 모든 요소가 NULL로 초기화된다.
  • while 반복문 안에서는 sArr[i] = ptr;과 같이 자른 문자열의 메모리 주소를 배열에 저장하고, 배열의 인덱스를 증가시킨다.
  • 포인터 ptr은 반복문을 반복하면서 문자열을 자를 때마다 안에 저장된 메모리 주소가 계속 바뀌므로 나중에 다시 사용할 수 없다.
  • 하지만 예제처럼 ptr에 저장된 메모리 주소가 바뀌기 전에 다른 곳에 보관해 두면 자른 문자열을 나중에도 계속 사용할 수 있다.

References