Pisahkan string dengan pembatas di C


155

Bagaimana cara menulis fungsi untuk membagi dan mengembalikan array untuk string dengan pembatas dalam bahasa pemrograman C?

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

25
Anda dapat menggunakan strtokfungsi dari perpustakaan standar untuk mencapai hal yang sama.
Daniel Kamil Kozar


Sebuah komentar ... titik kunci untuk strtok()fungsi keluarga adalah pemahaman static variablesdalam C. yaitu bagaimana mereka berperilaku antara pemanggilan fungsi berturut-turut di mana mereka digunakan. Lihat kode saya di bawah ini
fnisi

Jawaban:


165

Anda dapat menggunakan strtok()fungsi untuk membagi string (dan menentukan pembatas yang akan digunakan). Catatan yang strtok()akan memodifikasi string yang diteruskan ke dalamnya. Jika string asli diperlukan di tempat lain, buat salinannya dan berikan salinannya strtok().

EDIT:

Contoh (perhatikan itu tidak menangani pembatas berturut-turut, "JAN ,,, FEB, MAR" misalnya):

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

Keluaran:

$ ./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
Hai! yang strtokditandai sebagai usang oleh strsep(3)di halaman manual.
osgx

4
Karena ini mungkin pertanyaan / jawaban kanonik pada Stack Overflow untuk ini, bukankah ada beberapa peringatan sehubungan dengan multi-threading menggunakan strtok?
Peter Mortensen

3
@osgx Menurut halaman itu, strsepadalah pengganti untuk strtok, tetapi strtoklebih disukai untuk portabilitas. Jadi, kecuali Anda membutuhkan dukungan untuk bidang kosong atau memecah beberapa string sekaligus, strtokadalah pilihan yang lebih baik.

4
@ Dojo: Itu mengingatnya; itulah salah satu alasannya bermasalah. Akan lebih baik untuk menggunakan strtok_s()(Microsoft, C11 Annex K, opsional) atau strtok_r()(POSIX) daripada biasa strtok(). Dataran strtok()itu jahat dalam fungsi perpustakaan. Tidak ada fungsi yang memanggil fungsi perpustakaan yang dapat digunakan strtok()pada saat itu, dan tidak ada fungsi yang dipanggil oleh fungsi perpustakaan yang dapat dipanggil strtok().
Jonathan Leffler

3
Hanya sebuah catatan yang strtok()tidak aman utas (karena alasan @ JonathanLeffler disebutkan) dan oleh karena itu seluruh fungsi ini tidak aman utas. Jika Anda mencoba menggunakan ini di lingkungan yang beralur, Anda akan mendapatkan hasil yang tidak menentu dan tidak dapat diprediksi. Mengganti strtok()untuk strtok_r()perbaikan masalah ini.
Sean W

70

Saya pikir strsepmasih alat terbaik untuk ini:

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

Itu secara harfiah satu baris yang memisahkan sebuah string.

Kurung tambahan adalah elemen gaya untuk menunjukkan bahwa kami sengaja menguji hasil penugasan, bukan operator kesetaraan ==.

Agar pola itu berfungsi, tokendan strkeduanya memiliki tipe char *. Jika Anda mulai dengan string literal, maka Anda ingin membuat salinannya terlebih dahulu:

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

Jika dua pembatas muncul bersamaan str, Anda akan mendapatkan tokennilai yang merupakan string kosong. Nilai strdimodifikasi dalam bahwa setiap pembatas yang ditemui ditimpa dengan byte nol - alasan lain yang baik untuk menyalin string yang diuraikan terlebih dahulu.

Dalam komentar, seseorang menyarankan itu strtoklebih baik daripada strsepkarena strtoklebih portabel. Ubuntu dan Mac OS X miliki strsep; aman untuk menebak bahwa sistem unixy lain juga melakukannya. Windows kekurangan strsep, tetapi memiliki strbrkyang memungkinkan strseppenggantian yang singkat dan manis ini :

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

Berikut adalah penjelasan yang baik strsepvs strtok. Pro dan kontra dapat dinilai secara subyektif; Namun, saya pikir itu adalah tanda yang strsepdirancang sebagai pengganti strtok.


3
Lebih tepatnya pada portabilitas: ini bukan POSIX 7 , tetapi BSD yang diturunkan, dan diimplementasikan pada glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Saya baru saja akan bertanya ... Pelle's C memiliki strdup (), tetapi tidak ada strsep ().
rdtsc

1
mengapa tofreeyang gratis dan tidak str?
Sdlion

1
Anda tidak dapat membebaskan strkarena nilainya dapat diubah oleh panggilan ke strsep(). Nilai tofreetitik secara konsisten mengarah ke awal memori yang ingin Anda bebaskan.
Tyler

26

String tokenizer kode ini harus menempatkan Anda ke arah yang benar.

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

Metode di bawah ini akan melakukan semua pekerjaan (alokasi memori, menghitung panjang) untuk Anda. Informasi dan deskripsi lebih lanjut dapat ditemukan di sini - Implementasi metode Java String.split () untuk membagi string 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;
}

Bagaimana cara menggunakannya:

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 Programmer bintang tiga :)) Ini terdengar menarik.
Michi

Ketika saya melakukan ini, itu menambah terlalu banyak token terakhir, atau mengalokasikan terlalu banyak memori. Ini adalah outputnya: 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
Contoh ini memiliki banyak kebocoran memori. Bagi siapa pun yang membaca ini, jangan gunakan pendekatan ini. Lebih suka pendekatan strtok atau strsep tokenization.
Jorma Rebane

7

Ini dua sen saya:

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

Pemakaian:

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
oh boi, tiga petunjuk! Saya sudah takut menggunakannya lol itu hanya saya, saya tidak begitu baik dengan pointer di c.
Hafiz Temuri

Terima kasih sobat, semua jawaban strtok di atas tidak berfungsi dalam kasus saya bahkan setelah banyak upaya, dan kode Anda bekerja seperti pesona!
hmmftg

4

Dalam contoh di atas, akan ada cara untuk mengembalikan array string yang diakhiri null (seperti yang Anda inginkan) di tempat dalam string. Itu tidak akan memungkinkan untuk meneruskan string literal, karena itu harus dimodifikasi oleh fungsi:

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

Mungkin ada cara yang lebih rapi untuk melakukannya, tetapi Anda mendapatkan idenya.


3

Fungsi ini mengambil string char * dan membaginya dengan pembatas. Mungkin ada beberapa pemisah dalam satu baris. Perhatikan bahwa fungsi memodifikasi string orignal. Anda harus membuat salinan string asli terlebih dahulu jika Anda ingin yang asli tetap tidak berubah. Fungsi ini tidak menggunakan panggilan fungsi cstring apa pun sehingga mungkin sedikit lebih cepat daripada yang lain. Jika Anda tidak peduli dengan alokasi memori, Anda dapat mengalokasikan sub_strings di bagian atas fungsi dengan ukuran strlen (src_str) / 2 dan (seperti c ++ "versi" yang disebutkan) melewati bagian bawah fungsi. Jika Anda melakukan ini, fungsi dikurangi menjadi O (N), tetapi cara memori yang dioptimalkan di bawah ini adalah O (2N).

Fungsi:

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

Bagaimana cara menggunakannya:

  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

Di bawah ini adalah strtok()implementasi saya dari perpustakaan zString . zstring_strtok()berbeda dari perpustakaan standar strtok()dalam cara memperlakukan pembatas berurutan.

Lihat saja kode di bawah ini, yakin bahwa Anda akan mendapatkan ide tentang cara kerjanya (saya mencoba menggunakan sebanyak mungkin komentar)

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

Di bawah ini adalah contoh penggunaan ...

  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)

Perpustakaan dapat diunduh dari Github https://github.com/fnoyanisi/zString


bagus itulah yang saya cari.
Kostia Kim

3

Saya pikir solusi berikut ini ideal:

  • Tidak menghancurkan string sumber
  • Masuk kembali - yaitu, Anda dapat dengan aman menyebutnya dari mana saja dalam satu utas atau lebih
  • Portable
  • Menangani banyak pemisah dengan benar
  • Cepat dan efisien

Penjelasan kode:

  1. Tetapkan struktur tokenuntuk menyimpan alamat dan panjang token
  2. Alokasikan memori yang cukup untuk ini dalam kasus terburuk, yaitu ketika strseluruhnya terdiri dari separator sehingga ada strlen(str) + 1 token, semuanya string kosong
  3. Pindai strrekaman alamat dan panjang setiap token
  4. Gunakan ini untuk mengalokasikan larik keluaran dengan ukuran yang benar, termasuk ruang ekstra untuk NULLnilai sentinel
  5. Alokasikan, salin, dan tambahkan token menggunakan informasi awal dan panjang - gunakan memcpykarena lebih cepat dari strcpydan kita tahu panjangnya
  6. Bebaskan alamat token dan larik panjang
  7. Kembalikan array token
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;
}

Catatan malloc memeriksa dihapus untuk singkatnya.

Secara umum, saya tidak akan mengembalikan array char *pointer dari fungsi split seperti ini karena menempatkan banyak tanggung jawab pada penelepon untuk membebaskan mereka dengan benar. Sebuah antarmuka Saya lebih suka adalah untuk memungkinkan penelpon untuk lulus fungsi callback dan panggilan ini untuk setiap token, seperti yang telah saya dijelaskan di sini: Split String di C .


Memindai pemisah dua kali mungkin lebih disarankan daripada mengalokasikan array yang berpotensi besar token.
chqrlie

2

Coba gunakan ini.

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

Metode yang dioptimalkan ini membuat (atau memperbarui array yang ada) dari pointer dalam hasil * dan mengembalikan jumlah elemen dalam jumlah *.

Gunakan "maks" untuk menunjukkan jumlah maksimum string yang Anda harapkan (ketika Anda menentukan array yang ada atau alasan lain), jika tidak set ke 0

Untuk membandingkan dengan daftar pembatas, tentukan delim sebagai char * dan ganti baris:

if (str[i]==delim) {

dengan dua baris berikut:

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

Nikmati

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

Contoh penggunaan:

#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

Versi saya:

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

Ini adalah fungsi pemisahan string yang dapat menangani pembatas multi-karakter. Perhatikan bahwa jika pembatas lebih panjang dari string yang sedang dipisah, maka bufferdan stringLengthsakan diatur ke (void *) 0, dan numStringsakan diatur ke 0.

Algoritma ini telah diuji, dan berfungsi. (Penafian: Ini belum diuji untuk string non-ASCII, dan mengasumsikan bahwa penelepon memberikan parameter yang valid)

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

Kode sampel:

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

Perpustakaan:

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

Bagaimana saya menyebutnya dari main? Saya tidak tahu apa yang harus dilewatkan ke buffer.
Aymon Fournier

Logika alokasi salah. realloc () mengembalikan pointer baru dan Anda membuang nilai yang dikembalikan. Tidak ada cara yang tepat untuk mengembalikan penunjuk memori baru - prototipe fungsi harus diubah untuk menerima ukuran yang dialokasikan bufferdan membiarkan alokasi untuk pemanggil, memproses elemen ukuran maks.
Alex

@Alex Tetap, sepenuhnya ditulis ulang, dan diuji. Catatan: tidak yakin apakah ini akan berfungsi untuk non-ASCII atau tidak.
Élektra

Sebagai permulaan, ini bukan kode C. Dan mengapa Anda melewatkan pointer dengan referensi aktual di C ++?
Kamiccolo

@ Kamiccolo Maaf, bagaimana tepatnya ini bukan kode C? Juga, mengapa melewati pointer dengan referensi masalah di sini?
Élektra

1

Pendekatan saya adalah memindai string dan membiarkan pointer menunjuk ke setiap karakter setelah pembatas (dan karakter pertama), pada saat yang sama menetapkan tampilan pembatas dalam string ke '\ 0'.
Pertama buat salinan string asli (karena itu konstan), kemudian dapatkan jumlah pemisahan dengan memindainya meneruskannya ke parameter pointer len . Setelah itu, arahkan pointer hasil pertama ke pointer string copy, lalu pindai string copy: sekali menemui pembatas, tetapkan ke '\ 0' sehingga string hasil sebelumnya diakhiri, dan arahkan pointer string hasil berikutnya ke yang berikutnya penunjuk karakter.

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

Metode ini salah. Saya baru saja menghapus posting ini, tetapi kemudian saya menyadari bahwa mungkin menarik bagi sebagian dari Anda.
metalcrash

1

Kode saya (diuji):

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

Hasil:

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
Sadarilah bahwa fungsi strtok mengubah string 'str' diterapkan!
SchLx

1

Explode & implode - string awal tetap utuh, alokasi memori dinamis

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

Pemakaian:

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

Jika Anda bersedia menggunakan perpustakaan eksternal, saya tidak bisa merekomendasikan bstrlibcukup. Dibutuhkan sedikit pengaturan ekstra, tetapi lebih mudah digunakan dalam jangka panjang.

Misalnya, pisahkan string di bawah ini, yang pertama buat bstringdengan bfromcstr()panggilan. (A bstringadalah pembungkus di sekitar buffer char). Selanjutnya, pisahkan string pada koma, simpan hasilnya dalam a struct bstrList, yang memiliki bidang qtydan array entry, yang merupakan array dari bstrings.

bstrlibmemiliki banyak fungsi lain untuk beroperasi pada bstrings

Mudah seperti pai ...

#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

Namun jawaban lain (ini dipindahkan ke sini dari sini ):

Coba gunakan fungsi strtok:

lihat detail tentang topik ini di sini atau di sini

Masalahnya di sini adalah Anda harus wordssegera memprosesnya . Jika Anda ingin menyimpannya dalam array Anda harus mengalokasikan correct sizeuntuk itu penyihir tidak diketahui.

Jadi misalnya:

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

}

Catatan : Kami menggunakan loop dan fungsi yang sama untuk menghitung jumlah (lulus satu) dan untuk membuat salinan (lulus dua), untuk menghindari masalah alokasi.

Catatan 2 : Anda dapat menggunakan beberapa implementasi strtok lainnya dengan alasan yang disebutkan dalam posting terpisah.

Anda bisa menggunakan ini seperti:

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

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

(Saya tidak mengujinya, jadi tolong beri tahu saya jika itu tidak berhasil!)


0

Dua masalah seputar pertanyaan ini adalah manajemen memori dan keamanan utas. Seperti yang dapat Anda lihat dari banyak posting, ini bukan tugas yang mudah untuk diselesaikan dengan mulus di C. Saya menginginkan solusi yaitu:

  • Aman utas. (strtok bukan thread aman)
  • Tidak menggunakan malloc atau turunannya (untuk menghindari masalah manajemen memori)
  • Memeriksa batas array pada bidang individual (untuk menghindari kesalahan segmen pada data yang tidak diketahui)
  • Bekerja dengan pemisah bidang multi-byte (utf-8)
  • mengabaikan bidang tambahan dalam input
  • menyediakan soft error rutin untuk panjang bidang tidak valid

Solusi yang saya temukan memenuhi semua kriteria ini. Mungkin sedikit lebih banyak pekerjaan yang perlu disiapkan daripada beberapa solusi lain yang diposting di sini, tetapi saya pikir dalam praktiknya, pekerjaan tambahan itu sepadan untuk menghindari perangkap umum dari solusi lain.

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

Di bawah ini adalah contoh kompilasi dan keluaran. Perhatikan bahwa dalam contoh saya, saya sengaja mengeja "APRIL" sehingga Anda dapat melihat cara kerja kesalahan lunak.

$ 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

Nikmati!


0

Berikut ini adalah implementasi lain yang akan beroperasi dengan aman untuk tokenize string-literal yang cocok dengan prototipe yang diminta dalam pertanyaan mengembalikan pointer-to-pointer yang dialokasikan untuk char (misalnya char **). String pembatas dapat berisi beberapa karakter, dan string input dapat berisi sejumlah token. Semua alokasi dan realokasi ditangani oleh mallocatau realloctanpa POSIX strdup.

Jumlah awal dari pointer yang dialokasikan dikendalikan oleh NPTRSkonstanta dan satu-satunya batasan adalah bahwa itu lebih besar dari nol. Yang char **dikembalikan berisi sentinel NULL setelah token terakhir mirip dengan *argv[]dan dalam bentuk yang dapat digunakan oleh execv, execvpdan execve.

Seperti dengan strtok()beberapa pembatas berurutan yang diperlakukan sebagai pembatas tunggal, demikian "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"juga akan diurai seolah-olah hanya satu pemisah yang ','terpisah "MAY,JUN".

Fungsi di bawah ini dikomentari secara in-line dan sebuah pendek main()ditambahkan untuk membagi bulan. Jumlah awal dari pointer yang dialokasikan ditetapkan 2untuk memaksa tiga realokasi selama tokenizing string input:

#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 */
    }
}

Contoh Penggunaan / Output

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

Beri tahu saya jika Anda memiliki pertanyaan lebih lanjut.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.