Komentarz ... kluczową kwestią dla strtok()funkcji rodziny jest zrozumienie static variablesw C. tj. Jak zachowują się pomiędzy kolejnymi wywołaniami funkcji, w których są używane. Zobacz mój kod poniżej
fnisi
Odpowiedzi:
165
Możesz użyć tej strtok()funkcji, aby podzielić ciąg (i określić separator). Zauważ, że strtok()zmodyfikuje przekazany do niego ciąg. Jeśli oryginalny ciąg jest wymagany w innym miejscu, wykonaj jego kopię i przekaż kopię do strtok().
EDYTOWAĆ:
Przykład (pamiętaj, że nie obsługuje kolejnych separatorów, na przykład „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;}
Cześć! strtokjest oznaczony jako przestarzały przez strsep(3)na stronie podręcznika .
osgx
4
Ponieważ może to być kanoniczne pytanie / odpowiedź na temat przepełnienia stosu, czy nie ma żadnych zastrzeżeń dotyczących wielowątkowości przy użyciu strtok?
Peter Mortensen
3
@osgx Według tej strony strsepjest zamiennikiem strtok, ale strtokjest preferowany ze względu na przenośność. Tak więc, jeśli nie potrzebujesz obsługi pustych pól lub dzielenia wielu ciągów jednocześnie, strtokjest lepszym wyborem.
4
@Dojo: Pamięta to; to jeden z powodów, dla których jest to problematyczne. Byłoby lepiej użyć strtok_s()(Microsoft, C11 Annex K, opcjonalne) lub strtok_r()(POSIX) niż zwykłego strtok(). Zwykłe strtok()jest złe w funkcji biblioteki. Żadna funkcja wywołująca funkcję biblioteczną nie może być używana strtok()w tym czasie i żadna funkcja wywoływana przez funkcję biblioteki nie może wywołać strtok().
Jonathan Leffler
3
Tylko uwaga, która strtok()nie jest bezpieczna wątkowo (z powodów, o których wspomniał @JonathanLeffler) i dlatego cała ta funkcja nie jest bezpieczna wątkowo. Jeśli spróbujesz użyć tego w wydeptanym środowisku, uzyskasz nieregularne i nieprzewidywalne wyniki. Wymiana w strtok()celu strtok_r()rozwiązania tego problemu.
Sean W.
70
Myślę, że strsepnadal jest to najlepsze narzędzie do tego:
while((token = strsep(&str,","))) my_fn(token);
To dosłownie jedna linia, która dzieli ciąg.
Dodatkowe nawiasy to element stylistyczny wskazujący, że celowo testujemy wynik przypisania, a nie operator równości ==.
Aby ten wzór zadziałał, tokeni stroba mają typ char *. Jeśli zacząłeś od literału ciągu, najpierw chciałbyś zrobić jego kopię:
// 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);
Jeśli dwa separatory pojawią się razem w str, otrzymasz tokenwartość, która jest pustym ciągiem. Wartość strjest modyfikowana w taki sposób, że każdy napotkany separator jest nadpisywany bajtem zerowym - kolejny dobry powód, aby skopiować najpierw analizowany ciąg.
W komentarzu ktoś zasugerował, że strtokjest to lepsze niż to, strsepże strtokjest bardziej przenośne. Ubuntu i Mac OS X mają strsep; można zgadnąć, że inne systemy unixy również. Windows brakuje strsep, ale ma to, strbrkco umożliwia tę krótką i słodką strsepwymianę:
Tutaj jest dobrym wyjaśnieniem strsepvs strtok. Za i przeciw można ocenić subiektywnie; jednak myślę, że jest to wymowny znak, który strsepzostał zaprojektowany jako zamiennik strtok.
Dokładniej mówiąc o przenośności: nie jest to POSIX 7 , ale wyprowadzone z BSD i zaimplementowane w glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Właśnie miałem zapytać ... C Pelle'a ma strdup (), ale nie ma strsep ().
rdtsc
1
dlaczego ten tofreejest wolny, a nie str?
Sdlion
1
Nie możesz zwolnić, strponieważ jego wartość można zmienić, wywołując numer strsep(). Wartość tofreekonsekwentnie wskazuje początek pamięci, którą chcesz zwolnić.
Tyler
26
String tokenizer ten kod powinien skierować Cię we właściwym kierunku.
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;}
Jak tego użyć:
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 Three star Programmer :)) To brzmi interesująco.
Michi
Kiedy to robię, albo dodaje zbyt dużo do ostatniego tokenu, albo przydziela mu za dużo pamięci. Oto wynik: 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
W tym przykładzie występują liczne wycieki pamięci. Każdy, kto to czyta, nie powinien stosować tego podejścia. Zamiast tego preferuj metody tokenizacji strtok lub strsep.
Jorma Rebane
7
Oto moje dwa centy:
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, trzy wskazówki! Już boję się go używać lol to tylko ja, nie jestem zbyt dobry ze wskazówkami w c.
Hafiz Temuri
Dzięki stary, wszystkie powyższe odpowiedzi strtok nie zadziałały w moim przypadku nawet po wielu wysiłkach, a twój kod działa jak urok!
hmmftg
4
W powyższym przykładzie byłby sposób na zwrócenie tablicy ciągów zakończonych znakiem null (tak jak chcesz) w miejscu w ciągu. Nie dałoby to jednak możliwości przekazania ciągu literału, ponieważ musiałby zostać zmodyfikowany przez funkcję:
#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;}
Prawdopodobnie jest na to lepszy sposób, ale masz pomysł.
Ta funkcja pobiera łańcuch char * i dzieli go przez separator. W rzędzie może znajdować się wiele separatorów. Zwróć uwagę, że funkcja modyfikuje oryginalny łańcuch. Musisz najpierw wykonać kopię oryginalnego ciągu, jeśli chcesz, aby oryginał pozostał niezmieniony. Ta funkcja nie używa żadnych wywołań funkcji cstring, więc może być trochę szybsza niż inne. Jeśli nie zależy Ci na alokacji pamięci, możesz przydzielić sub_strings na górze funkcji z rozmiarem strlen (src_str) / 2 i (tak jak wspomniana „wersja” c ++) pominąć dolną połowę funkcji. Jeśli to zrobisz, funkcja zostanie zredukowana do O (N), ale zoptymalizowany sposób pamięci pokazany poniżej to O (2N).
Funkcja:
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;}
Poniżej moja strtok()implementacja z biblioteki zString .
zstring_strtok()różni się od biblioteki standardowej strtok()sposobem, w jaki traktuje kolejne ograniczniki.
Wystarczy spojrzeć na poniższy kod, na pewno zorientujesz się, jak to działa (próbowałem użyć jak największej liczby komentarzy)
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;}
Poniżej znajduje się przykładowe użycie ...
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)
Re-entrant - tzn. Możesz bezpiecznie zadzwonić do niego z dowolnego miejsca w jednym lub kilku wątkach
Przenośny
Prawidłowo obsługuje wiele separatorów
Szybko i wydajnie
Wyjaśnienie kodu:
Zdefiniuj strukturę tokendo przechowywania adresu i długości tokenów
Przydziel wystarczającą ilość pamięci dla nich w najgorszym przypadku, kiedy
strskłada się w całości z separatorów, więc są strlen(str) + 1
tokeny, wszystkie puste ciągi
Skanuj strrejestrując adres i długość każdego tokena
Użyj tego, aby przydzielić tablicę wyjściową o odpowiednim rozmiarze, w tym dodatkową przestrzeń na NULLwartość wartownika
Przydzielaj, kopiuj i dodawaj tokeny, korzystając z informacji o początku i długości - używaj, memcpyponieważ jest szybszy niż strcpyi znamy długości
Zwolnij adres tokenu i tablicę długości
Zwróć tablicę tokenów
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;}
mallocSprawdzanie notatek zostało pominięte ze względu na zwięzłość.
Ogólnie rzecz biorąc, nie zwracałbym tablicy char *wskaźników z takiej funkcji podziału, jak ta, ponieważ nakłada ona na wywołującego dużą odpowiedzialność za ich prawidłowe zwolnienie. Interfejs Wolę to, aby rozmówca przekazać funkcję zwrotną i nazywają to dla każdego powodu, jak opisałem tutaj: podzielić ciąg w C .
Dwukrotne skanowanie w poszukiwaniu separatorów jest prawdopodobnie bardziej wskazane niż przydzielanie potencjalnie dużej tablicy plików token.
chqrlie
2
Spróbuj tego użyć.
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;}
Ta zoptymalizowana metoda tworzy (lub aktualizuje istniejącą) tablicę wskaźników w * wyniku i zwraca liczbę elementów w * count.
Użyj „max”, aby wskazać maksymalną liczbę oczekiwanych ciągów (gdy określasz istniejącą tablicę lub inny sposób), w przeciwnym razie ustaw ją na 0
Aby porównać z listą ograniczników, zdefiniuj separator jako znak * i zamień wiersz:
if(str[i]==delim){
z dwoma następującymi wierszami:
char*c=delim;while(*c &&*c!=str[i]) c++;if(*c){
Cieszyć się
#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;}
Jest to funkcja podziału ciągów, która obsługuje ograniczniki wieloznakowe. Zwróć uwagę, że jeśli separator jest dłuższy niż dzielony ciąg, to bufferi stringLengthszostanie ustawiony na (void *) 0i numStringszostanie ustawiony na 0.
Ten algorytm został przetestowany i działa. (Zastrzeżenie: nie zostało przetestowane pod kątem ciągów innych niż ASCII i zakłada, że wywołujący podał prawidłowe parametry)
Jak mam to nazwać z main? Nie wiem, co przekazać do bufora.
Aymon Fournier
Logika alokacji jest nieprawidłowa. realloc () zwraca nowy wskaźnik, a ty odrzucasz zwróconą wartość. Brak prawidłowego sposobu na zwrócenie nowego wskaźnika pamięci - prototyp funkcji powinien zostać zmieniony tak, aby akceptował rozmiar alokacji bufferi pozostawił alokację wywołującemu, procesowi maksymalnemu rozmiarowi elementów.
Alex
@Alex Naprawiono, całkowicie przepisano i przetestowano. Uwaga: nie jestem pewien, czy to zadziała dla innych niż ASCII, czy nie.
Élektra
Na początek to nie jest kod C. I dlaczego miałbyś przekazywać wskaźniki przez rzeczywiste referencje w C ++?
Kamiccolo
@Kamiccolo Przepraszam, dlaczego to nie jest kod w C? Ponadto, dlaczego przekazywanie wskaźników przez odniesienie jest tutaj problemem?
Élektra
1
Moje podejście polega na zeskanowaniu ciągu i pozwoleniu, aby wskaźniki wskazywały na każdy znak po separatorach (i pierwszym znaku), jednocześnie przypisując wygląd separatora w ciągu do '\ 0'.
Najpierw wykonaj kopię oryginalnego ciągu (ponieważ jest on stały), a następnie uzyskaj liczbę podziałów, skanując go i przekaż go do parametru wskaźnika len . Następnie skieruj pierwszy wskaźnik wyniku na wskaźnik kopiowania ciągu, a następnie zeskanuj ciąg kopiowania: po napotkaniu separatora przypisz go do `` \ 0 '', w ten sposób poprzedni ciąg wynikowy zostanie zakończony i wskaż następny wskaźnik ciągu wynikowego na następny wskaźnik znaku.
Ta metoda jest zła. Właśnie usunąłem ten post, ale potem zdałem sobie sprawę, że może być interesujący dla niektórych z was.
metalcrash
1
Mój kod (przetestowany):
#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);}
Wynik:
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
Jeśli chcesz skorzystać z zewnętrznej biblioteki, nie mogę bstrlibwystarczająco polecić . Wymaga trochę dodatkowej konfiguracji, ale na dłuższą metę jest łatwiejszy w użyciu.
Na przykład, podziel poniższy ciąg, najpierw tworzy się bstringz bfromcstr()wywołaniem. (A bstringto opakowanie wokół bufora znaków). Następnie podziel ciąg przecinkami, zapisując wynik w a struct bstrList, który zawiera pola qtyi tablicę entry, która jest tablicą bstrings.
bstrlibma wiele innych funkcji do działania na bstrings
Problem polega na tym, że musisz wordsnatychmiast przetworzyć plik. Jeśli chcesz przechowywać go w tablicy, musisz przeznaczyć correct sizena to nieznaną.
Na przykład:
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);}
Uwaga : Używamy tej samej pętli i funkcji do obliczania zliczeń (przebieg pierwszy) i do tworzenia kopii (przebieg drugi), aby uniknąć problemów z alokacją.
Uwaga 2 : Możesz użyć innej implementacji strtok z powodów wymienionych w oddzielnych postach.
Możesz użyć tego jak:
int main(void){char**array=Split("Hello World!"," ");// Now you have the array// ...// Then free the memoryFree_Array(array);array= NULL;return0;}
(Nie testowałem tego, więc daj mi znać, jeśli to nie działa!)
Dwie kwestie związane z tym pytaniem to zarządzanie pamięcią i bezpieczeństwo wątków. Jak widać z licznych postów, nie jest to łatwe zadanie do wykonania bezproblemowo w C. Zależało mi na rozwiązaniu, które jest:
Bezpieczny wątek. (strtok nie jest bezpieczny wątkowo)
Nie wykorzystuje malloc ani żadnej z jego pochodnych (aby uniknąć problemów z zarządzaniem pamięcią)
Sprawdza granice tablic w poszczególnych polach (aby uniknąć błędów segmentowych w przypadku nieznanych danych)
Działa z wielobajtowymi separatorami pól (utf-8)
ignoruje dodatkowe pola w danych wejściowych
udostępnia procedurę miękkiego błędu dla nieprawidłowych długości pól
Rozwiązanie, które wymyśliłem, spełnia wszystkie te kryteria. Prawdopodobnie jest to trochę więcej pracy niż konfiguracja niektórych innych zamieszczonych tutaj rozwiązań, ale myślę, że w praktyce dodatkowa praca jest tego warta, aby uniknąć typowych pułapek innych rozwiązań.
Poniżej znajduje się przykład kompilacji i wyników. Zauważ, że w moim przykładzie celowo przeliterowałem „KWIECIEŃ”, abyś mógł zobaczyć, jak działa błąd programowy.
$ 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
Oto kolejna implementacja, która będzie bezpiecznie działać, aby tokenizować literał ciągu pasujący do prototypu żądanego w pytaniu, zwracając przydzielony wskaźnik do wskaźnika do znaku (np char **.). Ciąg ogranicznika może zawierać wiele znaków, a ciąg wejściowy może zawierać dowolną liczbę tokenów. Wszystkie alokacje i realokacje są obsługiwane przez POSIX malloclub reallocbez niego strdup.
Początkowa liczba przydzielonych wskaźników jest kontrolowana przez NPTRSstałą, a jedynym ograniczeniem jest to, że jest większa od zera. char **Powrócił zawiera SentinelNULL po ostatnim podobnym do tokenu *argv[]oraz w formie użytkowej przezexecv , execvpi execve.
Podobnie jak w przypadku strtok()wielu separatorów sekwencyjnych jest traktowanych jako pojedynczy separator, "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"zostanie przeanalizowany tak, jakby ','oddzielał tylko jeden "MAY,JUN".
Poniższa funkcja jest komentowana w linii, a skrót main()został dodany, dzieląc miesiące. Początkowa liczba przydzielonych wskaźników została ustawiona na, 2aby wymusić trzy realokacje podczas tokenizacji ciągu wejściowego:
#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 */}}
Przykładowe użycie / wyjście
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
strtok
funkcji z biblioteki standardowej, aby osiągnąć to samo.strtok()
funkcji rodziny jest zrozumieniestatic variables
w C. tj. Jak zachowują się pomiędzy kolejnymi wywołaniami funkcji, w których są używane. Zobacz mój kod poniżejOdpowiedzi:
Możesz użyć tej
strtok()
funkcji, aby podzielić ciąg (i określić separator). Zauważ, żestrtok()
zmodyfikuje przekazany do niego ciąg. Jeśli oryginalny ciąg jest wymagany w innym miejscu, wykonaj jego kopię i przekaż kopię dostrtok()
.EDYTOWAĆ:
Przykład (pamiętaj, że nie obsługuje kolejnych separatorów, na przykład „JAN ,,, FEB, MAR”):
Wynik:
źródło
strtok
jest oznaczony jako przestarzały przezstrsep(3)
na stronie podręcznika .strsep
jest zamiennikiemstrtok
, alestrtok
jest preferowany ze względu na przenośność. Tak więc, jeśli nie potrzebujesz obsługi pustych pól lub dzielenia wielu ciągów jednocześnie,strtok
jest lepszym wyborem.strtok_s()
(Microsoft, C11 Annex K, opcjonalne) lubstrtok_r()
(POSIX) niż zwykłegostrtok()
. Zwykłestrtok()
jest złe w funkcji biblioteki. Żadna funkcja wywołująca funkcję biblioteczną nie może być używanastrtok()
w tym czasie i żadna funkcja wywoływana przez funkcję biblioteki nie może wywołaćstrtok()
.strtok()
nie jest bezpieczna wątkowo (z powodów, o których wspomniał @JonathanLeffler) i dlatego cała ta funkcja nie jest bezpieczna wątkowo. Jeśli spróbujesz użyć tego w wydeptanym środowisku, uzyskasz nieregularne i nieprzewidywalne wyniki. Wymiana wstrtok()
celustrtok_r()
rozwiązania tego problemu.Myślę, że
strsep
nadal jest to najlepsze narzędzie do tego:To dosłownie jedna linia, która dzieli ciąg.
Dodatkowe nawiasy to element stylistyczny wskazujący, że celowo testujemy wynik przypisania, a nie operator równości
==
.Aby ten wzór zadziałał,
token
istr
oba mają typchar *
. Jeśli zacząłeś od literału ciągu, najpierw chciałbyś zrobić jego kopię:Jeśli dwa separatory pojawią się razem w
str
, otrzymasztoken
wartość, która jest pustym ciągiem. Wartośćstr
jest modyfikowana w taki sposób, że każdy napotkany separator jest nadpisywany bajtem zerowym - kolejny dobry powód, aby skopiować najpierw analizowany ciąg.W komentarzu ktoś zasugerował, że
strtok
jest to lepsze niż to,strsep
żestrtok
jest bardziej przenośne. Ubuntu i Mac OS X mająstrsep
; można zgadnąć, że inne systemy unixy również. Windows brakujestrsep
, ale ma to,strbrk
co umożliwia tę krótką i słodkąstrsep
wymianę:Tutaj jest dobrym wyjaśnieniem
strsep
vsstrtok
. Za i przeciw można ocenić subiektywnie; jednak myślę, że jest to wymowny znak, którystrsep
został zaprojektowany jako zamiennikstrtok
.źródło
tofree
jest wolny, a niestr
?str
ponieważ jego wartość można zmienić, wywołując numerstrsep()
. Wartośćtofree
konsekwentnie wskazuje początek pamięci, którą chcesz zwolnić.String tokenizer ten kod powinien skierować Cię we właściwym kierunku.
źródło
Poniższa metoda wykona całą pracę (alokację pamięci, liczenie długości) za Ciebie. Więcej informacji i opis można znaleźć tutaj - Implementacja metody Java String.split () w celu podzielenia łańcucha C.
Jak tego użyć:
źródło
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.¢
Oto moje dwa centy:
Stosowanie:
źródło
W powyższym przykładzie byłby sposób na zwrócenie tablicy ciągów zakończonych znakiem null (tak jak chcesz) w miejscu w ciągu. Nie dałoby to jednak możliwości przekazania ciągu literału, ponieważ musiałby zostać zmodyfikowany przez funkcję:
Prawdopodobnie jest na to lepszy sposób, ale masz pomysł.
źródło
Ta funkcja pobiera łańcuch char * i dzieli go przez separator. W rzędzie może znajdować się wiele separatorów. Zwróć uwagę, że funkcja modyfikuje oryginalny łańcuch. Musisz najpierw wykonać kopię oryginalnego ciągu, jeśli chcesz, aby oryginał pozostał niezmieniony. Ta funkcja nie używa żadnych wywołań funkcji cstring, więc może być trochę szybsza niż inne. Jeśli nie zależy Ci na alokacji pamięci, możesz przydzielić sub_strings na górze funkcji z rozmiarem strlen (src_str) / 2 i (tak jak wspomniana „wersja” c ++) pominąć dolną połowę funkcji. Jeśli to zrobisz, funkcja zostanie zredukowana do O (N), ale zoptymalizowany sposób pamięci pokazany poniżej to O (2N).
Funkcja:
Jak tego użyć:
źródło
źródło
Poniżej moja
strtok()
implementacja z biblioteki zString .zstring_strtok()
różni się od biblioteki standardowejstrtok()
sposobem, w jaki traktuje kolejne ograniczniki.Wystarczy spojrzeć na poniższy kod, na pewno zorientujesz się, jak to działa (próbowałem użyć jak największej liczby komentarzy)
Poniżej znajduje się przykładowe użycie ...
Bibliotekę można pobrać z Github https://github.com/fnoyanisi/zString
źródło
Myślę, że następujące rozwiązanie jest idealne:
Wyjaśnienie kodu:
token
do przechowywania adresu i długości tokenówstr
składa się w całości z separatorów, więc sąstrlen(str) + 1
tokeny, wszystkie puste ciągistr
rejestrując adres i długość każdego tokenaNULL
wartość wartownikamemcpy
ponieważ jest szybszy niżstrcpy
i znamy długościmalloc
Sprawdzanie notatek zostało pominięte ze względu na zwięzłość.Ogólnie rzecz biorąc, nie zwracałbym tablicy
char *
wskaźników z takiej funkcji podziału, jak ta, ponieważ nakłada ona na wywołującego dużą odpowiedzialność za ich prawidłowe zwolnienie. Interfejs Wolę to, aby rozmówca przekazać funkcję zwrotną i nazywają to dla każdego powodu, jak opisałem tutaj: podzielić ciąg w C .źródło
token
.Spróbuj tego użyć.
źródło
Ta zoptymalizowana metoda tworzy (lub aktualizuje istniejącą) tablicę wskaźników w * wyniku i zwraca liczbę elementów w * count.
Użyj „max”, aby wskazać maksymalną liczbę oczekiwanych ciągów (gdy określasz istniejącą tablicę lub inny sposób), w przeciwnym razie ustaw ją na 0
Aby porównać z listą ograniczników, zdefiniuj separator jako znak * i zamień wiersz:
z dwoma następującymi wierszami:
Cieszyć się
Przykład użycia:
źródło
Moja wersja:
źródło
Jest to funkcja podziału ciągów, która obsługuje ograniczniki wieloznakowe. Zwróć uwagę, że jeśli separator jest dłuższy niż dzielony ciąg, to
buffer
istringLengths
zostanie ustawiony na(void *) 0
inumStrings
zostanie ustawiony na0
.Ten algorytm został przetestowany i działa. (Zastrzeżenie: nie zostało przetestowane pod kątem ciągów innych niż ASCII i zakłada, że wywołujący podał prawidłowe parametry)
Przykładowy kod:
Biblioteki:
źródło
buffer
i pozostawił alokację wywołującemu, procesowi maksymalnemu rozmiarowi elementów.Moje podejście polega na zeskanowaniu ciągu i pozwoleniu, aby wskaźniki wskazywały na każdy znak po separatorach (i pierwszym znaku), jednocześnie przypisując wygląd separatora w ciągu do '\ 0'.
Najpierw wykonaj kopię oryginalnego ciągu (ponieważ jest on stały), a następnie uzyskaj liczbę podziałów, skanując go i przekaż go do parametru wskaźnika len . Następnie skieruj pierwszy wskaźnik wyniku na wskaźnik kopiowania ciągu, a następnie zeskanuj ciąg kopiowania: po napotkaniu separatora przypisz go do `` \ 0 '', w ten sposób poprzedni ciąg wynikowy zostanie zakończony i wskaż następny wskaźnik ciągu wynikowego na następny wskaźnik znaku.
źródło
Mój kod (przetestowany):
Wynik:
źródło
Explode & implode - początkowy ciąg pozostaje nienaruszony, dynamiczna alokacja pamięci
Stosowanie:
źródło
Jeśli chcesz skorzystać z zewnętrznej biblioteki, nie mogę
bstrlib
wystarczająco polecić . Wymaga trochę dodatkowej konfiguracji, ale na dłuższą metę jest łatwiejszy w użyciu.Na przykład, podziel poniższy ciąg, najpierw tworzy się
bstring
zbfromcstr()
wywołaniem. (Abstring
to opakowanie wokół bufora znaków). Następnie podziel ciąg przecinkami, zapisując wynik w astruct bstrList
, który zawiera polaqty
i tablicęentry
, która jest tablicąbstring
s.bstrlib
ma wiele innych funkcji do działania nabstring
sŁatwe jak ciasto ...
źródło
Jeszcze inna odpowiedź (została przeniesiona stąd ):
Spróbuj użyć funkcji strtok:
zobacz szczegóły na ten temat tutaj lub tutaj
Problem polega na tym, że musisz
words
natychmiast przetworzyć plik. Jeśli chcesz przechowywać go w tablicy, musisz przeznaczyćcorrect size
na to nieznaną.Na przykład:
Uwaga : Używamy tej samej pętli i funkcji do obliczania zliczeń (przebieg pierwszy) i do tworzenia kopii (przebieg drugi), aby uniknąć problemów z alokacją.
Uwaga 2 : Możesz użyć innej implementacji strtok z powodów wymienionych w oddzielnych postach.
Możesz użyć tego jak:
(Nie testowałem tego, więc daj mi znać, jeśli to nie działa!)
źródło
Dwie kwestie związane z tym pytaniem to zarządzanie pamięcią i bezpieczeństwo wątków. Jak widać z licznych postów, nie jest to łatwe zadanie do wykonania bezproblemowo w C. Zależało mi na rozwiązaniu, które jest:
Rozwiązanie, które wymyśliłem, spełnia wszystkie te kryteria. Prawdopodobnie jest to trochę więcej pracy niż konfiguracja niektórych innych zamieszczonych tutaj rozwiązań, ale myślę, że w praktyce dodatkowa praca jest tego warta, aby uniknąć typowych pułapek innych rozwiązań.
Poniżej znajduje się przykład kompilacji i wyników. Zauważ, że w moim przykładzie celowo przeliterowałem „KWIECIEŃ”, abyś mógł zobaczyć, jak działa błąd programowy.
Cieszyć się!
źródło
Oto kolejna implementacja, która będzie bezpiecznie działać, aby tokenizować literał ciągu pasujący do prototypu żądanego w pytaniu, zwracając przydzielony wskaźnik do wskaźnika do znaku (np
char **
.). Ciąg ogranicznika może zawierać wiele znaków, a ciąg wejściowy może zawierać dowolną liczbę tokenów. Wszystkie alokacje i realokacje są obsługiwane przez POSIXmalloc
lubrealloc
bez niegostrdup
.Początkowa liczba przydzielonych wskaźników jest kontrolowana przez
NPTRS
stałą, a jedynym ograniczeniem jest to, że jest większa od zera.char **
Powrócił zawiera SentinelNULL
po ostatnim podobnym do tokenu*argv[]
oraz w formie użytkowej przezexecv
,execvp
iexecve
.Podobnie jak w przypadku
strtok()
wielu separatorów sekwencyjnych jest traktowanych jako pojedynczy separator,"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
zostanie przeanalizowany tak, jakby','
oddzielał tylko jeden"MAY,JUN"
.Poniższa funkcja jest komentowana w linii, a skrót
main()
został dodany, dzieląc miesiące. Początkowa liczba przydzielonych wskaźników została ustawiona na,2
aby wymusić trzy realokacje podczas tokenizacji ciągu wejściowego:Przykładowe użycie / wyjście
Daj mi znać, jeśli masz dodatkowe pytania.
źródło