programing

C로 구분하는 문자열 분할

minecode 2022. 7. 11. 21:43
반응형

C로 구분하는 문자열 분할

C 프로그래밍 언어로 딜리미터가 있는 문자열에 대해 배열을 분할하고 반환하는 함수를 작성하려면 어떻게 해야 합니까?

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

함수를 사용하여 문자열을 분할할 수 있습니다(및 사용할 딜리미터를 지정합니다).주의: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]

생각에는strsep여전히 최적의 도구입니다.

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

그것은 말 그대로 줄을 나누는 한 줄이다.

것을 연산자가 아닙니다.==.

하려면 , 「 」token ★★★★★★★★★★★★★★★★★」str 다 이 있다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, 를 얻을 수 있습니다.tokenvalue입니다.「」의 값str는 검출된 각 딜리미터가0 바이트로 덮어쓰기 되어 있기 때문에 수정되어 있습니다.이것은, 최초로 해석되는 문자열을 카피하는 적절한 이유이기도 합니다.

어떤 로 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 네.strtok보다 strsepstrtok이치노 및 는 Ubuntu mac Mac OS X ub를 탑재하고 있습니다.strsep; 다른 Unixy 시스템도 마찬가지라고 추측해도 무방합니다.에 Windows가 없다strsep단, ,가 있습니다.strbrk '스위트'를 하게 합니다.strsep★★★★

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;
}

여기 좋은 설명이 있습니다.strsep »strtok할 수 아, 아, 아, 아, 아, '우리에게 좋은 '라고 생각합니다.strsep는 을 대체하기 위해 고안되었습니다.strtok.

문자열 토큰라이저 이 코드는 올바른 방향으로 이동합니다.

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;
}

제 의견은 이렇습니다.

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);

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

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

코드 설명:

  1. token
  2. 즉, 이 경우에 충분한 를 할당할 수 있습니다.str되어 있기 에, 「세퍼레이터」가 있습니다.strlen(str) + 1 빈 , "tokens, empty strings"
  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 *이러한 분할 기능으로부터의 포인터는, 발신자에게 올바르게 해방시키는 큰 책임을 지게 되기 때문에, 다음과 같이 기능합니다.여기서 설명한 바와 같이 발신자가 콜백함수를 전달하고 모든 토큰에 대해 콜백함수를 호출할 수 있도록 하는 것이 바람직합니다.

다.strtok() ★★★★★★★★★★★★★★★★★」strsep()입력 문자열을 변경합니다.strspn()strpbrk()를 사용하여 딜리미터를 기준으로 문자열을 분할하는 함수를 작성할 수 있습니다.

알고리즘:

  1. 있지 않으면 2로 돌아갑니다.그렇지 않으면 반환됩니다.null.
  2. 구분자가 워드의 를 사용).strspn()경우)라고.start.
  3. 이전 위치 더 끝)를.strpbrk()경우)라고.end.
  4. 및 을 " " " 에서 할당합니다.start로로 합니다.end아, 아, 아, 아, 아, 아, 아, 네.
  5. 리턴 토큰

장점:

  1. 스레드 세이프
  2. 복수의 딜리미터를 처리합니다.
  3. 포터블.
  4. 를 들어 입력 문자열은 수정하지 않습니다).strtok() ★★★★★★★★★★★★★★★★★」strsep()

구현:

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

/*
 * alloc_str function allocates memory and copy substring
 * to allocated memory.
 */

static char * alloc_str (const char * start, const char * end) {
    if (!start || !end || (start >= end)) {
        return NULL;
    }

    char * tmp = malloc (end - start + 1);
    if (tmp) {
        memcpy (tmp, start, end - start);
        tmp[end - start] = '\0';
    } else {
        fprintf (stderr, "Failed to allocate memory\n");
        exit (EXIT_FAILURE);
    }

    return tmp;
}

/*
 * str_split function returns the next token which is sequences of contiguous
 * characters separated by any of the characters that are part of delimiters.
 *
 * Parameters: 
 * p_str : Address of pointer to the string that you want to split.
 * sep : A set of characters that delimit the pieces in the string.
 *
 * Behaviour is undefined if sep is not a pointer to a null-terminated string. 
 *
 * Return :
 * Returns the pointer to dynamically allocated memory where the token is copied.
 * If p_str is NULL or empty string, NULL is returned.
 */

char * str_split (char ** p_str, const char * sep) {
    char * token = NULL;

    if (*p_str && **p_str) {
        char * p_end;

        // skip separator
        *p_str += strspn(*p_str, sep);

        p_end = *p_str;

        // find separator
        p_end = strpbrk (p_end, sep);

        // strpbrk() returns null pointer if no such character
        // exists in the input string which is part of sep argument.
        if (!p_end) {
            p_end = *p_str + strlen (*p_str);
        }

        token = alloc_str (*p_str, p_end);
        *p_str = p_end;
    }

    return token;
}

/*==================================================*/
/*==================================================*/

/*
 * Just a helper function
 */

void token_helper (char * in_str, const char * delim) {
    printf ("\nInput string : ");

    if (in_str) printf ("\"%s\"\n", in_str);
    else printf ("NULL\n");

    if (delim) printf ("Delimiter : \"%s\"\n", delim);

    char * ptr = in_str;
    char * token = NULL;

    printf ("Tokens:\n");
    while ((token = str_split(&ptr, delim)) != NULL) {
        printf ("-> %s\n", token);
        /* You can assign this token to a pointer of an array of pointers
         * and return that array of pointers from this function.
         * Since, this is for demonstration purpose, I am 
         * freeing the allocated memory now.
         */
        free (token);
    }
}

/*
 * Driver function
 */

int main (void) {
    /* test cases */

    char string[100] = "hello world!";
    const char * delim = " ";
    token_helper (string, delim);

    strcpy (string, " hello world,friend of mine!");
    delim = " ,";
    token_helper (string, delim);

    strcpy (string, "Another string");
    delim = "-!";
    token_helper (string, delim);

    strcpy (string, "   one  more   -- string  !");
    delim = "- !";
    token_helper (string, delim); 

    strcpy (string, "");
    delim = " ";
    token_helper (string, delim);

    token_helper (NULL, "");

    strcpy (string, "hi");
    delim = " -$";
    token_helper (string, delim);

    strcpy (string, "Give papa a cup of proper coffee in a copper coffee cup.");
    delim = "cp";
    token_helper (string, delim);

    strcpy (string, "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC");
    delim = ",";
    token_helper (string, delim);

    return 0;
}

출력:

# ./a.out

Input string : "hello world!"
Delimiter : " "
Tokens:
-> hello
-> world!

Input string : " hello world,friend of mine!"
Delimiter : " ,"
Tokens:
-> hello
-> world
-> friend
-> of
-> mine!

Input string : "Another string"
Delimiter : "-!"
Tokens:
-> Another string

Input string : "   one  more   -- string  !"
Delimiter : "- !"
Tokens:
-> one
-> more
-> string

Input string : ""
Delimiter : " "
Tokens:

Input string : NULL
Delimiter : ""
Tokens:

Input string : "hi"
Delimiter : " -$"
Tokens:
-> hi

Input string : "Give papa a cup of proper coffee in a copper coffee cup."
Delimiter : "cp"
Tokens:
-> Give 
-> a
-> a a 
-> u
->  of 
-> ro
-> er 
-> offee in a 
-> o
-> er 
-> offee 
-> u
-> .

Input string : "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
Delimiter : ","
Tokens:
-> JAN
-> FEB
-> MAR
-> APR
-> MAY
-> JUN
-> JUL
-> AUG
-> SEP
-> OCT
-> NOV
-> DEC

파티에는 늦었지만, 여기 2가지 기능이 더 있습니다.또, 고객의 요구에 맞추어 조정할 수도 있습니다(투고 하단의 소스 코드).

고객의 요구에 적합한 기능을 판별하려면 아래 구현 노트를 참조하십시오.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>  // C99

// tokenize destructively
char **str_toksarray_alloc(
    char **strp,       /* InOut: pointer to the source non-constant c-string */
    const char *delim, /* c-string containing the delimiting chars */
    size_t *ntoks,     /* InOut: # of tokens to parse/parsed (NULL or *ntoks==0 for all tokens) */
    bool keepnulls     /* false ignores empty tokens, true includes them */
    );

// tokenize non-destructively
char **str_toksarray_alloc2(
   const char *str,    /* the source c-string */
   const char *delim,
   size_t *ntoks,
   bool keepnulls
   );

사용상의 주의

source-string)을 거의 합니다.strp ★★★★★★★★★★★★★★★★★」str(신호)

strp(스트링에 할당됨)은 이미 할당되어 있는 토큰화되지 않은c 스트링의 주소입니다. str는 변경되지 않은c 스트링입니다(문자열일 수도 있습니다).'c-string'은nul- of - "terminated buffer of chars." (문자버퍼).나머지 인수는 두 함수에 대해 동일합니다.

사용 가능한 모든 토큰을 해석하려면 음소거 ntoks입니다).NULL 않은 최대 해석됩니다.*ntoks또는 토큰이 없어질 때까지(최초 토큰이 우선입니다).경우든, 「」가 되었을 때ntoksnon-NULL정상적으로 해석된 토큰의 수에 따라 갱신됩니다.

또한 음소거되지 않은 것은 ntoks이치노 10개의 토큰이 되어 있는 ntoks1000까지 990개의 포인터가 불필요하게 할당됩니다.되어 있는데, 한 경우, source-string 1000은 10개입니다.ntoks10월 10일

함수는 모두 char-pointer 배열을 할당하고 반환합니다만,str_toksarray_alloc() "source-string"은 "source-string"으로 지정합니다.str_toksarray_alloc2()는 동적으로 할당된 토큰의 복사본을 가리키도록 합니다(이름 끝에 있는2는 할당의 2를 나타냅니다).

에는 ""가 됩니다.NULL sentinel의 (sentinel 포인터).ntoks 않은 는)non-NULL,ntoks첫 번째 레벨 사이즈가 아닌 반환된 어레이의 길이를 발신자에게 전달합니다).

keepnulls로 설정되어 있다.true결과 토큰은 strsep() 함수에서 기대할 수 있는 것과 비슷합니다.대부분 source-string 내의 연속된 딜리미터가 빈 토큰(늘)을 생성하는 것을 의미합니다.delim는 빈 c-string 이거나 소스 문자열에 포함된 딜리미터-chars 가 발견되지 않았습니다.그 결과, 토큰은 1개뿐입니다.송신원 문자열입니다.strsep()와 달리 빈 토큰은 다음 설정으로 무시할 수 있습니다.keepnulls로로 합니다.false.

함수의 실패한 콜은 반환값을 체크함으로써 식별할 수 있습니다.NULL ""의 값을 ntoks제공된0 의 경우)ntoksnon-NULL어레이에 에 항상 할 것을 크래시를 할 수 가 포함되어 있기 " a an " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "기능에는 즉시 크래시를 연기할 수 있는 온전성 체크가 포함되어 있기 때문입니다(예를 들어 패스 등).NULL

정상적으로 종료하면, 발신자는 어레이를 해방할 수 있습니다.위해서str_toksarray_alloc(), 단순한 free()로 충분합니다.위해서str_toksarray_alloc2()두 번째 수준의 할당으로 인해 루프가 발생합니다.NULL sentinel의 )non-NULL ntoks사소한 것으로 만, 저는 을 하고 toksarray_free2()에 대해과 같이 :)

두 기능을 모두 사용한 간단한 예를 다음에 나타냅니다.

준비:

const char *src = ";b,test,Tèst,;;cd;ελληνικά,nørmälize,;string to";
const char *delim = ";,";
bool keepnulls = true;
size_t ntoks = 0;

str_toksarray_param():

// destructive (use copy of src)

char *scopy = strdup( src );
if (!scopy) { ... };          // handle strdup failure

printf( "%s\n", src );
char **arrtoks = str_toksarray_alloc( &scopy, delim, &ntoks, keepnulls );
printf( "%lu tokens read\n", ntoks );
if ( arrtoks ) {
    for (int i=0; arrtoks[i]; i++) {
        printf( "%d: %s\n", i, arrtoks[i] );
    }
}
free( scopy );
free( arrtoks );

/* OUTPUT
;b,test,Tèst,;;cd;ελληνικά,nørmälize,;string to
11 tokens read
0:
1: b
2: test
3: Tèst
4:
5:
6: cd
7: ελληνικά
8: nørmälize
9:
10: string to
 */

str_toksarray_param2():

// non-destructive

keepnulls = false;    // reject empty tokens

printf( "%s\n", src );
arrtoks = str_toksarray_alloc2( src, delim, &ntoks, keepnulls );
printf( "%lu tokens read\n", ntoks );
if ( arrtoks ) {
    for (int i=0; arrtoks[i]; i++) {
        printf( "%d: %s\n", i, arrtoks[i] );
    }
}
toksarray_free2( arrtoks );                     // dangling arrtoks
// or: arrtoks = toksarray_free2( arrtoks );    // non-dangling artoks

/* OUTPUT
;b,test,Tèst,;;cd;ελληνικά,nørmälize,;string to
7 tokens read
0: b
1: test
2: Tèst
3: cd
4: ελληνικά
5: nørmälize
6: string to
*/

구현에 관한 주의사항

두 함수 모두 토큰화에 strsep()를 사용하여 스레드 세이프를 실현하지만 표준 함수는 아닙니다.제공하지 않으면 언제든지 오픈 소스 구현을 사용할 수 있습니다(예를 들어 GNU나 Apple 등).에서 사용되는 함수 strdup()도 마찬가지입니다.str_toksarray_alloc2()(그 실장은 사소한 것이지만, 예를 들면 GNU나 Apple의 실장이 있습니다).

에서 strsep()를 사용하는 경우의 부작용str_toksarray_alloc()source-string의 시작 포인터가 해석 루프의 모든 단계에서 다음 토큰으로 계속 이동하는 것입니다.즉, 발신자가 개시 주소를 추가 포인터에 보존하지 않는 한, 해석된 문자열을 해방할 수 없습니다. 기능을 로컬로 수행함으로써 고객의 번거로움을 덜어줍니다.strpSaved포인터 str_toksarray_alloc2()소스 문자열에 닿지 않기 때문에 이 영향을 받지 않습니다.

두 기능의 주요 차이점은 다음과 같습니다.str_toksarray_alloc()는 검출된 토큰에 메모리를 할당하지 않습니다.오히려 어레이 포인터에만 공간을 할당하고 소스 문자열을 직접 가리키도록 설정합니다.이것은 strsep() 때문에 동작합니다. nul- 을 in-place로 in-place 토큰을 in-place로 종료합니다.이러한 의존성으로 인해 지원 코드가 복잡해질 수 있지만 문자열이 크면 성능도 크게 달라질 수 있습니다.source-string을 유지하는 것이 중요하지 않은 경우 메모리 설치 공간에도 큰 차이가 있을 수 있습니다.

반,는str_toksarray_alloc2()는 토큰의 동적으로 할당된 복사본을 더 이상 의존하지 않고 자체 유지 배열을 할당하여 반환합니다.첫 번째로 source-string의 로컬 복제에서 배열을 만들고 두 번째로 실제 토큰 내용을 배열에 복제하여 이를 수행합니다.는 이이 this this this and this this this this 、 이 this 。str_toksarray_alloc() 특별한 되어 있지 이것에 의해, 보다 간단하게 서포트 코드를 쓸 수 있게 됩니다(따라서 유지보수가 용이합니다).

두의 또 다른 첫 포인터입니다.ntoks음소거되어 있습니다.둘 다 사용 가능한 모든 토큰을 해석하지만 접근 방식은 상당히 다릅니다. str_toksarray_alloc()는 초기 사이즈가 16(char-ahead)인 alloc-ahead를 사용하여 해석 루프에서 필요에 따라 2배 증가시킵니다. str_toksarray_alloc2()는 사용 가능한 모든 토큰을 카운트하여 첫 번째 패스를 하고 그 많은 문자를 한 번만 할당합니다. 첫 .str_toksfound()표준 함수 strpbrk()strchr을 사용합니다.그 기능의 소스코드도 아래에 기재하고 있습니다.

프로젝트의 요구에 따라 어떤 접근방식이 더 나은지 결정하는 것은 전적으로 고객님의 몫입니다.각 함수의 코드를 자유롭게 조정하여 접근 및 취득할 수 있습니다.

특히 초기 크기와 성장률이 케이스별로 미세 조정된 경우(예를 들어 기능 파라미터로 설정)에는 평균적으로 매우 큰 문자열의 경우 할당 속도가 훨씬 빠릅니다. 를 그 것들과 하다.strchr() »strpbrk()는 거기서 차이를 만들 수 있습니다.하지만, 비교적 작은 문자열이 일반적이기 때문에, 한 무더기의 차자를 미리 할당하는 것은 단지 과잉 살상일 뿐입니다.아프지 않지만 이 경우 정당한 이유 없이 코드가 엉망이 됩니다.어쨌든, 당신에게 가장 적합한 것을 자유롭게 고르세요.

2번입니다.의 라고 할 수 str_toksarray_alloc2()메모리와 퍼포먼스가 작은 문자열에서 중간 문자열로 문제가 되는 경우는 거의 없기 때문에 대처가 훨씬 간단합니다.큰 에 대해서 다루어야 할 쓰도록 .str_toksarray_alloc()(단, 이 경우 프로젝트의 요구나 입력 사양에 가까운 특수한 문자열 해석 기능을 롤링해야 합니다).

이런, 그건 2센트보다 조금 더 많은 것 같아요(웃음).

어쨌든, 2개의 기능과 헬퍼의 코드에 대해 설명합니다(이미 거의 모든 것을 커버했기 때문에, 설명 코멘트의 대부분을 삭제했습니다).

소스 코드

str_toksarray_param():

// ----------------------------------------
// Tokenize destructively a nul-terminated source-string.
// Return a dynamically allocated, NULL terminated array of char-pointers
// each pointing to each token found in the source-string, or NULL on error.
//
char **str_toksarray_alloc(char **strp, const char *delim, size_t *ntoks, bool keepnulls)
{
    // sanity checks
    if ( !strp || !*strp || !**strp || !delim ) {
        goto failed;
    }

    char *strpSaved = *strp;                    // save initial *strp pointer
    bool ntoksOk = (ntoks && *ntoks);           // false when ntoks is muted
    size_t _ntoks = (ntoksOk ? *ntoks : 16);    // # of tokens to alloc-ahead

    // alloc array of char-pointers (+1 for NULL sentinel)
    char **toksarr = malloc( (_ntoks+1) * sizeof(*toksarr) );
    if ( !toksarr ) {
        goto failed;
    }

    // Parse *strp tokens into the array
    size_t i = 0;           // # of actually parsed tokens
    char *tok;
    while ( (tok = strsep(strp, delim)) ) {
        // if requested, ignore empty tokens
        if ( *tok == '\0' && !keepnulls ) {
            continue;
        }
        // non-muted ntoks reached? we are done
        if ( ntoksOk && i == _ntoks ) {
            *ntoks = i;
            break;
        }
        // muted ntoks & ran out of space? double toksarr and keep parsing
        if ( !ntoksOk && i == _ntoks ) {
            _ntoks *= 2;
            char **tmparr = realloc( toksarr, (_ntoks+1) * sizeof(*tmparr) );
            if ( !tmparr ) {
                *strp = strpSaved;
                free( toksarr );
                goto failed;
            }
            toksarr = tmparr;
        }
        toksarr[i++] = tok; // get token address
    }
    toksarr[i] = NULL;      // NULL sentinel

    *strp = strpSaved;      // restore initial *strp pointer
    if (ntoks) *ntoks = i;  // pass to caller # of parsed tokens
    return toksarr;

failed:
    if (ntoks) *ntoks = 0;
    return NULL;
}

str_toksarray_param2():

// ----------------------------------------
// Tokenize non-destructively a nul-terminated source-string.
// Return a dynamically allocated, NULL terminated array of dynamically
// allocated and nul-terminated string copies of each token found in the
// source-string. Return NULL on error.
// The 2 at the end of the name means 2-levels of allocation.
//
char **str_toksarray_alloc2( const char *str, const char *delim, size_t *ntoks, bool keepnulls )
{
    // sanity checks
    if ( !str || !*str || !delim ) {
        if (ntoks) *ntoks = 0;
        return NULL;
    }

    // make a copy of str to work with
    char *_str = strdup( str ); 
    if ( !_str ) {
        if (ntoks) *ntoks = 0;
        return NULL;
    }

    // if ntoks is muted we'll allocate str_tokscount() tokens, else *ntoks
    size_t _ntoks = (ntoks && *ntoks) ? *ntoks : str_tokscount(_str, delim, keepnulls);
    if ( _ntoks == 0 ) {        // str_tokscount() failed
        goto fail_free_str;
    }
    
    // alloc the array of strings (+1 for an extra NULL sentinel)
    char **toksarr = malloc( (_ntoks+1) * sizeof(*toksarr) );
    if ( !toksarr ) {
        goto fail_free_str;
    }

    // Parse str tokens and duplicate them into the array
    size_t i = 0;           // # of actually parsed tokens
    char *tok;
    while ( i < _ntoks && (tok = strsep(&_str, delim)) ) {
        // if requested, skip empty tokens
        if ( *tok == '\0' && !keepnulls ) {
            continue;
        }
        // duplicate current token into the array
        char *tmptok = strdup( tok );
        if ( !tmptok ) {
            goto fail_free_arr;
        }
        toksarr[i++] = tmptok;
    }
    toksarr[i] = NULL;      // NULL sentinel

    free( _str );           // release the local copy of the source-string
    if (ntoks) *ntoks = i;  // pass to caller the # of parsed tokens
    return toksarr;

// cleanup before failing
fail_free_arr:
    for (size_t idx=0; idx < i; idx++) {
        free( toksarr[idx] );
    }
    free( toksarr );

fail_free_str:
    free( _str );
    if (ntoks) *ntoks = 0;
    return NULL;
}

str_tokscount() - 도우미 함수. str_toksar_slots2()에서 사용됩니다.

// ----------------------------------------
// Return the count of tokens present in a nul-terminated source-string (str),
// based on the delimiting chars contained in a 2nd nul-terminated string (delim).
// If the boolean argument is false, empty tokens are excluded.
//
// To stay consistent with the behavior of strsep(), the function returns 1 if
// delim is an empty string or none of its delimiters is found in str (in those
// cases the source-string is considered a single token).
// 0 is returned when str or delim are passed as NULL pointers, or when str is
// passed as an empty string.
//
size_t str_tokscount( const char *str, const char *delim, bool keepnulls )
{
    // sanity checks
    if ( !str || !*str || !delim ) {
        return 0;
    }

    const char *tok = str;
    size_t nnulls = strchr(delim, *str) ? 1 : 0;
    size_t ntoks = 1;   // even when no delims in str, str counts as 1 token 
    for (; (str = strpbrk(tok, delim)); ntoks++ ) {
        tok = ++str;
        if ( strchr(delim, *str) ) {
            nnulls++;
        }
    }

    return keepnulls ? ntoks : (ntoks - nnulls);
}

toksarray_free2() - str_toksar_free2()에 의해 반환된 배열에서 사용합니다.

// ----------------------------------------
// Free a dynamically allocated, NULL terminated, array of char-pointers
// with each such pointer pointing to its own dynamically allocated data.
// Return NULL, so the caller has the choice of assigning it back to the
// dangling pointer. The 2 at the end of the name means 2-levels of deallocation.
//
// NULL terminated array means ending with a NULL sentinel.
//      e.g.: toksarr[0] = tok1, ..., toksarr[len] = NULL
//
char **toksarray_free2( char **toksarr )
{
    if ( toksarr ) {
        char **toks = toksarr;
        while ( *toks ) {   // walk until NULL sentinel
            free( *toks++ );
        }
        free( toksarr );
    }

    return NULL;
}

다음 방법은 모든 작업(메모리 할당, 길이 카운트)을 수행합니다.자세한 내용과 설명은 여기를 참조하십시오.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;
}

외부 도서관을 이용하실 의향이 있으시다면 아무리 추천드려도 부족할 것 같습니다.약간의 추가 설정이 필요하지만 장기적으로 사용하기 쉽습니다.

를 들어, 아래 '아까', '아까', '아까', '아까'가 만들어집니다.bstringbfromcstr())bstring차(버)다음 쉼표로 를 '따로 나누다'로합니다.struct bstrList " " " 가 qty 및 andentry의 배열입니다.bstrings.

bstrlib에는 그 할 수 있는 이 많이 .bstrings

식은 죽 먹기야...

#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], '_'));
  }

}
#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;
}

위의 예에서는 (필요한 대로) 문자열 내에 있는 늘 종단 문자열 배열을 반환하는 방법이 있습니다.단, 리터럴 문자열은 함수에 의해 수정되어야 하므로 전달되지 않습니다.

#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;
}

더 깔끔한 방법이 있을 수 있지만, 이해하실 수 있을 겁니다.

내 버전:

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;
}

이 최적화된 메서드는 *결과에 포인터의 배열을 만들고(또는 기존 포인터를 업데이트하고) *카운트 내의 요소 수를 반환합니다.

예상되는 최대 문자열 수를 나타내려면 "max"를 사용하고(기존 어레이 또는 기타 모든 reaseon을 지정하는 경우), 그렇지 않으면 0으로 설정합니다.

딜리미터 목록과 비교하려면 딜리미터를 문자*로 정의하고 행을 바꿉니다.

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;

}

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

#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;
}

나는 아주 간단한 것을 만들려고 했다.메인()에도 예를 제시하겠습니다.

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

void split(char* inputArr, char** outputArr, char* delim) {
    
        char *temp;
        temp = strtok(inputArr, delim);

        for(int i = 0; temp != NULL; i++) {
            outputArr[i] = temp;
            temp = strtok(NULL, " ");
        }
}

int main(int argc, char **argv){
    
    /* check for proper arguments */
    
    if(argc != 2){
        printf("One Argument Expected\n");
    } else {

        printf("\n");
        /*---------main code starts here----------*/
        FILE * myScriptFile;
        myScriptFile = fopen(argv[1], "r");
        
        /* read txt file and split into array like java split() */
        
        int bufferLen = 100;
        char buffer[bufferLen];
        
        char *splitArr[100];        

        while(fgets(buffer, bufferLen, myScriptFile) != NULL){
            
            split(buffer, splitArr, " ");

            printf("Index 0 String: %s\n", splitArr[0]);
            printf("Index 1 String: %s\n", splitArr[1]);
            printf("Index 2 String: %s\n", splitArr[2]);
            printf("Index 3 String: %s\n", splitArr[3]);
        }
        fclose(myScriptFile);
    }
    printf("\nProgram-Script Ended\n");
    return 0;
}

.txt 파일에는

Hello this is test
Hello2 this is test2

파라미터로서 .txt 파일을 사용하여 실행하면

Index 0 String: Hello
Index 1 String: this
Index 2 String: is
Index 3 String: test

Index 0 String: Hello2
Index 1 String: this
Index 2 String: is
Index 3 String: test2

이는 다중 문자 구분 기호를 처리할 수 있는 문자열 분할 함수입니다. " " " " " " " " 입니다.buffer ★★★★★★★★★★★★★★★★★」stringLengths로 설정됩니다.(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>
#include <cstring>
#include <cstdio>
int main()
{
    char buf[] = "This is Luke Skywalker    here!";
    for( char* tok = strtok( buf, " ");
         tok != nullptr;
         tok = strtok( nullptr, " ")) {
        puts( tok);
    }
}

출력

This
is
Luke
Skywalker
here!

이 함수는 char* 문자열을 디미네이터로 분할합니다.연속적으로 여러 개의 디리미네이터가 존재할 수 있습니다.이 함수는 원래 문자열을 변경합니다.원본 문자열의 복사본을 먼저 만들어야 원본 문자열이 변경되지 않습니다.이 함수는 cstring 함수 호출을 사용하지 않기 때문에 다른 함수에 비해 조금 빠를 수 있습니다.메모리 할당에 관심이 없는 경우, sub_strings를 size strlen(src_str)/2로 함수의 맨 위에 할당할 수 있습니다.또한 (c++"버전"과 같이) 함수의 맨 아래 절반을 건너뜁니다.이렇게 하면 기능이 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);

내 접근법은 문자열을 스캔하여 포인터가 삭제자(및 첫 번째 문자) 뒤의 모든 문자를 가리키도록 하고 동시에 문자열 내의 삭제자의 모양을 '\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;
}

는 저의 ★★★★★★★★★★★★★★★★★★.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 에서 다운로드할 수 있습니다.

이거 써봐.

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;
}

내 코드(테스트 완료):

#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

이 질문에 관한 두 가지 문제는 메모리 관리와 스레드 안전성입니다.많은 투고에서 알 수 있듯이, 이것은 C에서 매끄럽게 이루어지기 쉬운 작업이 아닙니다.다음과 같은 솔루션을 원했습니다.

  • 스레드 세이프(스트록은 스레드 세이프가 아닙니다)
  • 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

맛있게 드세요!

여기에 할당된 포인터를 char로 반환하는 질문에서 요청된 프로토타입과 일치하는 문자열 리터럴을 토큰화하기 위해 안전하게 작동하는 또 다른 구현이 있습니다(예: char).char **딜리미터 문자열에는 여러 문자를 포함할 수 있으며 입력 문자열에는 토큰 수를 지정할 수 있습니다. 및 all음 、 음음 、 음음 、 。malloc ★★★★★★★★★★★★★★★★★」realloc POSIX »strdup.

는, 「포인터」에됩니다.NPTRS0으로 하다char **반환되는 것은 초병 포함 NULL 한 경우*argv[] 있습니다.execv,execvp ★★★★★★★★★★★★★★★★★」execve.

★★★★★★★와 같이strtok()딜리미터로서 에, 「」는 「1개의 딜리미터로서 취급됩니다."JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"으로 해석됩니다.','"MAY,JUN".

으로 코멘트를 코멘트를 합니다.main()이치노는 할된음음음음음음음음음음음음음음음음음음음 the the the the the the로 되었습니다.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

더 궁금하신 점이 있으시면 알려주세요.

간단한 해결책을 찾다가 이걸 발견했어요.모든 옵션에 매료되어 있습니다만, 자신의 사용 예나 취향에 만족하지 않습니다(매우 나쁠지도 모릅니다).

메모리를 재할당하지 않고, 사람이 읽을 수 있는 + 코멘트를 사용하는 것을 목표로 하는, 다소 독특한 솔루션을 개발했습니다.

다음 URL에서 gist.github에 업로드되었습니다.

예:

#include "./strutils.c"

struct str_split_info info;
info.source = " SPLIT ME hello SPLIT ME world SPLIT ME whats SPLIT ME going SPLIT ME on SPLIT ME today";
info.delimiter = " SPLIT ME ";

str_split_begin(&info);

char * substr;

for (int i=0; i<info.splitStringsCount; i++) {
  substr = info.splitStrings[i];
  printf("substring: '%s'\n", substr);
}

str_split_end(&info);

출력:

$ ./test
substring: ''
substring: 'hello'
substring: 'world'
substring: 'whats'
substring: 'going'
substring: 'on'
substring: 'today'

스트럿의 모든 소스.c

#ifndef STRUTILS_C
#define STRUTILS_C 1

#ifndef str
#define str char *
#endif

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

#include <stdio.h>

struct str_split_info {
  /* The string to be split
  * Provided by caller of str_split_begin function
  */
  str source;
  /* The string that cuts the source string, all occurances of
  * this string will be removed from the source string

  * Provided by caller of str_split_begin function
  */
  str delimiter;

  /* Array of strings split by delimiter
  * Provided and allocated by str_split_begin function
  * Must be garbage collected by str_split_end function
  */
  str * splitStrings;

  /* Array of string lengths split by delimiter
    * Provided and allocated by str_split_begin function
    * Must be garbage collected by str_split_end function
    */
  int * splitStringsLengths;

  /* Number of strings split by delimiter contained in splitStrings
  * Provided by str_split_begin function
  */
  int splitStringsCount;
};
#define str_split_infop struct str_split_info *

/* Split a string by a delimiting string
* 
* The caller is responsible only for calling str_split_end
* when finished with the results in 'info'
*/
void str_split_begin (str_split_infop info) {
  info->splitStringsCount = 0;

  int sourceLength = strlen(info->source);
  int sourceOffset = 0;
  char sourceChar;

  int delimiterLength = strlen(info->delimiter);
  int delimiterOffset = 0;
  char delimiterChar;

  //first pass, simply count occurances so we can allocate only once
  for (sourceOffset = 0; sourceOffset<sourceLength; sourceOffset++) {
    sourceChar = info->source[sourceOffset];
    delimiterChar = info->delimiter[delimiterOffset];

    if (sourceChar == delimiterChar) {
      delimiterOffset++;

      if (delimiterOffset >= delimiterLength) {
        delimiterOffset = 0;
        //increment count
        info->splitStringsCount ++;
      }
    } else {
      delimiterOffset = 0;
    }
  }
  info->splitStringsCount++;

  //allocate arrays since we know the count
  //this one is an array of strings, which are each char arrays
  info->splitStrings = (str *) malloc(sizeof (str *) * info->splitStringsCount);
  //this one is an array of ints
  info->splitStringsLengths = (int*) malloc(sizeof(int) *info->splitStringsCount);

  int stringBegin = 0;
  int stringEnd = 0;
  int splitIndex = 0;
  int splitLength = 0;

  //second pass, fill the arrays
  for (sourceOffset = 0; sourceOffset<sourceLength; sourceOffset++) {
    sourceChar = info->source[sourceOffset];
    delimiterChar = info->delimiter[delimiterOffset];

    if (sourceChar == delimiterChar) {
      delimiterOffset++;

      //if we've reached the end of the delimiter
      if (delimiterOffset >= delimiterLength) {

        //don't worry about delimiter trailing null, strlen doesn't count those
        stringEnd = sourceOffset - delimiterLength;
        
        //char count of substring we want to split
        splitLength = stringEnd - stringBegin + 1;

        //allocate for our substring split
        info->splitStrings[splitIndex] = (str) malloc(
          //+1 for trailing null for c-string
          sizeof(char) * splitLength + 1
        );

        //copy substring from source into splitStrings array
        memcpy(
          info->splitStrings[splitIndex],
          info->source + stringBegin,
          splitLength
        );
        //explicitly set the last char of this split to a NULL just for fun
        info->splitStrings[splitIndex][splitLength] = 0x00;

        //conveniently put the substring split size for the
        //user of str_split_begin :)
        info->splitStringsLengths[splitIndex] = splitLength;

        //move to next split index
        splitIndex ++;

        //reset delimiter offset so we look for new occurances of it
        delimiterOffset = 0;

        //next substring split should occur after the current delimiter
        stringBegin = sourceOffset+1;
      }
    } else {
      //reset delimiter offset so we look for new occurances of it
      delimiterOffset = 0;
    }
  }

  //handle edge case of last substring after last delimiter
  if (stringEnd != stringBegin) {
    stringEnd = sourceLength-1;

    splitLength = stringEnd - stringBegin + 1;

    //allocate for our substring split
    info->splitStrings[splitIndex] = (str) malloc(
      //+1 for trailing null for c-string
      sizeof(char) * splitLength + 1
    );

    //copy substring from source into splitStrings array
    memcpy(
      info->splitStrings[splitIndex],
      info->source + stringBegin,
      splitLength
    );
    
  }
}
int str_split_count (str_split_infop info) {
  return info->splitStringsCount;
}

void str_split_get (str_split_infop info, str * out) {
  for (int i=0; i < info->splitStringsCount; i++) {
    strcpy(out[i], info->splitStrings[i]);
  }
}

void str_split_end (str_split_infop info) {
  if (info->splitStringsCount > 0 && info->splitStrings != NULL) {
    //free each string allocated
    for (int i=0; i < info->splitStringsCount; i++) {
      free(info->splitStrings[i]);
    }
    //free string array pointer
    free (info->splitStrings);

    //free string lengths array pointer
    free(info->splitStringsLengths);

    info->splitStringsCount = 0;
  }
}

void str_split_test () {
  char * source = "hello world this is a test";
  str delimiter = " ";

  struct str_split_info info;
  
  info.source = source;
  info.delimiter = delimiter;

  str_split_begin (&info);

  //iterate thru split substrings
  //NOTE: removed/memory cleanup after str_split_end
  for (int i=0; i<info.splitStringsCount; i++) {
    // info.splitStrings[i];
  }

  str_split_end(&info);
}

#endif

언급URL : https://stackoverflow.com/questions/9210528/split-string-with-delimiters-in-c

반응형