แยกสตริงด้วยตัวคั่นใน 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
Hi! strtokถูกทำเครื่องหมายเป็นละทิ้งstrsep(3)ในหน้าคน
osgx

4
เนื่องจากอาจเป็นคำถาม / คำตอบที่ยอมรับได้ของ Stack Overflow สำหรับสิ่งนี้ไม่มีข้อควรพิจารณาบางประการเกี่ยวกับมัลติเธรดโดยใช้ strtok หรือไม่?
ปีเตอร์มอร์เทนเซ่น

3
@osgx ตามหน้านั้นstrsepจะเปลี่ยนสำหรับstrtokแต่strtokเป็นที่ต้องการสำหรับการพกพา ดังนั้นหากคุณไม่ต้องการการสนับสนุนเขตข้อมูลว่างเปล่าหรือแยกสตริงหลายรายการพร้อมกันstrtokเป็นตัวเลือกที่ดีกว่า

4
@Dojo: มันจำได้ นั่นเป็นเหตุผลข้อหนึ่งที่เป็นปัญหา มันจะดีกว่าที่จะใช้strtok_s()(Microsoft, C11 ภาคผนวก 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);

นั่นคือหนึ่งบรรทัดที่แยกสตริง

วงเล็บพิเศษเป็นองค์ประกอบของสไตลิสต์เพื่อระบุว่าเรากำลังทดสอบผลลัพธ์ของการมอบหมายโดยไม่==ตั้งใจ

สำหรับรูปแบบที่ไปทำงาน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คุณจะได้รับtokenค่าที่เป็นสตริงว่าง มูลค่าของstrการแก้ไขในแต่ละคั่นพบจะถูกเขียนทับด้วยไบต์ศูนย์ - อีกเหตุผลที่ดีที่จะคัดลอกสตริงถูกแยกแรก

ในความคิดเห็นมีคนแนะนำว่าstrtokดีกว่าstrsepเพราะstrtokพกพาได้มากกว่า Ubuntu และ Mac OS X มีstrsep; มันปลอดภัยที่จะคาดเดาว่าระบบยูนิกซ์อื่น ๆ ก็ทำได้เช่นกัน 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;
}

นี่เป็นคำอธิบายที่ดีของVSstrsep strtokข้อดีและข้อเสียอาจได้รับการตัดสินอย่างเป็นส่วนตัว แต่ฉันคิดว่ามันเป็นสัญญาณที่บอกว่าได้รับการออกแบบเป็นแทนสำหรับstrsepstrtok


3
แม่นยำมากขึ้นในการพกพา: มันเป็นไม่ POSIX 7แต่ BSD มาและดำเนินการในglibc
Ciro Santilli 法轮功冠状病六四事件法轮功

ฉันเพิ่งจะถาม ... เพลเลของ C มี strdup () แต่ไม่มี strsep ()
RDTSC

1
ทำไมtofreeเป็นหนึ่ง free'd และไม่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

วิธีการด้านล่างจะทำงานทั้งหมด (การจัดสรรหน่วยความจำการนับความยาว) สำหรับคุณ ข้อมูลเพิ่มเติมและคำอธิบายสามารถพบได้ที่นี่ - การดำเนินการของ Java String.split () วิธีการแยกสตริง C

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 โปรแกรมเมอร์สามดาว :)) ฟังดูน่าสนใจ
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
โอ้ Boi สามตัวชี้! ฉันกลัวที่จะใช้มันแล้วมันเป็นแค่ฉันฉันไม่ดีกับพอยน์เตอร์ใน c
Hafiz Temuri

ขอขอบคุณทุกคนคำตอบ strtok ข้างต้นไม่ได้ทำงานในกรณีของฉันแม้หลังจากความพยายามมากและรหัสของคุณทำงานเหมือนมีเสน่ห์!
hmmftg

4

ในตัวอย่างข้างต้นจะมีวิธีการคืนค่าอาร์เรย์ของสตริงที่สิ้นสุดด้วยค่า null (เช่นที่คุณต้องการ) ในสตริง มันจะไม่ทำให้เป็นไปได้ที่จะผ่านสตริงตัวอักษรแม้ว่ามันจะต้องมีการแก้ไขโดยฟังก์ชั่น:

#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 * และแยกโดย deliminator อาจมีผู้กระทำผิดหลายคนในแถว โปรดทราบว่าฟังก์ชั่นแก้ไขสตริง orignal คุณต้องทำสำเนาของสตริงต้นฉบับก่อนหากคุณต้องการให้ต้นฉบับไม่เปลี่ยนแปลง ฟังก์ชั่นนี้ไม่ได้ใช้ฟังก์ชั่น cstring ใด ๆ ดังนั้นมันอาจจะเร็วกว่าคนอื่นเล็กน้อย หากคุณไม่สนใจเกี่ยวกับการจัดสรรหน่วยความจำคุณสามารถจัดสรร sub_strings ที่ด้านบนของฟังก์ชั่นด้วยขนาด 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);

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ค่า Sentinel
  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


สแกนหาคั่นสองครั้งน่าจะเป็นมากขึ้นแนะนำให้เลือกกว่าจัดสรร array 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

วิธีการปรับให้เหมาะสมนี้สร้าง (หรืออัปเดตอาร์เรย์ที่มีอยู่) ของพอยน์เตอร์ในผลลัพธ์ * และส่งกลับจำนวนขององค์ประกอบในจำนวน *

ใช้ "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

นี่คือฟังก์ชันการแยกสตริงที่สามารถจัดการตัวคั่นหลายตัว โปรดทราบว่าถ้าตัวคั่นมีความยาวมากกว่าสตริงที่จะถูกแยกแล้วbufferและstringLengthsจะถูกตั้งค่า(void *) 0และจะถูกตั้งค่าnumStrings0

อัลกอริทึมนี้ได้รับการทดสอบและใช้งานได้ (ข้อจำกัดความรับผิดชอบ: ยังไม่ได้รับการทดสอบสำหรับสตริงที่ไม่ใช่ 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 แก้ไขเขียนใหม่และทดสอบอย่างสมบูรณ์ หมายเหตุ: ไม่แน่ใจว่าสิ่งนี้จะใช้ได้กับผู้ที่ไม่ใช่ 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

Explode & implode - สตริงเริ่มต้นยังคงไม่เปลี่ยนแปลงการจัดสรรหน่วยความจำแบบไดนามิก

#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คือ wrapper รอบ ๆ char buffer) ถัดไปแบ่งสตริงด้วยเครื่องหมายจุลภาคโดยบันทึกผลลัพธ์เป็น a struct bstrListซึ่งมีฟิลด์qtyและอาร์เรย์entryซึ่งเป็นอาร์เรย์bstrings

bstrlib มีฟังก์ชั่นอื่น ๆ อีกมากมายที่จะใช้งาน bstring s

ง่ายเหมือนพาย ...

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

}

หมายเหตุ : เราใช้ลูปและฟังก์ชั่นเดียวกันในการคำนวณจำนวน (ส่งหนึ่ง) และทำสำเนา (ผ่านสอง) เพื่อหลีกเลี่ยงปัญหาการจัดสรร

หมายเหตุ 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

สองประเด็นที่อยู่รอบ ๆ คำถามนี้คือการจัดการหน่วยความจำและความปลอดภัยของเธรด อย่างที่คุณเห็นจากโพสต์ต่าง ๆ นี่ไม่ใช่งานง่ายที่จะทำให้สำเร็จอย่างราบรื่นในซีฉันต้องการโซลูชันที่:

  • ด้ายปลอดภัย (strtok ไม่ปลอดภัยสำหรับเธรด)
  • ไม่ใช้ malloc หรืออนุพันธ์ใด ๆ (เพื่อหลีกเลี่ยงปัญหาการจัดการหน่วยความจำ)
  • ตรวจสอบขอบเขตของอาเรย์บนแต่ละฟิลด์ (เพื่อหลีกเลี่ยงข้อผิดพลาดของเซ็กเมนต์ในข้อมูลที่ไม่รู้จัก)
  • ทำงานร่วมกับตัวแยกฟิลด์แบบหลายไบต์ (utf-8)
  • ละเว้นฟิลด์พิเศษในอินพุต
  • จัดเตรียมรูทีนข้อผิดพลาดแบบ soft สำหรับความยาวฟิลด์ที่ไม่ถูกต้อง

วิธีการแก้ปัญหาที่ฉันเกิดขึ้นตรงตามเกณฑ์เหล่านี้ทั้งหมด อาจเป็นงานที่ต้องติดตั้งอีกเล็กน้อยกว่าโซลูชันอื่น ๆ ที่โพสต์ไว้ที่นี่ แต่ฉันคิดว่าในทางปฏิบัติงานพิเศษนั้นคุ้มค่าเพื่อหลีกเลี่ยงข้อผิดพลาดทั่วไปของโซลูชันอื่น ๆ

#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

นี่คือการดำเนินการที่จะทำงานได้อย่างปลอดภัยเพื่อ tokenize อื่นสตริงตัวอักษรตรงกับต้นแบบที่มีการร้องขอในคำถามที่ส่งคืนจัดสรรชี้ไปชี้ไปยังถ่าน (เช่นchar **) สตริงตัวคั่นสามารถมีหลายอักขระและสตริงอินพุตสามารถมีโทเค็นจำนวนเท่าใดก็ได้ การจัดสรรและการจัดสรรทั้งหมดจะถูกจัดการโดยmallocหรือreallocโดยไม่ต้อง strdupPOSIX

จำนวนพอยน์เตอร์เริ่มต้นที่จัดสรรนั้นควบคุมโดยNPTRSค่าคงที่และข้อ จำกัด เพียงอย่างเดียวคือมันมากกว่าศูนย์ char **กลับมีแมวมอง NULLหลังจากที่ผ่านมาโทเค็นคล้ายกับ*argv[]และในรูปแบบที่ใช้งานได้โดยexecv, execvpและexecveและ

เช่นเดียวกับstrtok()ตัวคั่นลำดับหลายจะถือว่าเป็นตัวคั่นเดียวดังนั้น"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"จะถูกแยกเป็นถ้าเพียงคนเดียวแยก','"MAY,JUN"

ฟังก์ชั่นด้านล่างแสดงความคิดเห็นในบรรทัดและสั้นmain()เพิ่มเข้ามาแยกเดือน จำนวนพอยน์เตอร์เริ่มต้นที่จัดสรรได้รับการตั้งค่า2เพื่อบังคับให้มีการจัดสรรใหม่สามครั้งในระหว่างการโทเค็นสตริงอินพุต:

#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.