Un comentario ... el punto clave para una strtok()función familiar es la comprensión static variablesen C. es decir, cómo se comportan entre llamadas de función sucesivas en las que se utilizan. Vea mi código a continuación
fnisi
Respuestas:
165
Puede usar la strtok()función para dividir una cadena (y especificar el delimitador a usar). Tenga en cuenta que strtok()modificará la cadena que se le pasa. Si la cadena original se requiere en otra parte, haga una copia y pase la copia astrtok() .
EDITAR:
Ejemplo (tenga en cuenta que no maneja delimitadores consecutivos, "JAN ,,, FEB, MAR" por ejemplo):
#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;}
¡Hola! el strtokestá marcado como obsoleto por strsep(3)en la página man.
osgx
44
Como esta puede ser la pregunta / respuesta canónica en Stack Overflow para esto, ¿no hay algunas advertencias con respecto al subprocesamiento múltiple usando strtok?
Peter Mortensen
3
@osgx De acuerdo con esa página, strsepes un reemplazo para strtok, pero strtokse prefiere para la portabilidad. Por lo tanto, a menos que necesite soporte para campos vacíos o dividir varias cadenas a la vez, strtokes una mejor opción.
44
@Dojo: lo recuerda; Esa es una de las razones por las que es problemático. Sería mejor usar strtok_s()(Microsoft, C11 Anexo K, opcional) o strtok_r()(POSIX) que simple strtok(). La llanura strtok()es malvada en una función de biblioteca. No se puede utilizar ninguna función que llame a la función de biblioteca strtok()en ese momento, y ninguna función llamada por la función de biblioteca puede llamar strtok().
Jonathan Leffler
3
Solo una nota que strtok()no es segura para subprocesos (por las razones que mencionó @JonathanLeffler) y, por lo tanto, toda esta función no es segura para subprocesos. Si intenta usar esto en un entorno tratado, obtendrá resultados erráticos e impredecibles. Reemplazar strtok()por strtok_r()soluciona este problema.
Sean W
70
Creo que strsepsigue siendo la mejor herramienta para esto:
while((token = strsep(&str,","))) my_fn(token);
Esa es literalmente una línea que divide una cadena.
Los paréntesis adicionales son un elemento estilístico para indicar que estamos probando intencionalmente el resultado de una asignación, no un operador de igualdad ==.
Para que ese patrón funcione, tokeny strambos tienen tipo char *. Si comenzaste con un literal de cadena, primero querrás hacer una copia de él:
// 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 aparecen dos delimitadores juntos str, obtendrá un tokenvalor que es la cadena vacía. El valor destr se modifica porque cada delimitador encontrado se sobrescribe con un byte cero, otra buena razón para copiar la cadena que se analiza primero.
En un comentario, alguien sugirió que strtokes mejor que strsepporque strtoksea más portátil. Ubuntu y Mac OS X tienen strsep; es seguro adivinar que otros sistemas unixy también lo hacen. Windows carece strsep, pero tiene lo strbrkque permite este strsepreemplazo corto y dulce :
Aquí es una buena explicación de strsepfrente strtok. Los pros y los contras pueden juzgarse subjetivamente; Sin embargo, creo que es una señal reveladora que strsepfue diseñada como un reemplazo para strtok.
Más precisamente sobre portabilidad: no es POSIX 7 , sino BSD derivado e implementado en glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Estaba a punto de preguntar ... La C de Pelle tiene strdup (), pero no strsep ().
rdtsc
1
¿ tofreePor qué es gratuito y no str?
Sdlion
1
No puede liberar strporque su valor se puede cambiar mediante llamadas a strsep(). El valor de tofreeapunta constantemente al inicio de la memoria que desea liberar.
Tyler
26
String tokenizer este código debería ponerlo en la dirección correcta.
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;}
Cómo usarlo:
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 Programador de tres estrellas :)) Esto suena interesante.
Michi
Cuando hago esto, agrega demasiado al último token o le asigna demasiada memoria. Esta es la salida: 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
Este ejemplo tiene múltiples pérdidas de memoria. Para cualquiera que lea esto, no use este enfoque. En su lugar, prefiera los enfoques de tokenización strtok o strsep.
Jorma Rebane el
7
Aquí están mis dos centavos:
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, tres punteros! Ya tengo miedo de usarlo jajaja solo soy yo, no soy muy bueno con los punteros en c.
Hafiz Temuri
Gracias, todas las respuestas anteriores no funcionaron en mi caso, incluso después de muchos esfuerzos, ¡y su código funciona de maravilla!
hmmftg
4
En el ejemplo anterior, habría una manera de devolver una matriz de cadenas terminadas en nulo (como desee) en su lugar en la cadena. Sin embargo, no sería posible pasar una cadena literal, ya que tendría que ser modificada por la función:
#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;}
Probablemente haya una forma más ordenada de hacerlo, pero se entiende la idea.
Esta función toma una cadena char * y la divide por el deliminador. Puede haber múltiples delimitadores seguidos. Tenga en cuenta que la función modifica la cadena original. Debe hacer una copia de la cadena original primero si necesita que el original permanezca inalterado. Esta función no utiliza ninguna llamada a la función cstring, por lo que puede ser un poco más rápida que otras. Si no le importa la asignación de memoria, puede asignar sub_cadenas en la parte superior de la función con tamaño strlen (src_str) / 2 y (como se menciona en la "versión" de c ++) omita la mitad inferior de la función. Si hace esto, la función se reduce a O (N), pero la forma optimizada de memoria que se muestra a continuación es O (2N).
La función:
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;}
A continuación se muestra mi strtok()implementación de la biblioteca zString .
zstring_strtok()difiere de las bibliotecas estándar strtok()en la forma en que trata los delimitadores consecutivos.
Solo eche un vistazo al código a continuación, seguro de que tendrá una idea de cómo funciona (intenté usar tantos comentarios como pude)
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;}
A continuación se muestra un ejemplo de uso ...
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)
Reingreso: es decir, puede llamarlo con seguridad desde cualquier lugar en uno o más hilos
Portátil
Maneja separadores múltiples correctamente
Rápido y eficiente
Explicación del código:
Definir una estructura. token para almacenar la dirección y la longitud de los tokens.
Asigne suficiente memoria para estos en el peor de los casos, que es cuando
strse compone completamente de separadores para que hayastrlen(str) + 1
tokens, todos ellos cadenas vacías
Escanear str registrando la dirección y la longitud de cada token
Use esto para asignar la matriz de salida del tamaño correcto, incluido un espacio adicional para un NULL valor centinela
Asigne, copie y agregue los tokens utilizando la información de inicio y longitud; utilícelo memcpyya que es más rápido questrcpy y conocemos las longitudes
Libere la dirección del token y la matriz de longitud
Devuelve la matriz de tokens
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;}
Notamalloc omitida por brevedad.
En general, no devolvería una serie de char *punteros de una función dividida como esta, ya que pone mucha responsabilidad en la persona que llama para liberarlos correctamente. Una interfaz prefiero es permitir que la persona que llama pase a una función de devolución de llamada y llamar a esto para cada modo, como lo he descrito aquí: dividir una cadena en C .
La exploración de separadores dos veces es probablemente más recomendable que la asignación de una matriz potencialmente grande de token.
chqrlie
2
Intenta usar esto.
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;}
Este método optimizado crea (o actualiza) una matriz de punteros en * resultado y devuelve el número de elementos en * count.
Use "max" para indicar el número máximo de cadenas que espera (cuando especifica una matriz existente o cualquier otra razón), de lo contrario, configúrelo en 0
Para comparar con una lista de delimitadores, defina delim como char * y reemplace la línea:
if(str[i]==delim){
con las dos siguientes líneas:
char*c=delim;while(*c &&*c!=str[i]) c++;if(*c){
Disfrutar
#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;}
Esta es una función de división de cadenas que puede manejar delimitadores de caracteres múltiples. Tenga en cuenta que si el delimitador es más larga que la cadena que se ha dividido, a continuación, buffery stringLengthsse establece en (void *) 0, y numStringsse establece en 0.
Este algoritmo ha sido probado y funciona. (Descargo de responsabilidad: no se ha probado para cadenas que no sean ASCII, y se supone que la persona que llamó dio parámetros válidos)
¿Cómo llamo a esto desde main? No sé qué pasar al búfer.
Aymon Fournier
La lógica de asignación es incorrecta. realloc () devuelve un nuevo puntero y descarta el valor devuelto. No hay una forma adecuada de devolver un nuevo puntero de memoria: el prototipo de la función debe cambiarse para aceptar el tamaño asignado buffery dejar la asignación a la persona que llama, procesar los elementos de tamaño máximo.
Alex
@Alex Fijo, completamente reescrito y probado. Nota: no estoy seguro de si esto funcionará para personas que no son ASCII o no.
Élektra
Para empezar, este no es el código C. ¿Y por qué pasarías punteros por referencia real en C ++?
Kamiccolo
@Kamiccolo Lo siento, ¿cómo exactamente no es este código C? Además, ¿por qué pasar los punteros por referencia es un problema aquí?
Élektra
1
Mi enfoque es escanear la cadena y dejar que los punteros apunten a cada carácter después de los delimitadores (y el primer carácter), al mismo tiempo asignar las apariencias del deliminador en cadena a '\ 0'.
Primero haga una copia de la cadena original (ya que es constante), luego obtenga el número de divisiones escaneando y páselo al parámetro puntero len . Después de eso, apunte el primer puntero de resultado al puntero de la cadena de copia, luego escanee la cadena de copia: una vez que encuentre un deliminador, asígnelo a '\ 0' para que la cadena de resultado anterior finalice y apunte el siguiente puntero de cadena de resultado al siguiente puntero de caracteres.
Este método está mal. Acabo de eliminar esta publicación, pero luego me di cuenta de que puede ser interesante para algunos de ustedes.
metalcrash
1
Mi código (probado):
#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);}
Resultado:
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 está dispuesto a usar una biblioteca externa, no puedo recomendar bstrlib suficiente. Requiere un poco de configuración adicional, pero es más fácil de usar a largo plazo.
Por ejemplo, divida la cadena a continuación, primero se crea un bstringcon la bfromcstr()llamada. (A bstringes una envoltura alrededor de un búfer de caracteres). Luego, divida la cadena en comas, guardando el resultado en a struct bstrList, que tiene campos qtyy una matriz entry, que es una matriz de bstrings.
bstrlib tiene muchas otras funciones para operar bstring s
El problema aquí es que tienes que procesarlo de wordsinmediato. Si desea almacenarlo en una matriz, debe asignarlo correct sizeporque no se conoce.
Así por ejemplo:
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);}
Nota : Utilizamos el mismo bucle y función para calcular los recuentos (pase uno) y para hacer las copias (pase dos), a fin de evitar problemas de asignación.
Nota 2 : puede usar alguna otra implementación de strtok que mencione los motivos en publicaciones separadas.
Puedes usar esto como:
int main(void){char**array=Split("Hello World!"," ");// Now you have the array// ...// Then free the memoryFree_Array(array);array= NULL;return0;}
Dos cuestiones relacionadas con esta pregunta son la administración de memoria y la seguridad de subprocesos. Como puede ver en las numerosas publicaciones, esta no es una tarea fácil de lograr sin problemas en C. Deseaba una solución que sea:
A salvo de amenazas. (strtok no es seguro para subprocesos)
No emplea malloc ni ninguno de sus derivados (para evitar problemas de administración de memoria)
Comprueba los límites de la matriz en los campos individuales (para evitar fallas de segmento en datos desconocidos)
Funciona con separadores de campo de varios bytes (utf-8)
ignora los campos adicionales en la entrada
proporciona una rutina de error suave para longitudes de campo no válidas
La solución que se me ocurrió cumple con todos estos criterios. Probablemente sea un poco más de trabajo configurar que algunas otras soluciones publicadas aquí, pero creo que en la práctica, el trabajo adicional vale la pena para evitar las trampas comunes de otras soluciones.
A continuación se muestra un ejemplo de compilación y salida. Tenga en cuenta que en mi ejemplo, deletreé a propósito "ABRIL" para que pueda ver cómo funciona el error de software.
$ 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
Aquí hay otra implementación que funcionará de manera segura para simular un literal de cadena que coincida con el prototipo solicitado en la pregunta que devuelve un puntero a puntero asignado a char (por ejemplo char **). La cadena delimitador puede contener varios caracteres, y la cadena de entrada puede contener cualquier cantidad de tokens. Todas las asignaciones y reasignaciones son manejadas por malloco reallocsin POSIX strdup.
El número inicial de punteros asignados está controlado por la NPTRSconstante y la única limitación es que sea mayor que cero. La char **regresado contiene un centinelaNULL después de la última ficha similar a *argv[]y en forma utilizable por execv, execvpyexecve .
Al igual que con strtok()los delimitadores secuenciales múltiples, se tratan como un delimitador único, por "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"lo que se analizarán como si solo un solo ','separador "MAY,JUN".
La siguiente función se comenta en línea y main()se agregó un breve dividiendo los meses. El número inicial de punteros asignados se estableció en 2forzar tres reasignaciones durante la tokenización de la cadena de entrada:
#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 */}}
Ejemplo de uso / salida
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
strtok
función de la biblioteca estándar para lograr lo mismo.strtok()
función familiar es la comprensiónstatic variables
en C. es decir, cómo se comportan entre llamadas de función sucesivas en las que se utilizan. Vea mi código a continuaciónRespuestas:
Puede usar la
strtok()
función para dividir una cadena (y especificar el delimitador a usar). Tenga en cuenta questrtok()
modificará la cadena que se le pasa. Si la cadena original se requiere en otra parte, haga una copia y pase la copia astrtok()
.EDITAR:
Ejemplo (tenga en cuenta que no maneja delimitadores consecutivos, "JAN ,,, FEB, MAR" por ejemplo):
Salida:
fuente
strtok
está marcado como obsoleto porstrsep(3)
en la página man.strsep
es un reemplazo parastrtok
, perostrtok
se prefiere para la portabilidad. Por lo tanto, a menos que necesite soporte para campos vacíos o dividir varias cadenas a la vez,strtok
es una mejor opción.strtok_s()
(Microsoft, C11 Anexo K, opcional) ostrtok_r()
(POSIX) que simplestrtok()
. La llanurastrtok()
es malvada en una función de biblioteca. No se puede utilizar ninguna función que llame a la función de bibliotecastrtok()
en ese momento, y ninguna función llamada por la función de biblioteca puede llamarstrtok()
.strtok()
no es segura para subprocesos (por las razones que mencionó @JonathanLeffler) y, por lo tanto, toda esta función no es segura para subprocesos. Si intenta usar esto en un entorno tratado, obtendrá resultados erráticos e impredecibles. Reemplazarstrtok()
porstrtok_r()
soluciona este problema.Creo que
strsep
sigue siendo la mejor herramienta para esto:Esa es literalmente una línea que divide una cadena.
Los paréntesis adicionales son un elemento estilístico para indicar que estamos probando intencionalmente el resultado de una asignación, no un operador de igualdad
==
.Para que ese patrón funcione,
token
ystr
ambos tienen tipochar *
. Si comenzaste con un literal de cadena, primero querrás hacer una copia de él:Si aparecen dos delimitadores juntos
str
, obtendrá untoken
valor que es la cadena vacía. El valor destr
se modifica porque cada delimitador encontrado se sobrescribe con un byte cero, otra buena razón para copiar la cadena que se analiza primero.En un comentario, alguien sugirió que
strtok
es mejor questrsep
porquestrtok
sea más portátil. Ubuntu y Mac OS X tienenstrsep
; es seguro adivinar que otros sistemas unixy también lo hacen. Windows carecestrsep
, pero tiene lostrbrk
que permite estestrsep
reemplazo corto y dulce :Aquí es una buena explicación de
strsep
frentestrtok
. Los pros y los contras pueden juzgarse subjetivamente; Sin embargo, creo que es una señal reveladora questrsep
fue diseñada como un reemplazo parastrtok
.fuente
tofree
Por qué es gratuito y nostr
?str
porque su valor se puede cambiar mediante llamadas astrsep()
. El valor detofree
apunta constantemente al inicio de la memoria que desea liberar.String tokenizer este código debería ponerlo en la dirección correcta.
fuente
El siguiente método hará todo el trabajo (asignación de memoria, contando la longitud) por usted. Puede encontrar más información y descripción aquí: Implementación del método Java String.split () para dividir la cadena C
Cómo usarlo:
fuente
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.¢
Aquí están mis dos centavos:
Uso:
fuente
En el ejemplo anterior, habría una manera de devolver una matriz de cadenas terminadas en nulo (como desee) en su lugar en la cadena. Sin embargo, no sería posible pasar una cadena literal, ya que tendría que ser modificada por la función:
Probablemente haya una forma más ordenada de hacerlo, pero se entiende la idea.
fuente
Esta función toma una cadena char * y la divide por el deliminador. Puede haber múltiples delimitadores seguidos. Tenga en cuenta que la función modifica la cadena original. Debe hacer una copia de la cadena original primero si necesita que el original permanezca inalterado. Esta función no utiliza ninguna llamada a la función cstring, por lo que puede ser un poco más rápida que otras. Si no le importa la asignación de memoria, puede asignar sub_cadenas en la parte superior de la función con tamaño strlen (src_str) / 2 y (como se menciona en la "versión" de c ++) omita la mitad inferior de la función. Si hace esto, la función se reduce a O (N), pero la forma optimizada de memoria que se muestra a continuación es O (2N).
La función:
Cómo usarlo:
fuente
fuente
A continuación se muestra mi
strtok()
implementación de la biblioteca zString .zstring_strtok()
difiere de las bibliotecas estándarstrtok()
en la forma en que trata los delimitadores consecutivos.Solo eche un vistazo al código a continuación, seguro de que tendrá una idea de cómo funciona (intenté usar tantos comentarios como pude)
A continuación se muestra un ejemplo de uso ...
La biblioteca se puede descargar desde Github https://github.com/fnoyanisi/zString
fuente
Creo que la siguiente solución es ideal:
Explicación del código:
token
para almacenar la dirección y la longitud de los tokens.str
se compone completamente de separadores para que hayastrlen(str) + 1
tokens, todos ellos cadenas vacíasstr
registrando la dirección y la longitud de cada tokenNULL
valor centinelamemcpy
ya que es más rápido questrcpy
y conocemos las longitudesNota
malloc
omitida por brevedad.En general, no devolvería una serie de
char *
punteros de una función dividida como esta, ya que pone mucha responsabilidad en la persona que llama para liberarlos correctamente. Una interfaz prefiero es permitir que la persona que llama pase a una función de devolución de llamada y llamar a esto para cada modo, como lo he descrito aquí: dividir una cadena en C .fuente
token
.Intenta usar esto.
fuente
Este método optimizado crea (o actualiza) una matriz de punteros en * resultado y devuelve el número de elementos en * count.
Use "max" para indicar el número máximo de cadenas que espera (cuando especifica una matriz existente o cualquier otra razón), de lo contrario, configúrelo en 0
Para comparar con una lista de delimitadores, defina delim como char * y reemplace la línea:
con las dos siguientes líneas:
Disfrutar
Ejemplo de uso:
fuente
Mi version:
fuente
Esta es una función de división de cadenas que puede manejar delimitadores de caracteres múltiples. Tenga en cuenta que si el delimitador es más larga que la cadena que se ha dividido, a continuación,
buffer
ystringLengths
se establece en(void *) 0
, ynumStrings
se establece en0
.Este algoritmo ha sido probado y funciona. (Descargo de responsabilidad: no se ha probado para cadenas que no sean ASCII, y se supone que la persona que llamó dio parámetros válidos)
Código de muestra:
Bibliotecas:
fuente
buffer
y dejar la asignación a la persona que llama, procesar los elementos de tamaño máximo.Mi enfoque es escanear la cadena y dejar que los punteros apunten a cada carácter después de los delimitadores (y el primer carácter), al mismo tiempo asignar las apariencias del deliminador en cadena a '\ 0'.
Primero haga una copia de la cadena original (ya que es constante), luego obtenga el número de divisiones escaneando y páselo al parámetro puntero len . Después de eso, apunte el primer puntero de resultado al puntero de la cadena de copia, luego escanee la cadena de copia: una vez que encuentre un deliminador, asígnelo a '\ 0' para que la cadena de resultado anterior finalice y apunte el siguiente puntero de cadena de resultado al siguiente puntero de caracteres.
fuente
Mi código (probado):
Resultado:
fuente
Explotar e implosionar: la cadena inicial permanece intacta, asignación de memoria dinámica
Uso:
fuente
Si está dispuesto a usar una biblioteca externa, no puedo recomendar
bstrlib
suficiente. Requiere un poco de configuración adicional, pero es más fácil de usar a largo plazo.Por ejemplo, divida la cadena a continuación, primero se crea un
bstring
con labfromcstr()
llamada. (Abstring
es una envoltura alrededor de un búfer de caracteres). Luego, divida la cadena en comas, guardando el resultado en astruct bstrList
, que tiene camposqty
y una matrizentry
, que es una matriz debstring
s.bstrlib
tiene muchas otras funciones para operarbstring
sMuy fácil...
fuente
Otra respuesta más (esto se movió aquí desde aquí ):
Intente usar la función strtok:
ver detalles sobre este tema aquí o aquí
El problema aquí es que tienes que procesarlo de
words
inmediato. Si desea almacenarlo en una matriz, debe asignarlocorrect size
porque no se conoce.Así por ejemplo:
Nota : Utilizamos el mismo bucle y función para calcular los recuentos (pase uno) y para hacer las copias (pase dos), a fin de evitar problemas de asignación.
Nota 2 : puede usar alguna otra implementación de strtok que mencione los motivos en publicaciones separadas.
Puedes usar esto como:
(No lo probé, ¡así que avíseme si no funciona!)
fuente
Dos cuestiones relacionadas con esta pregunta son la administración de memoria y la seguridad de subprocesos. Como puede ver en las numerosas publicaciones, esta no es una tarea fácil de lograr sin problemas en C. Deseaba una solución que sea:
La solución que se me ocurrió cumple con todos estos criterios. Probablemente sea un poco más de trabajo configurar que algunas otras soluciones publicadas aquí, pero creo que en la práctica, el trabajo adicional vale la pena para evitar las trampas comunes de otras soluciones.
A continuación se muestra un ejemplo de compilación y salida. Tenga en cuenta que en mi ejemplo, deletreé a propósito "ABRIL" para que pueda ver cómo funciona el error de software.
¡Disfrutar!
fuente
Aquí hay otra implementación que funcionará de manera segura para simular un literal de cadena que coincida con el prototipo solicitado en la pregunta que devuelve un puntero a puntero asignado a char (por ejemplo
char **
). La cadena delimitador puede contener varios caracteres, y la cadena de entrada puede contener cualquier cantidad de tokens. Todas las asignaciones y reasignaciones son manejadas pormalloc
orealloc
sin POSIXstrdup
.El número inicial de punteros asignados está controlado por la
NPTRS
constante y la única limitación es que sea mayor que cero. Lachar **
regresado contiene un centinelaNULL
después de la última ficha similar a*argv[]
y en forma utilizable porexecv
,execvp
yexecve
.Al igual que con
strtok()
los delimitadores secuenciales múltiples, se tratan como un delimitador único, por"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
lo que se analizarán como si solo un solo','
separador"MAY,JUN"
.La siguiente función se comenta en línea y
main()
se agregó un breve dividiendo los meses. El número inicial de punteros asignados se estableció en2
forzar tres reasignaciones durante la tokenización de la cadena de entrada:Ejemplo de uso / salida
Avísame si tienes más preguntas.
fuente