Un commentaire ... le point clé pour une strtok()fonction familiale est la compréhension static variablesen C. c'est-à-dire comment ils se comportent entre les appels de fonction successifs dans lesquels ils sont utilisés. Voir mon code ci
fnisi
Réponses:
165
Vous pouvez utiliser la strtok()fonction pour fractionner une chaîne (et spécifier le délimiteur à utiliser). Notez que strtok()cela modifiera la chaîne qui y est passée. Si la chaîne d'origine est requise ailleurs, faites-en une copie et transmettez-la à strtok().
ÉDITER:
Exemple (notez qu'il ne gère pas les délimiteurs consécutifs, "JAN ,,, FEB, MAR" par exemple):
#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;}
Salut! le strtokest marqué comme obsolète par strsep(3)dans la page de manuel.
osgx
4
Comme cela peut être la question / réponse canonique sur Stack Overflow pour cela, n'y a-t-il pas quelques mises en garde concernant le multi-threading utilisant strtok?
Peter Mortensen
3
@osgx Selon cette page, strsepest un remplacement pour strtok, mais strtokest préféré pour la portabilité. Donc, à moins que vous n'ayez besoin de support pour les champs vides ou de fractionner plusieurs chaînes à la fois, strtokc'est un meilleur choix.
4
@Dojo: Il s'en souvient; c'est l'une des raisons pour lesquelles c'est problématique. Il serait préférable d'utiliser strtok_s()(Microsoft, C11 Annexe K, facultatif) ou strtok_r()(POSIX) que plain strtok(). Plain strtok()est mal dans une fonction de bibliothèque. Aucune fonction appelant la fonction de bibliothèque ne peut être utilisée strtok()à ce moment, et aucune fonction appelée par la fonction de bibliothèque ne peut être appelée strtok().
Jonathan Leffler
3
Juste une note qui strtok()n'est pas thread-safe (pour les raisons mentionnées par @JonathanLeffler) et donc toute cette fonction n'est pas thread-safe. Si vous essayez de l'utiliser dans un environnement difficile, vous obtiendrez des résultats erratiques et imprévisibles. Le remplacement strtok()pour strtok_r()corrige ce problème.
Sean W
70
Je pense que strsepc'est toujours le meilleur outil pour cela:
while((token = strsep(&str,","))) my_fn(token);
C'est littéralement une ligne qui divise une chaîne.
Les parenthèses supplémentaires sont un élément stylistique pour indiquer que nous testons intentionnellement le résultat d'une affectation, pas un opérateur d'égalité ==.
Pour que ce modèle fonctionne, tokenet les strdeux ont du type char *. Si vous avez commencé avec une chaîne littérale, vous voudrez d'abord en faire une copie:
// 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);
Si deux délimiteurs apparaissent ensemble dans str, vous obtiendrez une tokenvaleur correspondant à la chaîne vide. La valeur de strest modifiée en ce que chaque délimiteur rencontré est écrasé par un octet zéro - une autre bonne raison de copier la chaîne analysée en premier.
Dans un commentaire, quelqu'un a suggéré que strtokc'est mieux que strsepparce que strtokc'est plus portable. Ubuntu et Mac OS X ont strsep; il est sûr de deviner que d'autres systèmes unixy le font aussi. Windows manque strsep, mais il a strbrkce qui permet ce strsepremplacement court et doux :
Voici une bonne explication de strsepvs strtok. Les avantages et les inconvénients peuvent être jugés subjectivement; cependant, je pense que c'est un signe révélateur qui a strsepété conçu pour remplacer strtok.
Plus précisément sur la portabilité: il ne s'agit pas de POSIX 7 , mais de BSD, et implémenté sur la glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
J'étais sur le point de demander ... Le C de Pelle a strdup (), mais pas strsep ().
rdtsc
1
pourquoi celui-là tofreeest-il libre et non str?
Sdlion
1
Vous ne pouvez pas libérer strcar sa valeur peut être modifiée par des appels à strsep(). La valeur de tofreesystématiquement pointe vers le début de la mémoire que vous souhaitez libérer.
Tyler
26
Tokenizer de chaîne ce code devrait vous mettre dans la bonne direction.
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;}
Comment l'utiliser:
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 Programmeur trois étoiles :)) Cela semble intéressant.
Michi
Lorsque je fais cela, cela ajoute trop au dernier jeton ou lui alloue trop de mémoire. Voici la sortie: 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
Cet exemple présente plusieurs fuites de mémoire. Pour toute personne lisant ceci, n'utilisez pas cette approche. Préférez plutôt les approches de tokenisation strtok ou strsep.
Jorma Rebane
7
Voici mes deux cents:
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, trois pointeurs! J'ai déjà peur de l'utiliser lol c'est juste moi, je ne suis pas très bon avec les pointeurs en c.
Hafiz Temuri
Merci mec, toutes les réponses strtok ci-dessus n'ont pas fonctionné dans mon cas, même après beaucoup d'efforts, et votre code fonctionne comme un charme!
hmmftg
4
Dans l'exemple ci-dessus, il y aurait un moyen de retourner un tableau de chaînes terminées par null (comme vous le souhaitez) en place dans la chaîne. Cela ne permettrait cependant pas de passer une chaîne littérale, car elle devrait être modifiée par la fonction:
#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;}
Il y a probablement une façon plus soignée de le faire, mais vous voyez l'idée.
Cette fonction prend une chaîne char * et la divise par le déliminateur. Il peut y avoir plusieurs déliminateurs d'affilée. Notez que la fonction modifie la chaîne orignal. Vous devez d'abord faire une copie de la chaîne d'origine si vous souhaitez que l'original reste inchangé. Cette fonction n'utilise aucun appel de fonction cstring, elle peut donc être un peu plus rapide que d'autres. Si vous ne vous souciez pas de l'allocation de mémoire, vous pouvez allouer des sub_strings en haut de la fonction avec la taille strlen (src_str) / 2 et (comme la "version" c ++ mentionnée) sauter la moitié inférieure de la fonction. Si vous faites cela, la fonction est réduite à O (N), mais la méthode d'optimisation de la mémoire illustrée ci-dessous est O (2N).
La fonction:
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;}
Voici mon strtok()implémentation de la bibliothèque zString .
zstring_strtok()diffère des bibliothèques standardstrtok() par la manière dont elle traite les délimiteurs consécutifs.
Jetez un œil au code ci-dessous, assurez-vous que vous aurez une idée de son fonctionnement (j'ai essayé d'utiliser autant de commentaires que possible)
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;}
Voici un exemple d'utilisation ...
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 - c'est-à-dire que vous pouvez l'appeler en toute sécurité de n'importe où dans un ou plusieurs threads
Portable
Gère correctement plusieurs séparateurs
Rapide et efficace
Explication du code:
Définir une structure tokenpour stocker l'adresse et la longueur des jetons
Allouez suffisamment de mémoire pour ceux-ci dans le pire des cas, c'est-à-dire lorsque
strest entièrement composé de séparateurs, donc il y a des strlen(str) + 1
jetons, tous des chaînes vides
Balayage strenregistrant l'adresse et la longueur de chaque jeton
Utilisez ceci pour allouer le tableau de sortie de la taille correcte, y compris un espace supplémentaire pour une NULLvaleur sentinelle
Allouez, copiez et ajoutez les jetons en utilisant les informations de début et de longueur - utilisez memcpycar c'est plus rapide questrcpy et nous connaissons les longueurs
Libérez l'adresse du jeton et le tableau de longueur
Renvoyer le tableau de jetons
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;}
Remarque lamalloc vérification omise par souci de concision.
En général, je ne retournerais pas un tableau de char *pointeurs à partir d'une fonction fractionnée comme celle-ci car cela place beaucoup de responsabilité sur l'appelant pour les libérer correctement. Une interface que je préfère est de permettre à l'appelant de passer une fonction de rappel et appeler cela pour chaque jeton, comme je l' ai décrit ici: découper une chaîne en C .
Il est probablement préférable de rechercher deux fois les séparateurs plutôt que d'allouer un large éventail de fichiers token.
chqrlie le
2
Essayez d'utiliser ceci.
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;}
Cette méthode optimisée crée (ou met à jour un tableau existant) de pointeurs dans * result et renvoie le nombre d'éléments dans * count.
Utilisez "max" pour indiquer le nombre maximum de chaînes que vous attendez (lorsque vous spécifiez un tableau existant ou tout autre reaseon), sinon définissez-le sur 0
Pour comparer avec une liste de délimiteurs, définissez delim comme un caractère * et remplacez la ligne:
if(str[i]==delim){
avec les deux lignes suivantes:
char*c=delim;while(*c &&*c!=str[i]) c++;if(*c){
Prendre plaisir
#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;}
Il s'agit d'une fonction de fractionnement de chaîne qui peut gérer des délimiteurs à plusieurs caractères. Notez que si le délimiteur est plus long que la chaîne en cours de fractionnement, alors bufferet stringLengthssera défini sur (void *) 0, et numStringssera défini sur 0.
Cet algorithme a été testé et fonctionne. (Clause de non-responsabilité: il n'a pas été testé pour les chaînes non-ASCII, et il suppose que l'appelant a donné des paramètres valides)
Comment appeler cela depuis le main? Je ne sais pas quoi passer au tampon.
Aymon Fournier
La logique d'attribution est fausse. realloc () renvoie un nouveau pointeur et vous ignorez la valeur renvoyée. Aucune manière appropriée de renvoyer un nouveau pointeur de mémoire - le prototype de fonction doit être modifié pour accepter la taille de l'allocation bufferet laisser l'allocation à l'appelant, traiter les éléments de taille maximale.
Alex
@Alex Fixe, entièrement réécrit et testé. Remarque: je ne sais pas si cela fonctionnera pour les non-ASCII ou non.
Élektra
Pour commencer, ce n'est pas du code C. Et pourquoi passeriez-vous des pointeurs par référence réelle en C ++?
Kamiccolo
@Kamiccolo Je suis désolé, comment ce code n'est-il pas exactement C? Aussi, pourquoi le passage de pointeurs par référence pose-t-il un problème ici?
Élektra
1
Mon approche est de scanner la chaîne et de laisser les pointeurs pointer vers chaque caractère après les déliminateurs (et le premier caractère), en même temps attribuer les apparences du déliminateur dans la chaîne à '\ 0'.
Faites d'abord une copie de la chaîne d'origine (car elle est constante), puis obtenez le nombre de divisions en la parcourant, passez-la au paramètre de pointeur len . Après cela, pointez le premier pointeur de résultat vers le pointeur de chaîne de copie, puis scannez la chaîne de copie: une fois que vous rencontrez un déliminateur, affectez-le à '\ 0' ainsi la chaîne de résultat précédente est terminée, et pointez le pointeur de chaîne de résultat suivant vers le suivant pointeur de caractère.
Cette méthode est fausse. Je viens juste de supprimer ce post, mais j'ai réalisé que c'était peut-être intéressant pour certains d'entre vous.
metalcrash
1
Mon code (testé):
#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);}
Résultat:
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
Si vous êtes prêt à utiliser une bibliothèque externe, je ne peux pas vous recommander bstrlib trop vous . Cela prend un peu de configuration supplémentaire, mais est plus facile à utiliser à long terme.
Par exemple, divisez la chaîne ci-dessous, on crée d'abord un bstringavec l' bfromcstr()appel. (A bstringest un wrapper autour d'un tampon de caractères). Ensuite, divisez la chaîne par des virgules, en enregistrant le résultat dans a struct bstrList, qui contient des champs qtyet un tableau entry, qui est un tableau de bstrings.
bstrliba de nombreuses autres fonctions pour opérer sur bstrings
Le problème ici est que vous devez traiter wordsimmédiatement. Si vous voulez le stocker dans un tableau, vous devez allouer le correct sizecar il est inconnu.
Donc par exemple:
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);}
Remarque : Nous utilisons la même boucle et la même fonction pour calculer les comptages (passer un) et pour faire les copies (passer deux), afin d'éviter les problèmes d'allocation.
Remarque 2 : Vous pouvez utiliser une autre implémentation du strtok dont les raisons sont mentionnées dans des articles séparés.
Vous pouvez utiliser ceci comme:
int main(void){char**array=Split("Hello World!"," ");// Now you have the array// ...// Then free the memoryFree_Array(array);array= NULL;return0;}
(Je ne l'ai pas testé, alors faites-moi savoir si cela ne fonctionne pas!)
Deux problèmes entourant cette question sont la gestion de la mémoire et la sécurité des threads. Comme vous pouvez le voir dans les nombreux articles, ce n'est pas une tâche facile à accomplir de manière transparente en C.Je souhaitais une solution qui soit:
Fil sûr. (strtok n'est pas thread-safe)
N'emploie pas malloc ou l'un de ses dérivés (pour éviter les problèmes de gestion de la mémoire)
Vérifie les limites du tableau sur les champs individuels (pour éviter les erreurs de segment sur des données inconnues)
Fonctionne avec des séparateurs de champ multi-octets (utf-8)
ignore les champs supplémentaires dans l'entrée
fournit une routine d'erreur douce pour les longueurs de champ non valides
La solution que j'ai trouvée répond à tous ces critères. C'est probablement un peu plus de travail à mettre en place que certaines autres solutions affichées ici, mais je pense qu'en pratique, le travail supplémentaire en vaut la peine afin d'éviter les écueils courants des autres solutions.
Voici un exemple de compilation et de sortie. Notez que dans mon exemple, j'ai volontairement épelé «AVRIL» afin que vous puissiez voir comment fonctionne l'erreur logicielle.
$ 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
Voici une autre implémentation qui fonctionnera en toute sécurité pour tokeniser un littéral de chaîne correspondant au prototype demandé dans la question renvoyant un pointeur à pointeur alloué à char (par exemple char **). La chaîne de délimitation peut contenir plusieurs caractères et la chaîne d'entrée peut contenir n'importe quel nombre de jetons. Toutes les allocations et réallocations sont gérées par mallocou reallocsans POSIXstrdup .
Le nombre initial de pointeurs alloués est contrôlé par la NPTRSconstante et la seule limitation est qu'il soit supérieur à zéro. Le char **retourné contient une sentinelleNULL après le dernier jeton similaire à *argv[]et sous la forme utilisable par execv, execvpetexecve .
Comme avec strtok()plusieurs délimiteurs séquentiels sont traités comme un seul délimiteur, ils "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"seront analysés comme si un seul ','séparait "MAY,JUN".
La fonction ci-dessous est commentée en ligne et un court a main()été ajouté pour répartir les mois. Le nombre initial de pointeurs alloués a été défini sur 2pour forcer trois réallocations lors de la création de jetons de la chaîne d'entrée:
#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 */}}
Exemple d'utilisation / sortie
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
strtok
fonction de la bibliothèque standard pour réaliser la même chose.strtok()
fonction familiale est la compréhensionstatic variables
en C. c'est-à-dire comment ils se comportent entre les appels de fonction successifs dans lesquels ils sont utilisés. Voir mon code ciRéponses:
Vous pouvez utiliser la
strtok()
fonction pour fractionner une chaîne (et spécifier le délimiteur à utiliser). Notez questrtok()
cela modifiera la chaîne qui y est passée. Si la chaîne d'origine est requise ailleurs, faites-en une copie et transmettez-la àstrtok()
.ÉDITER:
Exemple (notez qu'il ne gère pas les délimiteurs consécutifs, "JAN ,,, FEB, MAR" par exemple):
Production:
la source
strtok
est marqué comme obsolète parstrsep(3)
dans la page de manuel.strsep
est un remplacement pourstrtok
, maisstrtok
est préféré pour la portabilité. Donc, à moins que vous n'ayez besoin de support pour les champs vides ou de fractionner plusieurs chaînes à la fois,strtok
c'est un meilleur choix.strtok_s()
(Microsoft, C11 Annexe K, facultatif) oustrtok_r()
(POSIX) que plainstrtok()
. Plainstrtok()
est mal dans une fonction de bibliothèque. Aucune fonction appelant la fonction de bibliothèque ne peut être utiliséestrtok()
à ce moment, et aucune fonction appelée par la fonction de bibliothèque ne peut être appeléestrtok()
.strtok()
n'est pas thread-safe (pour les raisons mentionnées par @JonathanLeffler) et donc toute cette fonction n'est pas thread-safe. Si vous essayez de l'utiliser dans un environnement difficile, vous obtiendrez des résultats erratiques et imprévisibles. Le remplacementstrtok()
pourstrtok_r()
corrige ce problème.Je pense que
strsep
c'est toujours le meilleur outil pour cela:C'est littéralement une ligne qui divise une chaîne.
Les parenthèses supplémentaires sont un élément stylistique pour indiquer que nous testons intentionnellement le résultat d'une affectation, pas un opérateur d'égalité
==
.Pour que ce modèle fonctionne,
token
et lesstr
deux ont du typechar *
. Si vous avez commencé avec une chaîne littérale, vous voudrez d'abord en faire une copie:Si deux délimiteurs apparaissent ensemble dans
str
, vous obtiendrez unetoken
valeur correspondant à la chaîne vide. La valeur destr
est modifiée en ce que chaque délimiteur rencontré est écrasé par un octet zéro - une autre bonne raison de copier la chaîne analysée en premier.Dans un commentaire, quelqu'un a suggéré que
strtok
c'est mieux questrsep
parce questrtok
c'est plus portable. Ubuntu et Mac OS X ontstrsep
; il est sûr de deviner que d'autres systèmes unixy le font aussi. Windows manquestrsep
, mais il astrbrk
ce qui permet cestrsep
remplacement court et doux :Voici une bonne explication de
strsep
vsstrtok
. Les avantages et les inconvénients peuvent être jugés subjectivement; cependant, je pense que c'est un signe révélateur qui astrsep
été conçu pour remplacerstrtok
.la source
tofree
est-il libre et nonstr
?str
car sa valeur peut être modifiée par des appels àstrsep()
. La valeur detofree
systématiquement pointe vers le début de la mémoire que vous souhaitez libérer.Tokenizer de chaîne ce code devrait vous mettre dans la bonne direction.
la source
La méthode ci-dessous fera tout le travail (allocation de mémoire, comptage de la longueur) pour vous. Plus d'informations et une description peuvent être trouvées ici - Implémentation de la méthode Java String.split () pour diviser la chaîne C
Comment l'utiliser:
la source
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.¢
Voici mes deux cents:
Usage:
la source
Dans l'exemple ci-dessus, il y aurait un moyen de retourner un tableau de chaînes terminées par null (comme vous le souhaitez) en place dans la chaîne. Cela ne permettrait cependant pas de passer une chaîne littérale, car elle devrait être modifiée par la fonction:
Il y a probablement une façon plus soignée de le faire, mais vous voyez l'idée.
la source
Cette fonction prend une chaîne char * et la divise par le déliminateur. Il peut y avoir plusieurs déliminateurs d'affilée. Notez que la fonction modifie la chaîne orignal. Vous devez d'abord faire une copie de la chaîne d'origine si vous souhaitez que l'original reste inchangé. Cette fonction n'utilise aucun appel de fonction cstring, elle peut donc être un peu plus rapide que d'autres. Si vous ne vous souciez pas de l'allocation de mémoire, vous pouvez allouer des sub_strings en haut de la fonction avec la taille strlen (src_str) / 2 et (comme la "version" c ++ mentionnée) sauter la moitié inférieure de la fonction. Si vous faites cela, la fonction est réduite à O (N), mais la méthode d'optimisation de la mémoire illustrée ci-dessous est O (2N).
La fonction:
Comment l'utiliser:
la source
la source
Voici mon
strtok()
implémentation de la bibliothèque zString .zstring_strtok()
diffère des bibliothèques standardstrtok()
par la manière dont elle traite les délimiteurs consécutifs.Jetez un œil au code ci-dessous, assurez-vous que vous aurez une idée de son fonctionnement (j'ai essayé d'utiliser autant de commentaires que possible)
Voici un exemple d'utilisation ...
La bibliothèque peut être téléchargée depuis Github https://github.com/fnoyanisi/zString
la source
Je pense que la solution suivante est idéale:
Explication du code:
token
pour stocker l'adresse et la longueur des jetonsstr
est entièrement composé de séparateurs, donc il y a desstrlen(str) + 1
jetons, tous des chaînes videsstr
enregistrant l'adresse et la longueur de chaque jetonNULL
valeur sentinellememcpy
car c'est plus rapide questrcpy
et nous connaissons les longueursRemarque la
malloc
vérification omise par souci de concision.En général, je ne retournerais pas un tableau de
char *
pointeurs à partir d'une fonction fractionnée comme celle-ci car cela place beaucoup de responsabilité sur l'appelant pour les libérer correctement. Une interface que je préfère est de permettre à l'appelant de passer une fonction de rappel et appeler cela pour chaque jeton, comme je l' ai décrit ici: découper une chaîne en C .la source
token
.Essayez d'utiliser ceci.
la source
Cette méthode optimisée crée (ou met à jour un tableau existant) de pointeurs dans * result et renvoie le nombre d'éléments dans * count.
Utilisez "max" pour indiquer le nombre maximum de chaînes que vous attendez (lorsque vous spécifiez un tableau existant ou tout autre reaseon), sinon définissez-le sur 0
Pour comparer avec une liste de délimiteurs, définissez delim comme un caractère * et remplacez la ligne:
avec les deux lignes suivantes:
Prendre plaisir
Exemple d'utilisation:
la source
Ma version:
la source
Il s'agit d'une fonction de fractionnement de chaîne qui peut gérer des délimiteurs à plusieurs caractères. Notez que si le délimiteur est plus long que la chaîne en cours de fractionnement, alors
buffer
etstringLengths
sera défini sur(void *) 0
, etnumStrings
sera défini sur0
.Cet algorithme a été testé et fonctionne. (Clause de non-responsabilité: il n'a pas été testé pour les chaînes non-ASCII, et il suppose que l'appelant a donné des paramètres valides)
Exemple de code:
Bibliothèques:
la source
buffer
et laisser l'allocation à l'appelant, traiter les éléments de taille maximale.Mon approche est de scanner la chaîne et de laisser les pointeurs pointer vers chaque caractère après les déliminateurs (et le premier caractère), en même temps attribuer les apparences du déliminateur dans la chaîne à '\ 0'.
Faites d'abord une copie de la chaîne d'origine (car elle est constante), puis obtenez le nombre de divisions en la parcourant, passez-la au paramètre de pointeur len . Après cela, pointez le premier pointeur de résultat vers le pointeur de chaîne de copie, puis scannez la chaîne de copie: une fois que vous rencontrez un déliminateur, affectez-le à '\ 0' ainsi la chaîne de résultat précédente est terminée, et pointez le pointeur de chaîne de résultat suivant vers le suivant pointeur de caractère.
la source
Mon code (testé):
Résultat:
la source
Exploser et imploser - la chaîne initiale reste intacte, allocation de mémoire dynamique
Usage:
la source
Si vous êtes prêt à utiliser une bibliothèque externe, je ne peux pas vous recommander
bstrlib
trop vous . Cela prend un peu de configuration supplémentaire, mais est plus facile à utiliser à long terme.Par exemple, divisez la chaîne ci-dessous, on crée d'abord un
bstring
avec l'bfromcstr()
appel. (Abstring
est un wrapper autour d'un tampon de caractères). Ensuite, divisez la chaîne par des virgules, en enregistrant le résultat dans astruct bstrList
, qui contient des champsqty
et un tableauentry
, qui est un tableau debstring
s.bstrlib
a de nombreuses autres fonctions pour opérer surbstring
sC'est de la tarte...
la source
Encore une autre réponse (cela a été déplacé d'ici à partir d' ici ):
Essayez d'utiliser la fonction strtok:
voir les détails sur ce sujet ici ou ici
Le problème ici est que vous devez traiter
words
immédiatement. Si vous voulez le stocker dans un tableau, vous devez allouer lecorrect size
car il est inconnu.Donc par exemple:
Remarque : Nous utilisons la même boucle et la même fonction pour calculer les comptages (passer un) et pour faire les copies (passer deux), afin d'éviter les problèmes d'allocation.
Remarque 2 : Vous pouvez utiliser une autre implémentation du strtok dont les raisons sont mentionnées dans des articles séparés.
Vous pouvez utiliser ceci comme:
(Je ne l'ai pas testé, alors faites-moi savoir si cela ne fonctionne pas!)
la source
Deux problèmes entourant cette question sont la gestion de la mémoire et la sécurité des threads. Comme vous pouvez le voir dans les nombreux articles, ce n'est pas une tâche facile à accomplir de manière transparente en C.Je souhaitais une solution qui soit:
La solution que j'ai trouvée répond à tous ces critères. C'est probablement un peu plus de travail à mettre en place que certaines autres solutions affichées ici, mais je pense qu'en pratique, le travail supplémentaire en vaut la peine afin d'éviter les écueils courants des autres solutions.
Voici un exemple de compilation et de sortie. Notez que dans mon exemple, j'ai volontairement épelé «AVRIL» afin que vous puissiez voir comment fonctionne l'erreur logicielle.
Prendre plaisir!
la source
Voici une autre implémentation qui fonctionnera en toute sécurité pour tokeniser un littéral de chaîne correspondant au prototype demandé dans la question renvoyant un pointeur à pointeur alloué à char (par exemple
char **
). La chaîne de délimitation peut contenir plusieurs caractères et la chaîne d'entrée peut contenir n'importe quel nombre de jetons. Toutes les allocations et réallocations sont gérées parmalloc
ourealloc
sans POSIXstrdup
.Le nombre initial de pointeurs alloués est contrôlé par la
NPTRS
constante et la seule limitation est qu'il soit supérieur à zéro. Lechar **
retourné contient une sentinelleNULL
après le dernier jeton similaire à*argv[]
et sous la forme utilisable parexecv
,execvp
etexecve
.Comme avec
strtok()
plusieurs délimiteurs séquentiels sont traités comme un seul délimiteur, ils"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
seront analysés comme si un seul','
séparait"MAY,JUN"
.La fonction ci-dessous est commentée en ligne et un court a
main()
été ajouté pour répartir les mois. Le nombre initial de pointeurs alloués a été défini sur2
pour forcer trois réallocations lors de la création de jetons de la chaîne d'entrée:Exemple d'utilisation / sortie
Dis moi si tu as d'autres questions.
la source