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,constchar 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);}return0;}
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:constchar*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:
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.
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();return0;}
int split (constchar*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]);return0;}
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 (constchar*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;}
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[]){constchar* 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 );return0;}
Es gibt wahrscheinlich einen besseren Weg, dies zu tun, aber Sie haben die Idee.
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,constchar 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;}elseif(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);constchar*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;elseif(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);}
#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 stringwhile((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;return0;}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 );return0;}
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,constchar*delim){staticchar*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))return0;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 ...
ExampleUsagechar 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,","));ExampleOutput1 A
2 B
3,4,5 C
6(null)
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:
Definieren Sie eine Struktur token zum Speichern der Adresse und Länge der Token
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
Scan str zeichnet die Adresse und Länge jedes Tokens auf
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
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
Geben Sie die Token-Adresse und das Längen-Array frei
Geben Sie das Array der Token zurück
typedefstruct{constchar*start;size_t len;} token;char**split(constchar*str,char sep){char**array;unsignedint 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);returnarray;}
Beachten Sie, dass diemalloc Ü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 .
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,constchar* 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;}
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,unsignedlong*count,unsignedlong 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 passif(_result){
_result[0]=str;}// scan the string for delimiter, up to specified lengthfor(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 passif(_result){
_result[*count]=str+i+1;}// increment count for each separator found++(*count);// if max is specified, dont go furtherif(max &&*count==max){break;}}}// when result array is specified, we are done hereif(_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 stringsfor(i=1; i<*count;++i){// find next stringwhile(*str)++str;++str;// add next string to result
_result[i]=str;}return _result;}
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.)
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.
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,constchar*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;return1;}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:
Found12 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
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
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 freevoidFree_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 memoryFree_Array(array);array= NULL;return0;}
(Ich habe es nicht getestet, also lass es mich wissen, wenn es nicht funktioniert!)
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.
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
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 SentinelNULL , 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 (constchar*src,constchar*delim){int i =0, in =0, nptrs = NPTRS;/* index, in/out flag, ptr count */char**dest = NULL;/* ptr-to-ptr to allocate/fill */constchar*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.
strtok
Funktion aus der Standardbibliothek verwenden, um dasselbe zu erreichen.strtok()
Familienfunktion ist das Verständnisstatic variables
in C. dh wie sie sich zwischen aufeinanderfolgenden Funktionsaufrufen verhalten, in denen sie verwendet werden. Siehe meinen Code untenAntworten:
Mit der
strtok()
Funktion können Sie eine Zeichenfolge teilen (und das zu verwendende Trennzeichen angeben). Beachten Sie, dass dadurchstrtok()
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 anstrtok()
.BEARBEITEN:
Beispiel (beachten Sie, dass keine aufeinanderfolgenden Trennzeichen behandelt werden, z. B. "JAN ,,, FEB, MAR"):
Ausgabe:
quelle
strtok
iststrsep(3)
in der Manpage als veraltet markiert .strsep
ist es ein Ersatz fürstrtok
, wird aber aus Gründen derstrtok
Portabilität bevorzugt. Esstrtok
ist also eine bessere Wahl , es sei denn, Sie benötigen Unterstützung für leere Felder oder das Aufteilen mehrerer Zeichenfolgen gleichzeitig .strtok_s()
(Microsoft, C11 Annex K, optional) oderstrtok_r()
(POSIX) als normal zu verwendenstrtok()
. Einfachstrtok()
ist böse in einer Bibliotheksfunktion. Derzeit darf keine Funktion verwendet werden, die die Bibliotheksfunktion aufruftstrtok()
, und es darf keine Funktion aufgerufen werden, die von der Bibliotheksfunktion aufgerufen wirdstrtok()
.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 Ersetzenstrtok()
fürstrtok_r()
behebt dieses Problem.Ich denke
strsep
ist immer noch das beste Werkzeug dafür: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
token
undstr
beide einen Typ habenchar *
. Wenn Sie mit einem Zeichenfolgenliteral begonnen haben, möchten Sie zuerst eine Kopie davon erstellen:Wenn zwei Trennzeichen zusammen in
str
angezeigt werden, erhalten Sie einentoken
Wert, der die leere Zeichenfolge ist. Der Wert vonstr
wird 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
strtok
besser ist alsstrsep
weilstrtok
es portabler ist. Ubuntu und Mac OS X habenstrsep
; Man kann davon ausgehen, dass dies auch bei anderen Unixy-Systemen der Fall ist. Windows fehltstrsep
, aber es hat,strbrk
was diesen kurzen und süßenstrsep
Ersatz ermöglicht:Hier ist eine gute Erklärung für
strsep
vsstrtok
. Die Vor- und Nachteile können subjektiv beurteilt werden; Ich denke jedoch, dass es ein aussagekräftiges Zeichen ist,strsep
das als Ersatz für konzipiert wurdestrtok
.quelle
tofree
ist der frei und nichtstr
?str
da der Wert durch Aufrufe von geändert werden kannstrsep()
. Der Wert von zeigttofree
konsistent auf den Anfang des Speichers, den Sie freigeben möchten.String Tokenizer Dieser Code sollte Sie in die richtige Richtung bringen.
quelle
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
Wie man es benutzt:
quelle
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.¢
Hier sind meine zwei Cent:
Verwendung:
quelle
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:
Es gibt wahrscheinlich einen besseren Weg, dies zu tun, aber Sie haben die Idee.
quelle
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:
Wie man es benutzt:
quelle
quelle
Unten ist meine
strtok()
Implementierung aus der zString-Bibliothek .zstring_strtok()
unterscheidet sich von Standardbibliothekenstrtok()
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).
Unten ist ein Beispiel für die Verwendung ...
Die Bibliothek kann von Github https://github.com/fnoyanisi/zString heruntergeladen werden
quelle
Ich halte folgende Lösung für ideal:
Erläuterung des Codes:
token
zum Speichern der Adresse und Länge der Tokenstr
vollständig aus Trennzeichen bestehtstrlen(str) + 1
Token , die alle leere Zeichenfolgen sindstr
zeichnet die Adresse und Länge jedes Tokens aufNULL
Sentinel-Wertmemcpy
sie hinzustrcpy
und wir die Längen kennenBeachten 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 .quelle
token
.Versuchen Sie dies zu verwenden.
quelle
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:
mit den beiden folgenden Zeilen:
Genießen
Anwendungsbeispiel:
quelle
Meine Version:
quelle
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,
buffer
undstringLengths
auf(void *) 0
und festgelegtnumStrings
wird0
.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.)
Beispielcode:
Bibliotheken:
quelle
buffer
Elemente zu akzeptieren und die Zuordnung dem Aufrufer zu überlassen und Elemente mit maximaler Größe zu verarbeiten.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.
quelle
Mein Code (getestet):
Ergebnis:
quelle
Explodieren & implodieren - die anfängliche Zeichenfolge bleibt erhalten, dynamische Speicherzuweisung
Verwendung:
quelle
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
bstring
mit dembfromcstr()
Aufruf. (EINbstring
ist ein Wrapper um einen Zeichenpuffer). Teilen Sie als Nächstes die Zeichenfolge in Kommas auf und speichern Sie das Ergebnis in astruct bstrList
mit Feldernqty
und einem Arrayentry
mit einem Array vonbstring
s.bstrlib
hat viele andere Funktionen, um anbstring
s zu arbeitenEinfach wie Torte ...
quelle
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
words
sofort verarbeiten müssen. Wenn Sie es in einem Array speichern möchten, müssen Sie das dafür zugewiesenecorrect size
Hex unbekannt zuweisen .Also zum Beispiel:
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:
(Ich habe es nicht getestet, also lass es mich wissen, wenn es nicht funktioniert!)
quelle
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:
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.
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.
Genießen!
quelle
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 vonmalloc
oderrealloc
ohne POSIX ausgeführtstrdup
.Die anfängliche Anzahl der zugewiesenen Zeiger wird durch die
NPTRS
Konstante gesteuert und die einzige Einschränkung besteht darin, dass sie größer als Null ist. Diechar **
zurück enthält einen SentinelNULL
, nachdem die letzte Token ähnlich zu*argv[]
und in der Form nutzbarexecv
,execvp
undexecve
.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 eingestellt2
, dass beim Tokenisieren der Eingabezeichenfolge drei Neuzuweisungen erzwungen werden:Beispiel Verwendung / Ausgabe
Lassen Sie mich wissen, wenn Sie weitere Fragen haben.
quelle