C에서 구분 기호로 문자열 분리


155

C 프로그래밍 언어에서 구분 기호가있는 문자열의 배열을 분할하고 반환하는 함수를 작성하는 방법은 무엇입니까?

char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');

25
strtok표준 라이브러리 의 함수를 사용 하여 동일한 결과를 얻을 수 있습니다 .
Daniel Kamil Kozar


주석 ... strtok()가족 기능 의 핵심 static variables은 C에서 이해하는 것 입니다. 즉, 그것이 사용되는 연속적인 함수 호출 사이에서 어떻게 동작하는지. 아래 코드를 참조하십시오
fnisi

답변:


165

strtok()함수를 사용하여 문자열을 분할하고 사용할 구분자를 지정할 수 있습니다. 참고 strtok()문자열을 수정합니다 그것으로 통과 시켰습니다. 다른 곳에서 원본 문자열이 필요한 경우 사본을 만들어 사본을strtok() .

편집하다:

예 (예 : 연속 분리 문자 "JAN ,,, FEB, MAR"은 처리하지 않음) :

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

char** str_split(char* a_str, const char a_delim)
{
    char** result    = 0;
    size_t count     = 0;
    char* tmp        = a_str;
    char* last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;

    /* Count how many elements will be extracted. */
    while (*tmp)
    {
        if (a_delim == *tmp)
        {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }

    /* Add space for trailing token. */
    count += last_comma < (a_str + strlen(a_str) - 1);

    /* Add space for terminating null string so caller
       knows where the list of returned strings ends. */
    count++;

    result = malloc(sizeof(char*) * count);

    if (result)
    {
        size_t idx  = 0;
        char* token = strtok(a_str, delim);

        while (token)
        {
            assert(idx < count);
            *(result + idx++) = strdup(token);
            token = strtok(0, delim);
        }
        assert(idx == count - 1);
        *(result + idx) = 0;
    }

    return result;
}

int main()
{
    char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    char** tokens;

    printf("months=[%s]\n\n", months);

    tokens = str_split(months, ',');

    if (tokens)
    {
        int i;
        for (i = 0; *(tokens + i); i++)
        {
            printf("month=[%s]\n", *(tokens + i));
            free(*(tokens + i));
        }
        printf("\n");
        free(tokens);
    }

    return 0;
}

산출:

$ ./main.exe
months=[JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC]

month=[JAN]
month=[FEB]
month=[MAR]
month=[APR]
month=[MAY]
month=[JUN]
month=[JUL]
month=[AUG]
month=[SEP]
month=[OCT]
month=[NOV]
month=[DEC]

60
안녕하세요! 은 strtok에 의해 폐기로 표시되어 strsep(3)매뉴얼 페이지.
osgx 2016 년

4
이것이 스택 오버플로에 대한 정식 질문 / 답변 일 수 있으므로 strtok을 사용한 멀티 스레딩과 관련하여 몇 가지주의 사항이 있습니까?
Peter Mortensen

3
@osgx 해당 페이지에 따르면를 strsep대신 한 strtok것이지만 strtok이식성을 선호합니다. 따라서 빈 필드를 지원하거나 여러 문자열을 한 번에 분할하지 않는 strtok한 더 나은 선택입니다.

4
@Dojo : 기억합니다. 그것이 문제가되는 이유 중 하나입니다. plain보다 strtok_s()(Microsoft, C11 Annex K, 옵션) 또는 strtok_r()(POSIX) 를 사용하는 것이 좋습니다 strtok(). strtok()도서관 기능에서 평범한 것은 악하다. 라이브러리 함수를 호출하는 함수는 strtok()당시에 사용 중이 아니며 라이브러리 함수가 호출 한 함수는을 호출 할 수 없습니다 strtok().
Jonathan Leffler

3
strtok()스레드 안전하지 않은 메모 (@JonathanLeffler가 언급 한 이유로) 때문에이 모든 기능은 스레드 안전하지 않습니다. 트레드 환경에서이를 사용하려고하면 불규칙하고 예측할 수없는 결과가 나타납니다. 교체 strtok()를 위해 strtok_r()이 문제 수정.
Sean W

70

나는 이것이 strsep여전히 가장 좋은 도구 라고 생각 합니다.

while ((token = strsep(&str, ","))) my_fn(token);

그것은 말 그대로 문자열을 나누는 한 줄입니다.

추가 괄호는 문체 요소로, 항등 연산자가 아닌 할당 결과를 의도적으로 테스트하고 있음을 나타냅니다 ==.

그 일에 패턴의 경우, tokenstr두 유형이 char *. 문자열 리터럴로 시작한 경우 먼저 복사본을 만들고 싶을 것입니다.

// More general pattern:
const char *my_str_literal = "JAN,FEB,MAR";
char *token, *str, *tofree;

tofree = str = strdup(my_str_literal);  // We own str's memory now.
while ((token = strsep(&str, ","))) my_fn(token);
free(tofree);

에 두 개의 구분 기호가 함께 표시 되면 빈 문자열 인 값을 str얻게 token됩니다. 의 가치str발견 된 각 구분 기호가 0 바이트로 겹쳐 쓰여지도록 이 수정됩니다. 이는 먼저 구문 분석되는 문자열을 복사해야하는 또 다른 이유입니다.

한 의견에서는 누군가 가 휴대 하기 쉽기 때문에 strtok더 낫다고 제안했습니다 . 우분투와 맥 OS X는 ; 다른 유닉스 시스템도 마찬가지라고 추측하는 것이 안전합니다. Windows에는 부족 하지만 다음 과 같이 짧고 달콤한 교체가 가능합니다.strsepstrtokstrsepstrsepstrbrkstrsep

char *strsep(char **stringp, const char *delim) {
  if (*stringp == NULL) { return NULL; }
  char *token_start = *stringp;
  *stringp = strpbrk(token_start, delim);
  if (*stringp) {
    **stringp = '\0';
    (*stringp)++;
  }
  return token_start;
}

다음strsepvs에 대한 좋은 설명입니다 strtok. 장단점은 주관적으로 판단 될 수 있습니다. 그러나 나는 이것이 strsep대체를 위해 고안된 신호라고 생각합니다 strtok.


3
이식성에 대해 더 정확하게 : POSIX 7 이 아니라 BSD가 파생되어 glibc에 구현되었습니다 .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

방금 물어 보려고했는데 ... Pelle의 C에는 strdup ()이 있지만 strsep ()는 없습니다.
rdtsc 2012

1
tofree자유롭지 str않습니까?
Sdlion

1
str에 대한 호출로 값을 변경할 수 있으므로 해제 할 수 없습니다 strsep(). 값은 tofree지속적으로 사용 가능한 메모리의 시작을 가리 킵니다.
Tyler

26

String tokenizer이 코드는 올바른 방향을 제시해야합니다.

int main(void) {
  char st[] ="Where there is will, there is a way.";
  char *ch;
  ch = strtok(st, " ");
  while (ch != NULL) {
  printf("%s\n", ch);
  ch = strtok(NULL, " ,");
  }
  getch();
  return 0;
}

13

아래 방법은 모든 작업 (메모리 할당, 길이 계산)을 수행합니다. 자세한 정보 및 설명은 여기에서 찾을 수 있습니다 -C 문자열을 분할하기위한 Java String.split () 메소드 구현

int split (const char *str, char c, char ***arr)
{
    int count = 1;
    int token_len = 1;
    int i = 0;
    char *p;
    char *t;

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
            count++;
        p++;
    }

    *arr = (char**) malloc(sizeof(char*) * count);
    if (*arr == NULL)
        exit(1);

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
        {
            (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
            if ((*arr)[i] == NULL)
                exit(1);

            token_len = 0;
            i++;
        }
        p++;
        token_len++;
    }
    (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
    if ((*arr)[i] == NULL)
        exit(1);

    i = 0;
    p = str;
    t = ((*arr)[i]);
    while (*p != '\0')
    {
        if (*p != c && *p != '\0')
        {
            *t = *p;
            t++;
        }
        else
        {
            *t = '\0';
            i++;
            t = ((*arr)[i]);
        }
        p++;
    }

    return count;
}

사용 방법:

int main (int argc, char ** argv)
{
    int i;
    char *s = "Hello, this is a test module for the string splitting.";
    int c = 0;
    char **arr = NULL;

    c = split(s, ' ', &arr);

    printf("found %d tokens.\n", c);

    for (i = 0; i < c; i++)
        printf("string #%d: %s\n", i, arr[i]);

    return 0;
}

4
Huh Three star Programmer :)) 재미 있겠다.
Michi

이 작업을 수행하면 마지막 토큰에 너무 많이 추가되거나 너무 많은 메모리가 할당됩니다. found 10 tokens. string #0: Hello, string #1: this string #2: is string #3: a string #4: test string #5: module string #6: for string #7: the string #8: string string #9: splitting.¢
KeizerHarm

2
이 예에는 여러 메모리 누수가 있습니다. 이 글을 읽는 사람에게는이 방법을 사용하지 마십시오. 대신 strtok 또는 strsep 토큰 화 방식을 선호하십시오.
Jorma Rebane

7

여기 내 두 센트가 있습니다 :

int split (const char *txt, char delim, char ***tokens)
{
    int *tklen, *t, count = 1;
    char **arr, *p = (char *) txt;

    while (*p != '\0') if (*p++ == delim) count += 1;
    t = tklen = calloc (count, sizeof (int));
    for (p = (char *) txt; *p != '\0'; p++) *p == delim ? *t++ : (*t)++;
    *tokens = arr = malloc (count * sizeof (char *));
    t = tklen;
    p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
    while (*txt != '\0')
    {
        if (*txt == delim)
        {
            p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
            txt++;
        }
        else *p++ = *txt++;
    }
    free (tklen);
    return count;
}

용법:

char **tokens;
int count, i;
const char *str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

count = split (str, ',', &tokens);
for (i = 0; i < count; i++) printf ("%s\n", tokens[i]);

/* freeing tokens */
for (i = 0; i < count; i++) free (tokens[i]);
free (tokens);

3
오, 세 개의 포인터! 나는 이미 그것을 사용하는 것이 무섭다. 나는 c의 포인터가 좋지 않다.
Hafiz Temuri

고맙습니다. 많은 노력 후에도 strtok 답변이 위의 경우 모두 작동하지 않았으며 코드는 매력처럼 작동합니다!
hmmftg

4

위의 예에서 문자열에서 원하는대로 널 종료 문자열 배열을 반환하는 방법이 있습니다. 함수에 의해 수정되어야하므로 리터럴 문자열을 전달할 수는 없습니다.

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

char** str_split( char* str, char delim, int* numSplits )
{
    char** ret;
    int retLen;
    char* c;

    if ( ( str == NULL ) ||
        ( delim == '\0' ) )
    {
        /* Either of those will cause problems */
        ret = NULL;
        retLen = -1;
    }
    else
    {
        retLen = 0;
        c = str;

        /* Pre-calculate number of elements */
        do
        {
            if ( *c == delim )
            {
                retLen++;
            }

            c++;
        } while ( *c != '\0' );

        ret = malloc( ( retLen + 1 ) * sizeof( *ret ) );
        ret[retLen] = NULL;

        c = str;
        retLen = 1;
        ret[0] = str;

        do
        {
            if ( *c == delim )
            {
                ret[retLen++] = &c[1];
                *c = '\0';
            }

            c++;
        } while ( *c != '\0' );
    }

    if ( numSplits != NULL )
    {
        *numSplits = retLen;
    }

    return ret;
}

int main( int argc, char* argv[] )
{
    const char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

    char* strCpy;
    char** split;
    int num;
    int i;

    strCpy = malloc( strlen( str ) * sizeof( *strCpy ) );
    strcpy( strCpy, str );

    split = str_split( strCpy, ',', &num );

    if ( split == NULL )
    {
        puts( "str_split returned NULL" );
    }
    else
    {
        printf( "%i Results: \n", num );

        for ( i = 0; i < num; i++ )
        {
            puts( split[i] );
        }
    }

    free( split );
    free( strCpy );

    return 0;
}

아마 더 깔끔한 방법이 있지만 아이디어를 얻습니다.


3

이 함수는 char * 문자열을 가져 와서 델리 미네 이터로 나눕니다. 한 줄에 여러 개의 델리 미네 이터가있을 수 있습니다. 이 함수는 orignal 문자열을 수정합니다. 원본을 변경하지 않고 유지하려면 먼저 원본 문자열을 복사해야합니다. 이 함수는 cstring 함수 호출을 사용하지 않으므로 다른 함수보다 약간 빠를 수 있습니다. 메모리 할당에 신경 쓰지 않는다면, strlen (src_str) / 2 크기로 함수 상단에 sub_string을 할당 할 수 있으며 (c ++ "version"과 같이) 함수의 하단을 건너 뜁니다. 이렇게하면 함수가 O (N)으로 감소하지만 아래에 표시된 메모리 최적화 방식은 O (2N)입니다.

함수:

char** str_split(char *src_str, const char deliminator, size_t &num_sub_str){
  //replace deliminator's with zeros and count how many
  //sub strings with length >= 1 exist
  num_sub_str = 0;
  char *src_str_tmp = src_str;
  bool found_delim = true;
  while(*src_str_tmp){
    if(*src_str_tmp == deliminator){
      *src_str_tmp = 0;
      found_delim = true;
    }
    else if(found_delim){ //found first character of a new string
      num_sub_str++;
      found_delim = false;
      //sub_str_vec.push_back(src_str_tmp); //for c++
    }
    src_str_tmp++;
  }
  printf("Start - found %d sub strings\n", num_sub_str);
  if(num_sub_str <= 0){
    printf("str_split() - no substrings were found\n");
    return(0);
  }

  //if you want to use a c++ vector and push onto it, the rest of this function
  //can be omitted (obviously modifying input parameters to take a vector, etc)

  char **sub_strings = (char **)malloc( (sizeof(char*) * num_sub_str) + 1);
  const char *src_str_terminator = src_str_tmp;
  src_str_tmp = src_str;
  bool found_null = true;
  size_t idx = 0;
  while(src_str_tmp < src_str_terminator){
    if(!*src_str_tmp) //found a NULL
      found_null = true;
    else if(found_null){
      sub_strings[idx++] = src_str_tmp;
      //printf("sub_string_%d: [%s]\n", idx-1, sub_strings[idx-1]);
      found_null = false;
    }
    src_str_tmp++;
  }
  sub_strings[num_sub_str] = NULL;

  return(sub_strings);
}

사용 방법:

  char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char *str = strdup(months);
  size_t num_sub_str;
  char **sub_strings = str_split(str, ',', num_sub_str);
  char *endptr;
  if(sub_strings){
    for(int i = 0; sub_strings[i]; i++)
      printf("[%s]\n", sub_strings[i]);
  }
  free(sub_strings);
  free(str);

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

/**
 *  splits str on delim and dynamically allocates an array of pointers.
 *
 *  On error -1 is returned, check errno
 *  On success size of array is returned, which may be 0 on an empty string
 *  or 1 if no delim was found.  
 *
 *  You could rewrite this to return the char ** array instead and upon NULL
 *  know it's an allocation problem but I did the triple array here.  Note that
 *  upon the hitting two delim's in a row "foo,,bar" the array would be:
 *  { "foo", NULL, "bar" } 
 * 
 *  You need to define the semantics of a trailing delim Like "foo," is that a
 *  2 count array or an array of one?  I choose the two count with the second entry
 *  set to NULL since it's valueless.
 *  Modifies str so make a copy if this is a problem
 */
int split( char * str, char delim, char ***array, int *length ) {
  char *p;
  char **res;
  int count=0;
  int k=0;

  p = str;
  // Count occurance of delim in string
  while( (p=strchr(p,delim)) != NULL ) {
    *p = 0; // Null terminate the deliminator.
    p++; // Skip past our new null
    count++;
  }

  // allocate dynamic array
  res = calloc( 1, count * sizeof(char *));
  if( !res ) return -1;

  p = str;
  for( k=0; k<count; k++ ){
    if( *p ) res[k] = p;  // Copy start of string
    p = strchr(p, 0 );    // Look for next null
    p++; // Start of next string
  }

  *array = res;
  *length = count;

  return 0;
}

char str[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,";

int main() {
  char **res;
  int k=0;
  int count =0;
  int rc;

  rc = split( str, ',', &res, &count );
  if( rc ) {
    printf("Error: %s errno: %d \n", strerror(errno), errno);
  }

  printf("count: %d\n", count );
  for( k=0; k<count; k++ ) {
    printf("str: %s\n", res[k]);
  }

  free(res );
  return 0;
}

3

아래는 내 strtok() zString 라이브러리 에서 구현 한 입니다. 연속적인 구분자를 처리하는 방식 zstring_strtok()에서 표준 라이브러리와 다릅니다 strtok().

아래 코드를 살펴보고 작동 방식에 대한 아이디어를 얻으십시오 (가능한 한 많은 주석을 사용하려고했습니다)

char *zstring_strtok(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",zstring_strtok(s,","));
      printf("2 %s\n",zstring_strtok(NULL,","));
      printf("3 %s\n",zstring_strtok(NULL,","));
      printf("4 %s\n",zstring_strtok(NULL,","));
      printf("5 %s\n",zstring_strtok(NULL,","));
      printf("6 %s\n",zstring_strtok(NULL,","));

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

라이브러리는 Github https://github.com/fnoyanisi/zString 에서 다운로드 할 수 있습니다.


좋은 것! 그것이 내가 찾던 것입니다.
Kostia Kim

3

다음 솔루션이 이상적이라고 생각합니다.

  • 소스 문자열을 파괴하지 않습니다
  • 재진입-즉, 하나 이상의 스레드에서 어디서나 안전하게 호출 할 수 있습니다.
  • 가지고 다닐 수 있는
  • 여러 구분 기호를 올바르게 처리
  • 빠르고 효율적인

코드 설명 :

  1. token토큰의 주소와 길이를 저장 하는 구조 정의
  2. 최악의 경우 이것에 충분한 메모리를 할당하십시오. 이는 str완전히 분리 자로 구성되어 strlen(str) + 1 토큰 이 있으므로 모두 빈 문자열입니다.
  3. 주사 str모든 토큰의 주소와 길이를 기록하는
  4. 이것을 사용하여 정확한 크기의 출력 배열을 할당하십시오. NULL센티넬 값을
  5. , 할당 복사 시작과 길이 정보를 사용하여 토큰을 추가 - 사용 memcpy보다 더 빨리 그것의 등을strcpy 합니다.
  6. 토큰 주소와 길이 배열을 비 웁니다
  7. 토큰 배열을 반환
typedef struct {
    const char *start;
    size_t len;
} token;

char **split(const char *str, char sep)
{
    char **array;
    unsigned int start = 0, stop, toks = 0, t;
    token *tokens = malloc((strlen(str) + 1) * sizeof(token));
    for (stop = 0; str[stop]; stop++) {
        if (str[stop] == sep) {
            tokens[toks].start = str + start;
            tokens[toks].len = stop - start;
            toks++;
            start = stop + 1;
        }
    }
    /* Mop up the last token */
    tokens[toks].start = str + start;
    tokens[toks].len = stop - start;
    toks++;
    array = malloc((toks + 1) * sizeof(char*));
    for (t = 0; t < toks; t++) {
        /* Calloc makes it nul-terminated */
        char *token = calloc(tokens[t].len + 1, 1);
        memcpy(token, tokens[t].start, tokens[t].len);
        array[t] = token;
    }
    /* Add a sentinel */
    array[t] = NULL; 
    free(tokens);
    return array;
}

malloc 검사는 간결함을 위해 생략.

일반적으로 char *분할 함수에서 포인터 배열을 반환하지 않습니다 . 호출자를 올바르게 해제하는 데 많은 책임이 있습니다. 내가 선호하는 인터페이스는 여기에 설명 된 것처럼 호출자가 콜백 함수를 전달하고 모든 토큰에 대해 이것을 호출하도록 허용하는 것 입니다 .C에서 문자열 분할 .


구분 기호를 두 번 스캔하는 것은 잠재적으로 큰 배열을 할당하는 것보다 좋습니다 token.
chqrlie

2

이것을 사용해보십시오.

char** strsplit(char* str, const char* delim){
    char** res = NULL;
    char*  part;
    int i = 0;

    char* aux = strdup(str);

    part = strdup(strtok(aux, delim));

    while(part){
        res = (char**)realloc(res, (i + 1) * sizeof(char*));
        *(res + i) = strdup(part);

        part = strdup(strtok(NULL, delim));
        i++;
    }

    res = (char**)realloc(res, i * sizeof(char*));
    *(res + i) = NULL;

    return res;
}

2

이 최적화 된 메소드는 * result에서 포인터 배열을 작성 (또는 기존 갱신)하고 * count의 요소 수를 리턴합니다.

"max"를 사용하여 예상되는 최대 문자열 수를 나타내십시오 (기존 배열 또는 다른 reaseon을 지정할 때). 그렇지 않으면 0으로 설정하십시오.

구분 기호 목록과 비교하려면 delim을 char *로 정의하고 행을 바꾸십시오.

if (str[i]==delim) {

다음 두 줄로 :

 char *c=delim; while(*c && *c!=str[i]) c++;
 if (*c) {

즐겨

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

char **split(char *str, size_t len, char delim, char ***result, unsigned long *count, unsigned long max) {
  size_t i;
  char **_result;

  // there is at least one string returned
  *count=1;

  _result= *result;

  // when the result array is specified, fill it during the first pass
  if (_result) {
    _result[0]=str;
  }

  // scan the string for delimiter, up to specified length
  for (i=0; i<len; ++i) {

    // to compare against a list of delimiters,
    // define delim as a string and replace 
    // the next line:
    //     if (str[i]==delim) {
    //
    // with the two following lines:
    //     char *c=delim; while(*c && *c!=str[i]) c++;
    //     if (*c) {
    //       
    if (str[i]==delim) {

      // replace delimiter with zero
      str[i]=0;

      // when result array is specified, fill it during the first pass
      if (_result) {
        _result[*count]=str+i+1;
      }

      // increment count for each separator found
      ++(*count);

      // if max is specified, dont go further
      if (max && *count==max)  {
        break;
      }

    }
  }

  // when result array is specified, we are done here
  if (_result) {
    return _result;
  }

  // else allocate memory for result
  // and fill the result array                                                                                    

  *result=malloc((*count)*sizeof(char*));
  if (!*result) {
    return NULL;
  }
  _result=*result;

  // add first string to result
  _result[0]=str;

  // if theres more strings
  for (i=1; i<*count; ++i) {

    // find next string
    while(*str) ++str;
    ++str;

    // add next string to result
    _result[i]=str;

  }

  return _result;
}  

사용 예 :

#include <stdio.h>

int main(int argc, char **argv) {
  char *str="JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char **result=malloc(6*sizeof(char*));
  char **result2=0;
  unsigned long count;
  unsigned long count2;
  unsigned long i;

  split(strdup(str),strlen(str),',',&result,&count,6);
  split(strdup(str),strlen(str),',',&result2,&count2,0);

  if (result)
  for (i=0; i<count; ++i) {
    printf("%s\n",result[i]);
  }

  printf("\n");

  if (result2)
  for (i=0; i<count2; ++i) {
    printf("%s\n", result2[i]);
  }

  return 0;

}

2

내 버전 :

int split(char* str, const char delimeter, char*** args) {
    int cnt = 1;
    char* t = str;

    while (*t == delimeter) t++;

    char* t2 = t;
    while (*(t2++))
        if (*t2 == delimeter && *(t2 + 1) != delimeter && *(t2 + 1) != 0) cnt++;

    (*args) = malloc(sizeof(char*) * cnt);

    for(int i = 0; i < cnt; i++) {
        char* ts = t;
        while (*t != delimeter && *t != 0) t++;

        int len = (t - ts + 1);
        (*args)[i] = malloc(sizeof(char) * len);
        memcpy((*args)[i], ts, sizeof(char) * (len - 1));
        (*args)[i][len - 1] = 0;

        while (*t == delimeter) t++;
    }

    return cnt;
}

2

다중 문자 분리 문자를 처리 할 수있는 문자열 분할 기능입니다. 구분 기호가 분할되고있는 문자열보다 긴 경우 해당 주 bufferstringLengths설정됩니다 (void *) 0, 그리고 numStrings으로 설정됩니다 0.

이 알고리즘은 테스트되었으며 작동합니다. (면책 조항 : 비 ASCII 문자열에 대해서는 테스트되지 않았으며 호출자가 유효한 매개 변수를 제공했다고 가정합니다)

void splitString(const char *original, const char *delimiter, char ** * buffer, int * numStrings, int * * stringLengths){
    const int lo = strlen(original);
    const int ld = strlen(delimiter);
    if(ld > lo){
        *buffer = (void *)0;
        *numStrings = 0;
        *stringLengths = (void *)0;
        return;
    }

    *numStrings = 1;

    for(int i = 0;i < (lo - ld);i++){
        if(strncmp(&original[i], delimiter, ld) == 0) {
            i += (ld - 1);
            (*numStrings)++;
        }
    }

    *stringLengths = (int *) malloc(sizeof(int) * *numStrings);

    int currentStringLength = 0;
    int currentStringNumber = 0;
    int delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(i < (lo - ld)){
            if(strncmp(&original[i], delimiter, ld) == 0){
                (*stringLengths)[currentStringNumber] = currentStringLength;
                currentStringNumber++;
                currentStringLength = 0;
                delimiterTokenDecrementCounter = ld - 1;
            } else {
                currentStringLength++;
            }
        } else {
            currentStringLength++;
        }

        if(i == (lo - 1)){
            (*stringLengths)[currentStringNumber] = currentStringLength;
        }
    }

    *buffer = (char **) malloc(sizeof(char *) * (*numStrings));
    for(int i = 0;i < *numStrings;i++){
        (*buffer)[i] = (char *) malloc(sizeof(char) * ((*stringLengths)[i] + 1));
    }

    currentStringNumber = 0;
    currentStringLength = 0;
    delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(currentStringLength >= (*stringLengths)[currentStringNumber]){
            (*buffer)[currentStringNumber][currentStringLength] = 0;
            delimiterTokenDecrementCounter = ld - 1;
            currentStringLength = 0;
            currentStringNumber++;
        } else {
            (*buffer)[currentStringNumber][currentStringLength] = (char)original[i];
            currentStringLength++;
        }
    }
    buffer[currentStringNumber][currentStringLength] = 0;
}

샘플 코드 :

int main(){
    const char *string = "STRING-1 DELIM string-2 DELIM sTrInG-3";
    char **buffer;
    int numStrings;
    int * stringLengths;

    splitString(string, " DELIM ", &buffer, &numStrings, &stringLengths);

    for(int i = 0;i < numStrings;i++){
        printf("String: %s\n", buffer[i]);
    }
}

라이브러리 :

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

메인에서 이것을 어떻게 호출합니까? 버퍼에 무엇을 전달해야할지 모르겠습니다.
Aymon Fournier

할당 논리가 잘못되었습니다. realloc ()은 새 포인터를 반환하고 반환 된 값을 버립니다. 새로운 메모리 포인터를 반환하는 적절한 방법이 없습니다. 함수 프로토 타입은 할당 된 크기를 수용 buffer하고 호출자에게 할당을 남겨두고 최대 크기 요소를 처리 하도록 변경해야합니다 .
Alex

@Alex 고정, 완전히 다시 작성 및 테스트되었습니다. 참고 : 이것이 ASCII가 아닌 경우 작동하는지 확실하지 않습니다.
Élektra

우선, 이것은 C 코드가 아닙니다. 그리고 C ++에서 실제 참조로 포인터를 전달하는 이유는 무엇입니까?
Kamiccolo

@ Kamiccolo 죄송합니다.이 코드는 정확히 C 코드가 아닙니다. 또한 왜 포인터를 참조로 전달하는 것이 문제입니까?
Élektra

1

내 접근 방식은 문자열을 스캔하고 포인터가 deliminators (및 첫 번째 문자) 뒤의 모든 문자를 가리 키도록하고 동시에 문자열에 deliminator의 모양을 '\ 0'에 할당하는 것입니다.
먼저 원래 문자열의 사본을 만들고 (정수이므로) 스캔하여 분할 수를 가져와 포인터 매개 변수 len에 전달합니다 . 그런 다음 첫 번째 결과 포인터를 복사 문자열 포인터로 가리킨 다음 복사 문자열을 스캔하십시오. 한번 델리 미네 이터가 발생하면 '\ 0'에 할당하여 이전 결과 문자열이 종료되고 다음 결과 문자열 포인터가 다음 문자열을 가리 키도록하십시오. 문자 포인터.

char** split(char* a_str, const char a_delim, int* len){
    char* s = (char*)malloc(sizeof(char) * strlen(a_str));
    strcpy(s, a_str);
    char* tmp = a_str;
    int count = 0;
    while (*tmp != '\0'){
        if (*tmp == a_delim) count += 1;
        tmp += 1;
    }
    *len = count;
    char** results = (char**)malloc(count * sizeof(char*));
    results[0] = s;
    int i = 1;
    while (*s!='\0'){
        if (*s == a_delim){
            *s = '\0';
            s += 1;
            results[i++] = s;
        }
        else s += 1;
    }
    return results;
}

이 방법은 잘못되었습니다. 이 게시물을 방금 삭제했지만 일부 사용자에게는 흥미로울 것입니다.
metalcrash

1

내 코드 (테스트) :

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

int dtmsplit(char *str, const char *delim, char ***array, int *length ) {
  int i=0;
  char *token;
  char **res = (char **) malloc(0 * sizeof(char *));

  /* get the first token */
   token = strtok(str, delim);
   while( token != NULL ) 
   {
        res = (char **) realloc(res, (i + 1) * sizeof(char *));
        res[i] = token;
        i++;
      token = strtok(NULL, delim);
   }
   *array = res;
   *length = i;
  return 1;
}

int main()
{
    int i;
    int c = 0;
    char **arr = NULL;

    int count =0;

    char str[80] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    c = dtmsplit(str, ",", &arr, &count);
    printf("Found %d tokens.\n", count);

    for (i = 0; i < count; i++)
        printf("string #%d: %s\n", i, arr[i]);

   return(0);
}

결과:

Found 12 tokens.
string #0: JAN
string #1: FEB
string #2: MAR
string #3: APR
string #4: MAY
string #5: JUN
string #6: JUL
string #7: AUG
string #8: SEP
string #9: OCT
string #10: NOV
string #11: DEC

1
strtok 함수는 문자열 'str'이 적용된 문자열을 변경합니다!
SchLx

1

분해 및 중단-초기 문자열은 그대로 유지되며 동적 메모리 할당

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

typedef struct
{
    uintptr_t   ptr;
    int         size;
} token_t;

int explode(char *str, int slen, const char *delimiter, token_t **tokens)
{
    int i = 0, c1 = 0, c2 = 0;

    for(i = 0; i <= slen; i++)
    {
            if(str[i] == *delimiter)
            {
                c1++;
            }
    }

    if(c1 == 0)
    {
            return -1;
    }

    *tokens = (token_t*)calloc((c1 + 1), sizeof(token_t));
    ((*tokens)[c2]).ptr = (uintptr_t)str;

    i = 0; 
    while(i <= slen)
    {
        if((str[i] == *delimiter) || (i == slen))
        {
                ((*tokens)[c2]).size = (int)((uintptr_t)&(str[i]) - (uintptr_t)(((*tokens)[c2]).ptr));
                if(i < slen)
                {
                    c2++;
                    ((*tokens)[c2]).ptr = (uintptr_t)&(str[i + 1]);
                }
        }
        i++;
    }
    return (c1 + 1);
}

char* implode(token_t *tokens, int size, const char *delimiter)
{
    int     i, len = 0;
    char    *str;

    for(i = 0; i < len; i++)
    {
        len += tokens[i].size + 1;
    }

    str = (char*)calloc(len, sizeof(char));

    len = 0;
    for(i = 0; i < size; i++)
    {
        memcpy((void*)&str[len], (void*)tokens[i].ptr, tokens[i].size);
        len += tokens[i].size;
        str[(len++)] = *delimiter;
    }

    str[len - 1] = '\0';

    return str;
}

용법:

int main(int argc, char **argv)
{
    int         i, c;
    char        *exp = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    token_t     *tokens;
    char        *imp;

    printf("%s\n", exp);

    if((c = explode(exp, strlen(exp), ",", &tokens)) > 0)
    {
        imp = implode(tokens, c, ",");
        printf("%s\n", imp);

        for(i = 0; i < c; i++)
        {
            printf("%.*s, %d\n", tokens[i].size, (char*)tokens[i].ptr, tokens[i].size);
        }
    }

    free((void*)tokens);
    free((void*)imp);
    return 0;
}

0

외부 라이브러리를 사용하려는 경우 권장 할 수 없습니다 bstrlib 충분 않습니다. 약간의 추가 설정이 필요하지만 장기적으로 사용하기가 더 쉽습니다.

예를 들어, 아래에서 문자열을 분할하면 먼저 호출 bstring과 함께을 만듭니다 bfromcstr(). (A bstring는 char 버퍼를 감싸는 래퍼입니다). 그런 다음 문자열을 쉼표로 나누고 결과를 struct bstrList필드에 포함하는 qty및에 배열 entry인을 (를) 저장합니다 bstring.

bstrlib 작동 할 다른 많은 기능이 있습니다 bstring

파이처럼 쉬운 ...

#include "bstrlib.h"
#include <stdio.h>
int main() {
  int i;
  char *tmp = "Hello,World,sak";
  bstring bstr = bfromcstr(tmp);
  struct bstrList *blist = bsplit(bstr, ',');
  printf("num %d\n", blist->qty);
  for(i=0;i<blist->qty;i++) {
    printf("%d: %s\n", i, bstr2cstr(blist->entry[i], '_'));
  }

}

0

또 다른 대답 (여기에서 여기 로 옮겨졌습니다) ) :

strtok 함수를 사용해보십시오 :

이 주제에 대한 자세한 내용은 여기 또는 여기를 참조 하십시오

여기서 문제는 words즉시 처리해야한다는 것 입니다. 배열에 저장하려면correct size 마녀를 알 수 없습니다.

예를 들어 :

char **Split(char *in_text, char *in_sep)
{
    char **ret = NULL;
    int count = 0;
    char *tmp = strdup(in_text);
    char *pos = tmp;

    // This is the pass ONE: we count 
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        count++;
        pos = NULL;
    }

    // NOTE: the function strtok changes the content of the string! So we free and duplicate it again! 
    free(tmp);
    pos = tmp = strdup(in_text);

    // We create a NULL terminated array hence the +1
    ret = calloc(count+1, sizeof(char*));
    // TODO: You have to test the `ret` for NULL here

    // This is the pass TWO: we store
    count = 0;
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        ret[count] = strdup(pos);
        count++;
        pos = NULL;
    }
    free(tmp);

    return count;
}

// Use this to free
void Free_Array(char** in_array)
{
    char *pos = in_array;

    while (pos[0] != NULL)
    {
        free(pos[0]);
        pos++;

    }

    free(in_array);

}

참고 : 할당 문제를 피하기 위해 동일한 루프와 함수를 사용하여 카운트를 계산하고 (패스 1) 복사를 수행합니다 (패스 2).

참고 2 : 별도의 게시물에 언급 된 이유로 인해 다른 strtok 구현을 사용할 수 있습니다.

다음과 같이 사용할 수 있습니다.

int main(void)
{
  char **array = Split("Hello World!", " ");
  // Now you have the array
  // ...

  // Then free the memory
  Free_Array(array);
  array = NULL;
  return 0;
}

(테스트하지 않았으므로 작동하지 않으면 알려주십시오!)


0

이 질문을 둘러싼 두 가지 문제는 메모리 관리와 스레드 안전성입니다. 많은 게시물에서 알 수 있듯이 C에서 완벽하게 수행하는 것은 쉬운 일이 아닙니다. 나는 다음과 같은 솔루션을 원했습니다.

  • 스레드 안전. (strtok은 스레드 안전하지 않습니다)
  • malloc 또는 그 파생어를 사용하지 않습니다 (메모리 관리 문제를 피하기 위해)
  • 알 수없는 데이터의 세그먼트 결함을 피하기 위해 개별 필드에서 배열 범위를 확인합니다.
  • 멀티 바이트 필드 구분 기호 (utf-8)와 함께 작동
  • 입력에서 추가 필드를 무시합니다
  • 유효하지 않은 필드 길이에 대한 소프트 오류 루틴 제공

내가 찾은 솔루션은 이러한 모든 기준을 충족시킵니다. 여기에 게시 된 다른 솔루션보다 약간 더 많은 작업이 필요하지만 실제로는 다른 솔루션의 일반적인 함정을 피하기 위해 추가 작업이 가치가 있다고 생각합니다.

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

struct splitFieldType {
    char *field;
    int   maxLength;
};

typedef struct splitFieldType splitField;

int strsplit(splitField *fields, int expected, const char *input, const char *fieldSeparator, void (*softError)(int fieldNumber,int expected,int actual))  {
    int i;
    int fieldSeparatorLen=strlen(fieldSeparator);
    const char *tNext, *tLast=input;

    for (i=0; i<expected && (tNext=strstr(tLast, fieldSeparator))!=NULL; ++i) {
        int len=tNext-tLast;
        if (len>=fields[i].maxLength) {
            softError(i,fields[i].maxLength-1,len);
            len=fields[i].maxLength-1;
        }
        fields[i].field[len]=0;
        strncpy(fields[i].field,tLast,len);
        tLast=tNext+fieldSeparatorLen;
    }
    if (i<expected) {
        if (strlen(tLast)>fields[i].maxLength) {
            softError(i,fields[i].maxLength,strlen(tLast));
        } else {
            strcpy(fields[i].field,tLast);
        }
        return i+1;
    } else {
        return i;
    }
}


void monthSplitSoftError(int fieldNumber, int expected, int actual) {
    fprintf(stderr,"monthSplit: input field #%d is %d bytes, expected %d bytes\n",fieldNumber+1,actual,expected);
}


int main() {
  const char *fieldSeparator=",";
  const char *input="JAN,FEB,MAR,APRI,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR";

  struct monthFieldsType {
    char field1[4];
    char field2[4];
    char field3[4];
    char field4[4];
    char field5[4];
    char field6[4];
    char field7[4];
    char field8[4];
    char field9[4];
    char field10[4];
    char field11[4];
    char field12[4];
  } monthFields;

  splitField inputFields[12] = {
    {monthFields.field1,  sizeof(monthFields.field1)},
    {monthFields.field2,  sizeof(monthFields.field2)},
    {monthFields.field3,  sizeof(monthFields.field3)},
    {monthFields.field4,  sizeof(monthFields.field4)},
    {monthFields.field5,  sizeof(monthFields.field5)},
    {monthFields.field6,  sizeof(monthFields.field6)},
    {monthFields.field7,  sizeof(monthFields.field7)},
    {monthFields.field8,  sizeof(monthFields.field8)},
    {monthFields.field9,  sizeof(monthFields.field9)},
    {monthFields.field10, sizeof(monthFields.field10)},
    {monthFields.field11, sizeof(monthFields.field11)},
    {monthFields.field12, sizeof(monthFields.field12)}
  };

  int expected=sizeof(inputFields)/sizeof(splitField);

  printf("input data: %s\n", input);
  printf("expecting %d fields\n",expected);

  int ct=strsplit(inputFields, expected, input, fieldSeparator, monthSplitSoftError);

  if (ct!=expected) {
    printf("string split %d fields, expected %d\n", ct,expected);
  }

  for (int i=0;i<expected;++i) {
    printf("field %d: %s\n",i+1,inputFields[i].field);
  }

  printf("\n");
  printf("Direct structure access, field 10: %s", monthFields.field10);
}

아래는 컴파일 및 출력 예제입니다. 이 예에서는 소프트 오류가 어떻게 작동하는지 알 수 있도록 의도적으로 "APRIL"을 표시했습니다.

$ gcc strsplitExample.c && ./a.out
input data: JAN,FEB,MAR,APRIL,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR
expecting 12 fields
monthSplit: input field #4 is 5 bytes, expected 3 bytes
field 1: JAN
field 2: FEB
field 3: MAR
field 4: APR
field 5: MAY
field 6: JUN
field 7: JUL
field 8: AUG
field 9: SEP
field 10: OCT
field 11: NOV
field 12: DEC

Direct structure access, field 10: OCT

즐겨!


0

다음은 할당 된 포인터 대 포인터를 char (예 :)로 반환하는 질문에 요청 된 프로토 타입과 일치 하는 문자열 리터럴 을 토큰 화하기 위해 안전하게 작동하는 또 다른 구현입니다 char **. 분리 문자 스트링은 여러 문자를 포함 할 수 있으며 입력 스트링은 임의의 수의 토큰을 포함 할 수 있습니다. 모든 할당 및 재 할당은 POSIX에 의해 malloc또는 reallocPOSIX없이 처리됩니다 strdup.

할당 된 초기 포인터 수는 NPTRS상수에 의해 제어되며 유일한 제한은 0보다 크다는 것입니다. char **반환이 포함 감시 NULL 후를 마지막으로 유사 토큰 *argv[]및 의한 양식 사용할 수있는 execv, execvp그리고execve .

에서와 같이 strtok()여러 개의 연속 구분 기호 때문에, 하나의 구분 기호로 처리하는 "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"단 하나의 경우로 해석됩니다 ','분리한다 "MAY,JUN".

아래의 기능은 인라인으로 주석 처리되어 main()있으며 달을 나누는 단락 이 추가되었습니다. 할당 된 초기 포인터 수는 2입력 문자열을 토큰 화하는 동안 3 개의 재 할당을 강제 실행하도록 설정되었습니다 .

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

#define NPTRS 2     /* initial number of pointers to allocate (must be > 0) */

/* split src into tokens with sentinel NULL after last token.
 * return allocated pointer-to-pointer with sentinel NULL on success,
 * or NULL on failure to allocate initial block of pointers. The number
 * of allocated pointers are doubled each time reallocation required.
 */
char **strsplit (const char *src, const char *delim)
{
    int i = 0, in = 0, nptrs = NPTRS;       /* index, in/out flag, ptr count */
    char **dest = NULL;                     /* ptr-to-ptr to allocate/fill */
    const char *p = src, *ep = p;           /* pointer and end-pointer */

    /* allocate/validate nptrs pointers for dest */
    if (!(dest = malloc (nptrs * sizeof *dest))) {
        perror ("malloc-dest");
        return NULL;
    }
    *dest = NULL;   /* set first pointer as sentinel NULL */

    for (;;) {  /* loop continually until end of src reached */
        if (!*ep || strchr (delim, *ep)) {  /* if at nul-char or delimiter char */
            size_t len = ep - p;            /* get length of token */
            if (in && len) {                /* in-word and chars in token */
                if (i == nptrs - 1) {       /* used pointer == allocated - 1? */
                    /* realloc dest to temporary pointer/validate */
                    void *tmp = realloc (dest, 2 * nptrs * sizeof *dest);
                    if (!tmp) {
                        perror ("realloc-dest");
                        break;  /* don't exit, original dest still valid */
                    }
                    dest = tmp;             /* assign reallocated block to dest */
                    nptrs *= 2;             /* increment allocated pointer count */
                }
                /* allocate/validate storage for token */
                if (!(dest[i] = malloc (len + 1))) {
                    perror ("malloc-dest[i]");
                    break;
                }
                memcpy (dest[i], p, len);   /* copy len chars to storage */
                dest[i++][len] = 0;         /* nul-terminate, advance index */
                dest[i] = NULL;             /* set next pointer NULL */
            }
            if (!*ep)                       /* if at end, break */
                break;
            in = 0;                         /* set in-word flag 0 (false) */
        }
        else {  /* normal word char */
            if (!in)                        /* if not in-word */
                p = ep;                     /* update start to end-pointer */
            in = 1;                         /* set in-word flag 1 (true) */
        }
        ep++;   /* advance to next character */
    }

    return dest;
}

int main (void) {

    char *str = "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC",
        **tokens;                           /* pointer to pointer to char */

    if ((tokens = strsplit (str, ","))) {   /* split string into tokens */
        for (char **p = tokens; *p; p++) {  /* loop over filled pointers */
            puts (*p);
            free (*p);      /* don't forget to free allocated strings */
        }
        free (tokens);      /* and pointers */
    }
}

사용 / 출력 예

$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC

더 궁금한 점이 있으면 알려주세요.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.