Geteilte Zeichenfolge mit Trennzeichen in C.

155

Wie schreibe ich eine Funktion zum Teilen und Zurückgeben eines Arrays für einen String mit Trennzeichen in der Programmiersprache C?

char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');
namco
quelle
25
Sie können die strtokFunktion aus der Standardbibliothek verwenden, um dasselbe zu erreichen.
Daniel Kamil Kozar
Ein Kommentar ... der entscheidende Punkt für eine strtok()Familienfunktion ist das Verständnis static variablesin C. dh wie sie sich zwischen aufeinanderfolgenden Funktionsaufrufen verhalten, in denen sie verwendet werden. Siehe meinen Code unten
fnisi

Antworten:

165

Mit der strtok()Funktion können Sie eine Zeichenfolge teilen (und das zu verwendende Trennzeichen angeben). Beachten Sie, dass dadurch strtok()die übergebene Zeichenfolge geändert wird. Wenn die Originalzeichenfolge an anderer Stelle benötigt wird, erstellen Sie eine Kopie davon und übergeben Sie die Kopie an strtok().

BEARBEITEN:

Beispiel (beachten Sie, dass keine aufeinanderfolgenden Trennzeichen behandelt werden, z. B. "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;
}

Ausgabe:

$ ./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]
hmjd
quelle
60
Hallo! Das strtokist strsep(3)in der Manpage als veraltet markiert .
Osgx
4
Gibt es nicht einige Einschränkungen in Bezug auf Multithreading mit strtok, da dies die kanonische Frage / Antwort zu Stack Overflow sein kann?
Peter Mortensen
3
@osgx Laut dieser Seite strsepist es ein Ersatz für strtok, wird aber aus Gründen der strtokPortabilität bevorzugt. Es strtokist also eine bessere Wahl , es sei denn, Sie benötigen Unterstützung für leere Felder oder das Aufteilen mehrerer Zeichenfolgen gleichzeitig .
4
@ Dojo: Es erinnert sich daran; Das ist einer der Gründe, warum es problematisch ist. Es wäre besser, strtok_s()(Microsoft, C11 Annex K, optional) oder strtok_r()(POSIX) als normal zu verwenden strtok(). Einfach strtok()ist böse in einer Bibliotheksfunktion. Derzeit darf keine Funktion verwendet werden, die die Bibliotheksfunktion aufruft strtok(), und es darf keine Funktion aufgerufen werden, die von der Bibliotheksfunktion aufgerufen wird strtok().
Jonathan Leffler
3
Nur ein Hinweis, der strtok()nicht threadsicher ist (aus den genannten Gründen @JonathanLeffler) und daher ist diese ganze Funktion nicht threadsicher. Wenn Sie versuchen, dies in einer Umgebung mit Profil zu verwenden, erhalten Sie unberechenbare und unvorhersehbare Ergebnisse. Das Ersetzen strtok()für strtok_r()behebt dieses Problem.
Sean W
70

Ich denke strsepist immer noch das beste Werkzeug dafür:

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

Das ist buchstäblich eine Zeile, die eine Zeichenfolge teilt.

Die zusätzlichen Klammern sind ein Stilelement, das darauf hinweist, dass wir absichtlich das Ergebnis einer Zuweisung testen, nicht einen Gleichheitsoperator ==.

Damit dieses Muster funktioniert tokenund strbeide einen Typ haben char *. Wenn Sie mit einem Zeichenfolgenliteral begonnen haben, möchten Sie zuerst eine Kopie davon erstellen:

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

Wenn zwei Trennzeichen zusammen in strangezeigt werden, erhalten Sie einen tokenWert, der die leere Zeichenfolge ist. Der Wert von strwird dahingehend geändert, dass jedes gefundene Trennzeichen mit einem Null-Byte überschrieben wird - ein weiterer guter Grund, die zuerst analysierte Zeichenfolge zu kopieren.

In einem Kommentar schlug jemand vor, dass dies strtokbesser ist als strsepweil strtokes portabler ist. Ubuntu und Mac OS X haben strsep; Man kann davon ausgehen, dass dies auch bei anderen Unixy-Systemen der Fall ist. Windows fehlt strsep, aber es hat, strbrkwas diesen kurzen und süßen strsepErsatz ermöglicht:

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

Hier ist eine gute Erklärung für strsepvs strtok. Die Vor- und Nachteile können subjektiv beurteilt werden; Ich denke jedoch, dass es ein aussagekräftiges Zeichen ist, strsepdas als Ersatz für konzipiert wurde strtok.

Tyler
quelle
3
Genauer gesagt zur Portabilität: Es ist nicht POSIX 7 , sondern BSD-abgeleitet und auf glibc implementiert .
Ciro Santilli 法轮功 冠状 病 六四 事件 20
Ich wollte gerade fragen ... Pelles C hat strdup (), aber kein strsep ().
rdtsc
1
warum tofreeist der frei und nicht str?
Sdlion
1
Sie können nicht freigeben, strda der Wert durch Aufrufe von geändert werden kann strsep(). Der Wert von zeigt tofreekonsistent auf den Anfang des Speichers, den Sie freigeben möchten.
Tyler
26

String Tokenizer Dieser Code sollte Sie in die richtige Richtung bringen.

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;
}
thenetimp
quelle
13

Die folgende Methode erledigt den gesamten Job (Speicherzuweisung, Zählen der Länge) für Sie. Weitere Informationen und eine Beschreibung finden Sie hier - Implementierung der Java String.split () -Methode zum Teilen von C-Strings

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

Wie man es benutzt:

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;
}
user1090944
quelle
4
Huh Drei-Sterne-Programmierer :)) Das klingt interessant.
Michi
Wenn ich das mache, fügt es dem letzten Token entweder zu viel hinzu oder weist ihm zu viel Speicher zu. Dies ist die Ausgabe: 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.¢
KeiserHarm
2
Dieses Beispiel weist mehrere Speicherlecks auf. Verwenden Sie diesen Ansatz nicht, wenn Sie dies lesen. Bevorzugen Sie stattdessen Strtok- oder Strsep-Tokenisierungsansätze.
Jorma Rebane
7

Hier sind meine zwei Cent:

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

Verwendung:

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);
Razz
quelle
3
Oh Boi, drei Zeiger! Ich habe bereits Angst davor, es zu benutzen, lol es ist nur ich, ich bin nicht sehr gut mit Zeigern in c.
Hafiz Temuri
Vielen Dank, Mann, alle oben genannten Antworten haben in meinem Fall auch nach vielen Anstrengungen nicht funktioniert, und Ihr Code funktioniert wie ein Zauber!
Hmmftg
4

Im obigen Beispiel gibt es eine Möglichkeit, ein Array von nullterminierten Zeichenfolgen (wie gewünscht) in der Zeichenfolge zurückzugeben. Es wäre jedoch nicht möglich, eine Literalzeichenfolge zu übergeben, da diese durch die folgende Funktion geändert werden müsste:

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

Es gibt wahrscheinlich einen besseren Weg, dies zu tun, aber Sie haben die Idee.

Matt
quelle
3

Diese Funktion nimmt eine Zeichenfolge * und teilt sie durch das Trennzeichen auf. Es können mehrere Trennzeichen hintereinander vorhanden sein. Beachten Sie, dass die Funktion die ursprüngliche Zeichenfolge ändert. Sie müssen zuerst eine Kopie der Originalzeichenfolge erstellen, wenn das Original unverändert bleiben soll. Diese Funktion verwendet keine cstring-Funktionsaufrufe und ist daher möglicherweise etwas schneller als andere. Wenn Sie sich nicht für die Speicherzuweisung interessieren, können Sie am oberen Rand der Funktion Unterstrings mit der Größe strlen (src_str) / 2 zuweisen und (wie in der erwähnten c ++ - "Version") die untere Hälfte der Funktion überspringen. Wenn Sie dies tun, wird die Funktion auf O (N) reduziert, aber die unten gezeigte speicheroptimierte Methode ist O (2N).

Die Funktion:

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

Wie man es benutzt:

  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);
Sam Petrocelli
quelle
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;
}
adamsch1
quelle
3

Unten ist meine strtok()Implementierung aus der zString-Bibliothek . zstring_strtok()unterscheidet sich von Standardbibliotheken strtok()darin, wie aufeinanderfolgende Trennzeichen behandelt werden.

Schauen Sie sich einfach den folgenden Code an, um sicherzugehen, dass Sie eine Vorstellung davon bekommen, wie er funktioniert (ich habe versucht, so viele Kommentare wie möglich zu verwenden).

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

Unten ist ein Beispiel für die Verwendung ...

  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)

Die Bibliothek kann von Github https://github.com/fnoyanisi/zString heruntergeladen werden

fnisi
quelle
Schön! das habe ich gesucht.
Kostia Kim
3

Ich halte folgende Lösung für ideal:

  • Zerstört die Quellzeichenfolge nicht
  • Wiedereinsteiger - dh Sie können es sicher von überall in einem oder mehreren Threads aufrufen
  • tragbar
  • Behandelt mehrere Trennzeichen korrekt
  • Schnell und effizient

Erläuterung des Codes:

  1. Definieren Sie eine Struktur token zum Speichern der Adresse und Länge der Token
  2. Weisen Sie im schlimmsten Fall genügend Speicher für diese zu, wenn dies strvollständig aus Trennzeichen bestehtstrlen(str) + 1 Token , die alle leere Zeichenfolgen sind
  3. Scan str zeichnet die Adresse und Länge jedes Tokens auf
  4. Verwenden Sie diese Option, um das Ausgabearray mit der richtigen Größe zuzuweisen, einschließlich eines zusätzlichen Speicherplatzes für a NULL Sentinel-Wert
  5. Ordnen Sie die Token anhand der Start- und Längeninformationen zu, kopieren Sie sie und fügen Sie memcpysie hinzustrcpy und wir die Längen kennen
  6. Geben Sie die Token-Adresse und das Längen-Array frei
  7. Geben Sie das Array der Token zurück
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;
}

Beachten Sie, dass die malloc Überprüfung der Kürze halber weggelassen wurde.

Im Allgemeinen würde ich ein Array von char *Zeigern von einer solchen Split-Funktion nicht zurückgeben, da dies dem Aufrufer eine große Verantwortung auferlegt, sie korrekt freizugeben. Eine Schnittstelle Ich ziehe es ist der Anrufer zu ermöglichen , eine Callback - Funktion zu übergeben und dies für jedes Zeichen nennen, wie ich hier beschrieben habe: Split ein String in C .

Martin Broadhurst
quelle
Das zweimalige Suchen nach Trennzeichen ist wahrscheinlich ratsamer als das Zuweisen eines potenziell großen Arrays von token.
Chqrlie
2

Versuchen Sie dies zu verwenden.

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;
}
David Jr.
quelle
2

Diese optimierte Methode erstellt (oder aktualisiert ein vorhandenes) Array von Zeigern in * result und gibt die Anzahl der Elemente in * count zurück.

Verwenden Sie "max", um die maximale Anzahl von Zeichenfolgen anzugeben, die Sie erwarten (wenn Sie ein vorhandenes Array oder ein anderes Reaseon angeben), andernfalls setzen Sie es auf 0

Um mit einer Liste von Trennzeichen zu vergleichen, definieren Sie delim als Zeichen * und ersetzen Sie die Zeile:

if (str[i]==delim) {

mit den beiden folgenden Zeilen:

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

Genießen

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

Anwendungsbeispiel:

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

}
Luxigo
quelle
2

Meine Version:

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;
}
Artem Samokhin
quelle
2

Dies ist eine Funktion zum Teilen von Zeichenfolgen, die Trennzeichen mit mehreren Zeichen verarbeiten kann. Beachten Sie, dass, wenn das Trennzeichen länger als die Zeichenfolge ist, die geteilt wird, bufferund stringLengthsauf (void *) 0und festgelegt numStringswird 0.

Dieser Algorithmus wurde getestet und funktioniert. (Haftungsausschluss: Es wurde nicht auf Nicht-ASCII-Zeichenfolgen getestet und es wird davon ausgegangen, dass der Aufrufer gültige Parameter angegeben hat.)

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

Beispielcode:

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

Bibliotheken:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
Élektra
quelle
Wie nenne ich das von main? Ich weiß nicht, was ich an den Puffer übergeben soll.
Aymon Fournier
Zuordnungslogik ist falsch. realloc () gibt einen neuen Zeiger zurück und Sie verwerfen den zurückgegebenen Wert. Es sollte kein geeigneter Weg gefunden werden, um einen neuen Speicherzeiger zurückzugeben. Der Funktionsprototyp sollte geändert werden, um die Größe der zugewiesenen bufferElemente zu akzeptieren und die Zuordnung dem Aufrufer zu überlassen und Elemente mit maximaler Größe zu verarbeiten.
Alex
@Alex Behoben, komplett neu geschrieben und getestet. Hinweis: Ich bin mir nicht sicher, ob dies für Nicht-ASCII funktioniert oder nicht.
Élektra
Für den Anfang ist dies kein C-Code. Und warum sollten Sie Zeiger in C ++ als Referenz übergeben?
Kamiccolo
@Kamiccolo Es tut mir leid, wie genau ist das nicht C-Code? Warum ist das Übergeben von Zeigern als Referenz hier ein Problem?
Élektra
1

Mein Ansatz ist es, die Zeichenfolge zu scannen und die Zeiger auf jedes Zeichen nach den Trennzeichen (und dem ersten Zeichen) zeigen zu lassen, während gleichzeitig das Erscheinungsbild des Trennzeichens in der Zeichenfolge '\ 0' zugewiesen wird.
Erstellen Sie zuerst eine Kopie der ursprünglichen Zeichenfolge (da diese konstant ist) und ermitteln Sie dann die Anzahl der Teilungen, indem Sie sie scannen und an den Zeigerparameter len übergeben . Zeigen Sie danach mit dem ersten Ergebniszeiger auf den Kopierzeichenfolgenzeiger und scannen Sie die Kopierzeichenfolge: Wenn Sie auf ein Trennzeichen stoßen, weisen Sie es '\ 0' zu, damit die vorherige Ergebniszeichenfolge beendet wird, und zeigen Sie mit dem nächsten Ergebniszeichenfolgenzeiger auf die nächste Zeichenzeiger.

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;
}
Metallabsturz
quelle
Diese Methode ist falsch. Ich habe diesen Beitrag gerade gelöscht, aber dann wurde mir klar, dass er für einige von Ihnen vielleicht interessant ist.
Metallcrash
1

Mein Code (getestet):

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

Ergebnis:

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
DaTaiMeo
quelle
1
Beachten Sie, dass die strtok-Funktion die Zeichenfolge ändert, auf die 'str' angewendet wurde!
SchLx
1

Explodieren & implodieren - die anfängliche Zeichenfolge bleibt erhalten, dynamische Speicherzuweisung

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

Verwendung:

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;
}
Dawid Szymański
quelle
0

Wenn Sie bereit sind, eine externe Bibliothek zu verwenden, kann ich dies nicht empfehlen bstrlib genug . Es erfordert ein wenig zusätzliches Setup, ist aber auf lange Sicht einfacher zu verwenden.

Teilen Sie zum Beispiel die Zeichenfolge unten, man erstellt zuerst eine bstringmit dem bfromcstr()Aufruf. (EINbstring ist ein Wrapper um einen Zeichenpuffer). Teilen Sie als Nächstes die Zeichenfolge in Kommas auf und speichern Sie das Ergebnis in a struct bstrListmit Feldern qtyund einem Array entrymit einem Array von bstrings.

bstrlibhat viele andere Funktionen, um an bstrings zu arbeiten

Einfach wie Torte ...

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

}
SAK
quelle
0

Noch eine Antwort (dies wurde von hier hierher verschoben ):

Versuchen Sie, die strtok-Funktion zu verwenden:

Details zu diesem Thema finden Sie hier oder hier

Das Problem hierbei ist, dass Sie das wordssofort verarbeiten müssen. Wenn Sie es in einem Array speichern möchten, müssen Sie das dafür zugewiesene correct sizeHex unbekannt zuweisen .

Also zum Beispiel:

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

}

Hinweis : Wir verwenden dieselbe Schleife und Funktion, um die Anzahl zu berechnen (Durchgang eins) und um die Kopien zu erstellen (Durchgang zwei), um Zuordnungsprobleme zu vermeiden.

Hinweis 2 : Sie können eine andere Implementierung des Strtok verwenden, die in separaten Beiträgen erwähnt wird.

Sie können dies wie folgt verwenden:

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

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

(Ich habe es nicht getestet, also lass es mich wissen, wenn es nicht funktioniert!)

SchLx
quelle
0

Zwei Probleme im Zusammenhang mit dieser Frage sind die Speicherverwaltung und die Thread-Sicherheit. Wie Sie den zahlreichen Beiträgen entnehmen können, ist dies keine einfache Aufgabe, die in C nahtlos erledigt werden kann. Ich wünschte mir eine Lösung, die:

  • Gewindesicher. (strtok ist nicht threadsicher)
  • Verwendet kein Malloc oder eines seiner Derivate (um Speicherverwaltungsprobleme zu vermeiden)
  • Überprüft die Arraygrenzen für die einzelnen Felder (um Segmentfehler bei unbekannten Daten zu vermeiden).
  • Funktioniert mit Multi-Byte-Feldtrennzeichen (utf-8)
  • ignoriert zusätzliche Felder in der Eingabe
  • Bietet eine weiche Fehlerroutine für ungültige Feldlängen

Die Lösung, die ich gefunden habe, erfüllt alle diese Kriterien. Das Einrichten ist wahrscheinlich etwas aufwändiger als bei einigen anderen hier veröffentlichten Lösungen, aber ich denke, dass sich die zusätzliche Arbeit in der Praxis lohnt, um die üblichen Fallstricke anderer Lösungen zu vermeiden.

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

Unten finden Sie ein Beispiel für das Kompilieren und Ausgeben. Beachten Sie, dass ich in meinem Beispiel absichtlich "APRIL" geschrieben habe, damit Sie sehen können, wie der weiche Fehler funktioniert.

$ 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

Genießen!

wryan
quelle
0

Hier ist eine weitere Implementierung, die sicher arbeitet, um ein Zeichenfolgenliteral zu token, das mit dem in der Frage angeforderten Prototyp übereinstimmt, der einen zugewiesenen Zeiger an Zeiger an char zurückgibt (z char **. B. ). Die Trennzeichenfolge kann mehrere Zeichen enthalten, und die Eingabezeichenfolge kann eine beliebige Anzahl von Token enthalten. Alle Zuweisungen und Neuzuweisungen werden von mallocoder reallocohne POSIX ausgeführt strdup.

Die anfängliche Anzahl der zugewiesenen Zeiger wird durch die NPTRSKonstante gesteuert und die einzige Einschränkung besteht darin, dass sie größer als Null ist. Die char **zurück enthält einen Sentinel NULL , nachdem die letzte Token ähnlich zu *argv[]und in der Form nutzbar execv, execvpundexecve .

Wie bei strtok()mehreren aufeinanderfolgenden Trennzeichen werden sie als ein einzelnes Trennzeichen behandelt, so "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"dass sie analysiert werden, als ob nur ein einzelnes Trennzeichen ','getrennt wird "MAY,JUN".

Die folgende Funktion wird inline kommentiert und ein Kurzfilm main()wurde hinzugefügt, der die Monate aufteilt. Die anfängliche Anzahl der zugewiesenen Zeiger wurde so eingestellt 2, dass beim Tokenisieren der Eingabezeichenfolge drei Neuzuweisungen erzwungen werden:

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

Beispiel Verwendung / Ausgabe

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

Lassen Sie mich wissen, wenn Sie weitere Fragen haben.

David C. Rankin
quelle