strtok ()는 C에서 문자열을 토큰으로 어떻게 분할합니까?


114

strtok()기능 작동에 대해 설명해주십시오 . 매뉴얼은 문자열을 토큰으로 나눕니다. 나는 그것이 실제로하는 일을 매뉴얼에서 이해할 수 없다.

에 나는 시계를 추가 str하고 *pch첫 While 루프가 발생했을 때의 작동을 확인의 내용은 str단지 "이"을했다. 아래에 표시된 출력이 화면에 어떻게 인쇄 되었습니까?

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

산출:

문자열 "-이것은 샘플 문자열입니다." 토큰으로 :
이
ㅏ
견본
끈

5
strtok()반환하기 전에 NUL로 토큰을 종료하여 인수 문자열을 수정합니다. 전체 버퍼 (str [])를 검사하려고하면 연속적인 호출 사이에 수정되는 것을 볼 수 strtok()있습니다.
Michael Foukarakis 2010 년

대신 시청 str, 시계 str[0], str[1], str[2], ...
PMG을

@pmg : str [0]과 str [1] .str [1]이 '\ 0'이어야하는 것을 보았지만 거기에 공백이있었습니다.
fuddin 2010 년

3
솔직히 확인하지 않아도되지만 마지막으로 전달 된 포인터와 중단 된 위치를 저장한다고 생각합니다. 그런 다음 포인터가 NULL이면 계속하거나 위치를 지우고 그렇지 않으면 다시 시작할 수 있습니다.
chris

7
@Firegun : 정적 변수 .
DCoder

답변:


38

strtok()문자열을 토큰으로 나눕니다. 즉, 구분자 중 하나에서 다음으로 시작하면 하나의 토큰이됩니다. 귀하의 경우 시작 토큰은 "-"에서 시작하고 다음 공백 ""으로 끝납니다. 다음 토큰은 ""에서 시작하여 ","로 끝납니다. 여기서 "This"를 출력으로 얻습니다. 마찬가지로 나머지 문자열은 공백에서 공백으로 토큰으로 분할되고 마지막으로 "."에서 마지막 토큰이 끝납니다.


하나의 토큰에 대한 종료 조건이 다음 토큰의 시작 토큰이되며 종료 조건 대신에 nul 문자가 배치됩니까?
fuddin 2010 년

1
@ fahad- 예, 다른 사람들이 제안한 것처럼 모든 구분 기호가 NUL 문자로 대체됩니다.
Sachin Shanbhag

모든 구분 기호가 Nul로 바뀌면 왜 문자열에 "-this"가 포함됩니까? "\ 0"을 포함해야합니다.
fuddin

2
@fahad-구분 기호 사이의 모든 문자가 아닌 구분 문자 만 NUL로 바꿉니다. 문자열을 여러 토큰으로 분할하는 것과 같습니다. "This"는 "-this"가 아닌 지정된 두 구분 기호 사이에 있기 때문에 표시됩니다.
Sachin Shanbhag

1
@Fahad-네, 물론입니다. 내가 이해하는 한 모든 공백, ","및 "-"는 구분자로 지정했기 때문에 NUL로 대체됩니다.
Sachin Shanbhag

212

strtok 런타임 함수는 다음과 같이 작동합니다.

strtok를 처음 호출 할 때 토큰화할 문자열을 제공합니다.

char s[] = "this is a string";

위의 문자열 공간에서 단어 사이의 좋은 구분 기호로 보이므로 다음을 사용하십시오.

char* p = strtok(s, " ");

이제 일어나는 일은 공백 문자가 발견 될 때까지 's'가 검색되고 첫 번째 토큰이 반환되고 ( 'this') p가 해당 토큰 (문자열)을 가리 킵니다.

다음 토큰을 얻고 동일한 문자열을 계속하려면 strtok가 이전에 전달 된 문자열에 대한 정적 포인터 를 유지하므로 NULL이 첫 번째 인수로 전달됩니다 .

p = strtok(NULL," ");

p는 이제 'is'를 가리 킵니다.

더 이상 공백을 찾을 수 없을 때까지 계속하면 마지막 문자열이 마지막 토큰 '문자열'로 반환됩니다.

더 편리하게 다음과 같이 작성하여 모든 토큰을 인쇄 할 수 있습니다.

for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
  puts(p);
}

편집하다:

반환 된 값을 저장하려면 strtok토큰을 다른 버퍼에 복사해야합니다. 예를 들어 strdup(p);원래 문자열 (내부 정적 포인터가 가리키는 strtok)이 토큰을 반환하기 위해 반복 사이에 수정되기 때문입니다.


그래서 실제로는 문자열 사이에 nul 문자를 넣지 않습니다. 왜 내 시계는 문자열이 "THIS"로만 남아 있다고 표시합니까?
fuddin 2010 년

4
실제로 발견 한 ''를 '\ 0'으로 대체합니다. 그리고 나중에 ''를 복원하지 않으므로 문자열이 영구적으로 손상됩니다.

33
정적 버퍼 +1, 이것이 내가 이해하지 못했다 무엇인가
IEatBagels

1
"첫 번째 토큰이 반환되고 p해당 토큰을 가리 킵니다 " 줄에서 누락 된 매우 중요한 세부 사항 strtok은 구분 기호 대신 null 문자를 배치하여 원래 문자열을 변경해야 한다는 것입니다 (그렇지 않으면 다른 문자열 함수는 위치를 알 수 없습니다. 토큰 종료). 또한 정적 변수를 사용하여 상태를 추적합니다.
Groo

@Groo 제가 2017 년에 한 편집에 이미 추가 한 것 같지만 당신 말이 맞습니다.
AndersK

25

strtok문자열에서 사용 가능한 다음 토큰을 가리키는 정적 내부 참조를 유지합니다. NULL 포인터를 전달하면 내부 참조에서 작동합니다.

이것이 strtok재진입이 아닌 이유 입니다. 새 포인터를 전달하자마자 이전 내부 참조가 손상됩니다.


오래된 내부 참조 '장해'는 무엇을 의미합니까? '덮어 쓰기'를 의미합니까?
ylun.ca 2015 년

1
@ ylun.ca : 네, 그게 제가 의미하는 바입니다.
John Bode

10

strtok매개 변수 자체를 변경하지 않습니다 ( str). 해당 포인터를 (지역 정적 변수에) 저장합니다. 그런 다음 매개 변수를 다시 전달하지 않고도 후속 호출에서 해당 매개 변수가 가리키는 것을 변경할 수 있습니다 . (그리고 그 포인터를 계속 진행시킬 수 있지만 작업을 수행해야합니다.)

POSIX strtok페이지에서 :

이 함수는 정적 저장소를 사용하여 호출 사이의 현재 문자열 위치를 추적합니다.

strtok_r이러한 유형의 마법을 수행하지 않는 스레드로부터 안전한 변형 ( )이 있습니다.


2
글쎄, C 라이브러리 함수는 스레딩이 전혀 그림에 없었던 때부터 시작되었으므로 (C 표준에 관한 한 2011 년에만 존재하기 시작했습니다) 재진입이 실제로 중요하지 않았습니다 ( 나는 추측한다). 이 정적 로컬은 함수를 "사용하기 쉽게"만듭니다 ( "쉬운"정의를 위해). 마찬가지로 ctime정적 문자열을 반환하지 않습니다 - 실제 (을 해제해야 경이 아무도 요구),하지만 재진입 당신이하지 그것을 잘 알고 있다면 당신을 여행.
Mat

이것은 잘못된 것입니다. " strtok매개 변수 자체를 변경하지 않습니다 ( str)." 수정 puts(str);이후 "-This"를 인쇄합니다 . strtokstr
MarredCheese

1
@MarredCheese : 다시 읽어보세요. 포인터를 수정하지 않습니다. 포인터가 가리키는 데이터 (즉, 문자열 데이터)를 수정합니다
Mat

오 좋아, 그게 당신이 얻는 일인지 몰랐습니다. 동의합니다.
MarredCheese

8

처음 호출 할 때 토큰화할 문자열을 strtok. 그런 다음 다음 토큰을 얻으려면 NULLNULL포인터를 반환하는 한 해당 함수에 제공 하면 됩니다.

strtok함수는 호출 할 때 처음 제공 한 문자열을 기록합니다. (다중 스레드 응용 프로그램에는 정말 위험합니다)


8

strtok는 문자열을 토큰 화합니다. 즉 일련의 하위 문자열로 변환합니다.

이러한 토큰 (또는 하위 문자열)을 구분하는 구분 기호를 검색하여이를 수행합니다. 그리고 구분자를 지정합니다. 귀하의 경우에는 ''또는 ','또는 '.'를 원합니다. 또는 '-'는 구분 기호입니다.

이러한 토큰을 추출하는 프로그래밍 모델은 기본 문자열과 구분 기호 집합을 strtok하는 것입니다. 그런 다음 반복적으로 호출하고 매번 strtok가 찾은 다음 토큰을 반환합니다. null을 반환 할 때 메인 문자열의 끝에 도달 할 때까지. 또 다른 규칙은 처음에만 문자열을 전달하고 이후에는 NULL을 전달하는 것입니다. 이것은 새 문자열로 토큰 화의 새 세션을 시작하거나 이전 토큰 화 세션에서 토큰을 검색하는 경우 strtok에 알리는 방법입니다. strtok는 토큰 화 세션의 상태를 기억합니다. 이러한 이유로 재진입 또는 스레드로부터 안전하지 않습니다 (대신 strtok_r을 사용해야합니다). 알아야 할 또 다른 사항은 실제로 원래 문자열을 수정한다는 것입니다. 찾은 구분 기호에 대해 '\ 0'을 씁니다.

strtok를 간단히 호출하는 한 가지 방법은 다음과 같습니다.

char str[] = "this, is the string - I want to parse";
char delim[] = " ,-";
char* token;

for (token = strtok(str, delim); token; token = strtok(NULL, delim))
{
    printf("token=%s\n", token);
}

결과:

this
is
the
string
I
want
to
parse

5

strtok는 입력 문자열을 수정합니다. 원래 문자열의 비트를 토큰으로 반환하도록 null 문자 ( '\ 0')를 그 안에 배치합니다. 사실 strtok는 메모리를 할당하지 않습니다. 일련의 상자로 문자열을 그리면 더 잘 이해할 수 있습니다.


3

strtok()작동 방식 을 이해하려면 먼저 정적 변수 가 무엇인지 알아야합니다 . 이 링크 는 잘 설명합니다 ....

의 작업의 핵심 strtok()은 seccessive 호출 사이의 마지막 구분자의 위치를 ​​보존하는 것입니다 (이것이 strtok()a로 호출 될 때 전달되는 원래 문자열을 계속 구문 분석하는 이유 입니다.null pointer 연속 호출에서 ).

에서 제공하는 기능과 약간 다른 기능을 가진 strtok()이라는 내 구현을 살펴보십시오.zStrtok()strtok()

char *zStrtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;           /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

그리고 여기에 사용 예가 있습니다.

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zStrtok(s,","));
      printf("2 %s\n",zStrtok(NULL,","));
      printf("3 %s\n",zStrtok(NULL,","));
      printf("4 %s\n",zStrtok(NULL,","));
      printf("5 %s\n",zStrtok(NULL,","));
      printf("6 %s\n",zStrtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

코드는 내가 zString이라는 Github 에서 유지 관리하는 문자열 처리 라이브러리에서 가져온 것입니다. 코드를 보거나 기여하십시오 :) https://github.com/fnoyanisi/zString


3

이것이 내가 strtok를 구현 한 방법입니다. 그다지 좋지는 않지만 2 시간 동안 작업 한 후 마침내 작동했습니다. 여러 구분 기호를 지원합니다.

#include "stdafx.h"
#include <iostream>
using namespace std;

char* mystrtok(char str[],char filter[]) 
{
    if(filter == NULL) {
        return str;
    }
    static char *ptr = str;
    static int flag = 0;
    if(flag == 1) {
        return NULL;
    }
    char* ptrReturn = ptr;
    for(int j = 0; ptr != '\0'; j++) {
        for(int i=0 ; filter[i] != '\0' ; i++) {
            if(ptr[j] == '\0') {
                flag = 1;
                return ptrReturn;
            }
            if( ptr[j] == filter[i]) {
                ptr[j] = '\0';
                ptr+=j+1;
                return ptrReturn;
            }
        }
    }
    return NULL;
}

int _tmain(int argc, _TCHAR* argv[])
{
    char str[200] = "This,is my,string.test";
    char *ppt = mystrtok(str,", .");
    while(ppt != NULL ) {
        cout<< ppt << endl;
        ppt = mystrtok(NULL,", ."); 
    }
    return 0;
}


1

다음은 구분 기호에 해시 테이블을 사용하는 구현입니다. 즉, O (n ^ 2) 대신 O (n)을 의미합니다 (여기 코드에 대한 링크가 있습니다) .

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define DICT_LEN 256

int *create_delim_dict(char *delim)
{
    int *d = (int*)malloc(sizeof(int)*DICT_LEN);
    memset((void*)d, 0, sizeof(int)*DICT_LEN);

    int i;
    for(i=0; i< strlen(delim); i++) {
        d[delim[i]] = 1;
    }
    return d;
}



char *my_strtok(char *str, char *delim)
{

    static char *last, *to_free;
    int *deli_dict = create_delim_dict(delim);

    if(!deli_dict) {
        /*this check if we allocate and fail the second time with entering this function */
        if(to_free) {
            free(to_free);
        }
        return NULL;
    }

    if(str) {
        last = (char*)malloc(strlen(str)+1);
        if(!last) {
            free(deli_dict);
            return NULL;
        }
        to_free = last;
        strcpy(last, str);
    }

    while(deli_dict[*last] && *last != '\0') {
        last++;
    }
    str = last;
    if(*last == '\0') {
        free(deli_dict);
        free(to_free);
        deli_dict = NULL;
        to_free = NULL;
        return NULL;
    }
    while (*last != '\0' && !deli_dict[*last]) {
        last++;
    }

    *last = '\0';
    last++;

    free(deli_dict);
    return str;
}

int main()
{
    char * str = "- This, a sample string.";
    char *del = " ,.-";
    char *s = my_strtok(str, del);
    while(s) {
        printf("%s\n", s);
        s = my_strtok(NULL, del);
    }
    return 0;
}

1

strtok ()는 마지막으로 중단 한 위치에 정적 변수에 포인터를 저장하므로 두 번째 호출에서 null을 전달할 때 strtok ()는 정적 변수에서 포인터를 가져옵니다.

동일한 문자열 이름을 제공하면 처음부터 다시 시작됩니다.

더욱이 strtok ()는 파괴적입니다. 즉, 원래 문자열을 변경합니다. 따라서 항상 원래의 사본을 가지고 있는지 확인하십시오.

strtok () 사용의 또 다른 문제는 주소를 정적 변수에 저장하므로 다중 스레드 프로그래밍에서 strtok ()를 두 번 이상 호출하면 오류가 발생한다는 것입니다. 이를 위해 strtok_r ()을 사용하십시오.


0

여전히이 strtok()함수를 이해하는 데 어려움을 겪고있는 사람들을 위해이 pythontutor 예제를 살펴 보십시오. C (또는 C ++, Python ...) 코드를 시각화하는 훌륭한 도구입니다.

링크가 끊어진 경우 다음을 붙여 넣으십시오.

#include <stdio.h>
#include <string.h>

int main()
{
    char s[] = "Hello, my name is? Matthew! Hey.";
    char* p;
    for (char *p = strtok(s," ,?!."); p != NULL; p = strtok(NULL, " ,?!.")) {
      puts(p);
    }
    return 0;
}

크레딧은 Anders K 로 이동합니다 .


0

토큰을 찾은 경우 문자 배열을 스캔하여 새 줄을 인쇄하고 문자를 인쇄 할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *s;
    s = malloc(1024 * sizeof(char));
    scanf("%[^\n]", s);
    s = realloc(s, strlen(s) + 1);
    int len = strlen(s);
    char delim =' ';
    for(int i = 0; i < len; i++) {
        if(s[i] == delim) {
            printf("\n");
        }
        else {
            printf("%c", s[i]);
        }
    }
    free(s);
    return 0;
}

0

따라서 이것은이 주제를 더 잘 이해하는 데 도움이되는 코드 조각입니다.

토큰 인쇄

과제 : 주어진 문장, s, 문장의 각 단어를 새 줄에 인쇄하십시오.

char *s;
s = malloc(1024 * sizeof(char));
scanf("%[^\n]", s);
s = realloc(s, strlen(s) + 1);
//logic to print the tokens of the sentence.
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
    printf("%s\n",p);
}

입력: How is that

결과:

How
is
that

설명 : 여기에서 "strtok ()"함수를 사용하고 for 루프를 사용하여 반복하여 토큰을 별도의 줄로 인쇄합니다.

이 함수는 매개 변수를 '문자열'및 '중단 점'으로 취하고 해당 중단 점에서 문자열을 분리하고 토큰을 형성합니다. 이제 이러한 토큰은 'p'에 저장되고 인쇄에 더 많이 사용됩니다.


예를 들어 설명하는 것이 문서를 참조하는 것보다 훨씬 낫다고 생각합니다.
tr_abhishek
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.