Um comentário ... o ponto chave para uma strtok()função de família é entender static variablesem C. ou seja, como eles se comportam entre chamadas de funções sucessivas nas quais são usadas. Veja meu código abaixo
fnisi
Respostas:
165
Você pode usar a strtok()função para dividir uma sequência (e especificar o delimitador a ser usado). Observe que strtok()modificará a string passada para ele. Se a sequência original for necessária em outro lugar, faça uma cópia e passe a cópia para strtok().
EDITAR:
Exemplo (observe que ele não trata delimitadores consecutivos, "JAN ,,, FEB, MAR", por exemplo):
#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;}
Oi! o strtokestá marcado como obsoleto strsep(3)na página do manual.
Osgx
4
Como essa pode ser a pergunta / resposta canônica no Stack Overflow para isso, não há algumas ressalvas em relação à multiencadeamento usando strtok?
Peter Mortensen
3
@osgx De acordo com essa página, strsepé um substituto para strtok, mas strtoké preferível para portabilidade. Portanto, a menos que você precise de suporte para campos vazios ou que divida várias cadeias de uma vez, strtoké uma escolha melhor.
4
@ Dojo: Ele lembra; essa é uma das razões pelas quais é problemático. Seria melhor usar strtok_s()(Microsoft, C11 anexo K, opcional) ou strtok_r()(POSIX) do que simples strtok(). Plain strtok()é mau em uma função de biblioteca. Nenhuma função que chame a função de biblioteca pode estar usando strtok()no momento e nenhuma função chamada pela função de biblioteca pode chamar strtok().
Jonathan Leffler
3
Apenas uma observação que strtok()não é segura para threads (pelas razões mencionadas por @ JonathanLeffler) e, portanto, toda essa função não é segura para threads. Se você tentar usar isso em um ambiente com piso, obterá resultados erráticos e imprevisíveis. A substituição strtok()por strtok_r()correções desse problema.
Sean W
70
Eu acho que strsepainda é a melhor ferramenta para isso:
while((token = strsep(&str,","))) my_fn(token);
Essa é literalmente uma linha que divide uma string.
Os parênteses extras são um elemento estilístico para indicar que estamos testando intencionalmente o resultado de uma atribuição, não um operador de igualdade ==.
Para que esse padrão funcione, tokene strambos têm tipo char *. Se você começou com uma cadeia de caracteres literal, convém fazer uma cópia dela primeiro:
// 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);
Se dois delimitadores aparecerem juntos str, você obterá um tokenvalor que é a sequência vazia. O valor de stré modificado em que cada delimitador encontrado é substituído por um byte zero - outro bom motivo para copiar a sequência sendo analisada primeiro.
Em um comentário, alguém sugeriu que strtoké melhor do que strsepporque strtoké mais portátil. Ubuntu e Mac OS X tem strsep; é seguro adivinhar que outros sistemas unixy também. O Windows não possui strsep, mas possui o strbrkque permite essa strsepsubstituição curta e agradável:
Aqui está uma boa explicação da strsepvs strtok. Os prós e contras podem ser julgados subjetivamente; no entanto, acho que é um sinal revelador que strsepfoi projetado como um substituto para strtok.
Mais precisamente na portabilidade: não é o POSIX 7 , mas o BSD derivado e implementado na glibc .
Ciro Santilli respondeu
Eu estava prestes a perguntar ... O C de Pelle tem strdup (), mas não strsep ().
Rdtsc
1
por que tofreeaquele é livre e não str?
Sdlion 17/05/19
1
Você não pode liberar strporque seu valor pode ser alterado com chamadas para strsep(). O valor de tofreeaponta consistentemente para o início da memória que você deseja liberar.
Tyler
26
O tokenizador de string este código deve colocá-lo na direção certa.
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;}
Como usá-lo:
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 três estrelas :)) Isso parece interessante.
06716 Michi
Quando faço isso, ele adiciona muito ao último token ou aloca muita memória. Esta é a saída: found 10 tokens. string #0: Hello, string #1: this string #2: is string #3: a string #4: test string #5: module string #6: for string #7: the string #8: string string #9: splitting.¢
KeiserHarm
2
Este exemplo possui vários vazamentos de memória. Para quem estiver lendo isso, não use essa abordagem. Prefira as abordagens de tokenização strtok ou strsep.
Jorma Rebane
7
Aqui estão meus dois 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, três ponteiros! Eu já estou com medo de usá-lo lol sou só eu, eu não sou muito bom com ponteiros em c.
Hafiz Temuri 03/04
Obrigado cara, todas as respostas acima do strtok não funcionaram no meu caso, mesmo depois de muitos esforços, e seu código funciona como um encanto!
Hmmftg #
4
No exemplo acima, haveria uma maneira de retornar uma matriz de seqüências terminadas nulas (como você deseja) no lugar na sequência. Porém, não seria possível transmitir uma string literal, pois precisaria ser modificada pela função:
#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;}
Provavelmente existe uma maneira mais clara de fazer isso, mas você entendeu.
Essa função pega uma string char * e a divide pelo deliminador. Pode haver vários deliminadores seguidos. Observe que a função modifica a string original. Você deve fazer uma cópia da sequência original primeiro se precisar que o original permaneça inalterado. Esta função não usa nenhuma chamada de função cstring, portanto pode ser um pouco mais rápida que outras. Se você não se importa com a alocação de memória, pode alocar sub_strings na parte superior da função com o tamanho strlen (src_str) / 2 e (como a "versão" do c ++ mencionada) pula a metade inferior da função. Se você fizer isso, a função será reduzida para O (N), mas a maneira otimizada de memória mostrada abaixo é O (2N).
A função:
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;}
Abaixo está minha strtok()implementação da biblioteca zString .
zstring_strtok()difere da biblioteca padrãostrtok() na maneira como trata delimitadores consecutivos.
Basta dar uma olhada no código abaixo, para ter uma idéia de como ele funciona (tentei usar o máximo de comentários possível)
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;}
Abaixo está um exemplo 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)
Reentrante - ou seja, você pode chamá-lo com segurança de qualquer lugar em um ou mais threads
Portátil
Lida com vários separadores corretamente
Rápido e eficiente
Explicação do código:
Definir uma estrutura token para armazenar o endereço e os comprimentos dos tokens
Aloque memória suficiente para eles no pior dos casos, que é quando
stré composto inteiramente de separadores para que hajastrlen(str) + 1
tokens, todos eles cadeias vazias
Varredura str registrando o endereço e o comprimento de cada token
Use isso para alocar a matriz de saída do tamanho correto, incluindo um espaço extra para um NULL valor de sentinela
Aloque, copie e adicione os tokens usando as informações de início e duração - use memcpyo mais rápido possívelstrcpy e sabemos os comprimentos
Libere o endereço do token e a matriz de comprimento
Retornar a 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;}
Amalloc verificação de notas foi omitida por questões de concisão.
Em geral, eu não retornaria uma matriz de char *ponteiros de uma função dividida como esta, pois coloca muita responsabilidade no chamador para liberá-los corretamente. Uma interface prefiro é permitir que o chamador para passar uma função de retorno de chamada e chamar isso para cada forma, como eu descrevi aqui: Divide uma string em C .
A varredura de separadores duas vezes é provavelmente mais aconselhável do que alocar uma matriz potencialmente grande de token.
chqrlie
2
Tente usar isso.
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;}
Esse método otimizado cria (ou atualiza uma matriz existente) de ponteiros em * result e retorna o número de elementos em * count.
Use "max" para indicar o número máximo de strings que você espera (quando você especificar uma matriz existente ou qualquer outra versão), caso contrário, defina-a como 0
Para comparar com uma lista de delimitadores, defina delim como um caractere * e substitua a linha:
if(str[i]==delim){
com as duas seguintes linhas:
char*c=delim;while(*c &&*c!=str[i]) c++;if(*c){
Aproveitar
#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 é uma função de divisão de cadeia que pode manipular delimitadores de vários caracteres. Observe que, se o delimitador for maior que a cadeia que está sendo dividida, buffere stringLengthsserá definido como (void *) 0e numStringsserá definido como 0.
Este algoritmo foi testado e funciona. (Isenção de responsabilidade: não foi testado para cadeias não ASCII e assume que o chamador forneceu parâmetros válidos)
Como eu chamo isso de main? Não sei o que passar para o buffer.
Aymon Fournier
A lógica de alocação está errada. realloc () retorna um novo ponteiro e você descarta o valor retornado. Nenhuma maneira adequada de retornar um novo ponteiro de memória - o protótipo da função deve ser alterado para aceitar o tamanho da alocação buffere deixar a alocação para o chamador, processar elementos de tamanho máximo.
1328 Alex
@Alex Corrigido, completamente reescrito e testado. Nota: não tenho certeza se isso funcionará para não ASCII ou não.
Elektra
Para iniciantes, esse não é um código C. E por que você passaria ponteiros por referência real em C ++?
Kamiccolo 17/08/19
@ Kamiccolo Sinto muito, como exatamente isso não é código C? Além disso, por que passar ponteiros por referência é um problema aqui?
Elektra
1
Minha abordagem é varrer a string e deixar os ponteiros apontarem para todos os caracteres após os deliminadores (e o primeiro caractere), ao mesmo tempo atribuir as aparências do deliminator na string a '\ 0'.
Primeiro faça uma cópia da string original (uma vez que é constante) e, em seguida, obtenha o número de divisões fazendo a varredura, passando-a para o parâmetro ponteiro len . Depois disso, aponte o ponteiro do primeiro resultado para o ponteiro da sequência de cópia e, em seguida, digitalize a sequência de cópias: uma vez que encontre um deliminador, atribua-o a '\ 0', assim a sequência de resultados anterior será encerrada e aponte o próximo ponteiro da sequência de resultados para o próximo ponteiro de caractere.
Este método está errado. Acabei de excluir este post, mas percebi que talvez seja interessante para alguns de vocês.
precisa saber é o seguinte
1
Meu código (testado):
#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
Se você estiver disposto a usar uma biblioteca externa, não posso recomendar bstrlib suficiente. É preciso uma configuração extra, mas é mais fácil de usar a longo prazo.
Por exemplo, divida a sequência abaixo, primeiro crie um bstringcom a bfromcstr()chamada. (A bstringé um invólucro em torno de um buffer de char). Em seguida, divida a string em vírgulas, salvando o resultado em a struct bstrList, que possui campos qtye uma matriz entry, que é uma matriz de bstrings.
bstrlib tem muitas outras funções para operar bstring s
O problema aqui é que você deve processar o wordsimediatamente. Se você deseja armazená-lo em uma matriz, é necessário alocá- correct sizelo, pois isso é desconhecido.
Então, por exemplo:
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 o mesmo loop e função para calcular as contagens (passe um) e para fazer as cópias (passe dois), a fim de evitar problemas de alocação.
Nota 2 : Você pode usar alguma outra implementação do strtok pelos motivos mencionados em postagens separadas.
Você pode usar isso como:
int main(void){char**array=Split("Hello World!"," ");// Now you have the array// ...// Then free the memoryFree_Array(array);array= NULL;return0;}
(Eu não testei, por favor, deixe-me saber se não funciona!)
Dois problemas que cercam essa pergunta são o gerenciamento de memória e a segurança de threads. Como você pode ver nas inúmeras postagens, essa não é uma tarefa fácil de executar perfeitamente em C. Eu desejava uma solução que fosse:
Discussão segura. (strtok não é seguro para threads)
Não emprega malloc ou qualquer um de seus derivados (para evitar problemas de gerenciamento de memória)
Verifica os limites da matriz nos campos individuais (para evitar falhas de segmento em dados desconhecidos)
Funciona com separadores de campos de vários bytes (utf-8)
ignora campos extras na entrada
fornece rotina de erro virtual para comprimentos de campo inválidos
A solução que eu encontrei atende a todos esses critérios. Provavelmente é um pouco mais trabalhoso de configurar do que algumas outras soluções postadas aqui, mas acho que na prática o trabalho extra vale a pena para evitar as armadilhas comuns de outras soluções.
Abaixo está um exemplo de compilação e saída. Observe que no meu exemplo, eu escrevi propositalmente "ABRIL" para que você possa ver como o erro virtual funciona.
$ 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
Aqui está outra implementação que operará com segurança para tokenizar um literal de string que corresponda ao protótipo solicitado na pergunta, retornando um ponteiro para ponteiro alocado para char (por exemplo char **). A cadeia de caracteres do delimitador pode conter vários caracteres e a cadeia de entrada pode conter qualquer número de tokens. Todas as alocações e realocações são tratadas por mallocou reallocsem POSIX strdup.
O número inicial de ponteiros alocados é controlado pela NPTRSconstante e a única limitação é que seja maior que zero. O char **retornado contém uma sentinelaNULL após o último token semelhante *argv[]e na forma utilizável por execv, execvpeexecve .
Assim como strtok()vários delimitadores seqüenciais são tratados como um único delimitador, "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"eles serão analisados como se apenas um único fosse ','separado "MAY,JUN".
A função abaixo é comentada em linha e um curta main()foi adicionado dividindo os meses. O número inicial de ponteiros alocados foi definido 2para forçar três realocações durante a tokenização da sequência 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 */}}
Exemplo de uso / saída
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
strtok
função da biblioteca padrão para obter a mesma coisa.strtok()
função de família é entenderstatic variables
em C. ou seja, como eles se comportam entre chamadas de funções sucessivas nas quais são usadas. Veja meu código abaixoRespostas:
Você pode usar a
strtok()
função para dividir uma sequência (e especificar o delimitador a ser usado). Observe questrtok()
modificará a string passada para ele. Se a sequência original for necessária em outro lugar, faça uma cópia e passe a cópia parastrtok()
.EDITAR:
Exemplo (observe que ele não trata delimitadores consecutivos, "JAN ,,, FEB, MAR", por exemplo):
Resultado:
fonte
strtok
está marcado como obsoletostrsep(3)
na página do manual.strsep
é um substituto parastrtok
, masstrtok
é preferível para portabilidade. Portanto, a menos que você precise de suporte para campos vazios ou que divida várias cadeias de uma vez,strtok
é uma escolha melhor.strtok_s()
(Microsoft, C11 anexo K, opcional) oustrtok_r()
(POSIX) do que simplesstrtok()
. Plainstrtok()
é mau em uma função de biblioteca. Nenhuma função que chame a função de biblioteca pode estar usandostrtok()
no momento e nenhuma função chamada pela função de biblioteca pode chamarstrtok()
.strtok()
não é segura para threads (pelas razões mencionadas por @ JonathanLeffler) e, portanto, toda essa função não é segura para threads. Se você tentar usar isso em um ambiente com piso, obterá resultados erráticos e imprevisíveis. A substituiçãostrtok()
porstrtok_r()
correções desse problema.Eu acho que
strsep
ainda é a melhor ferramenta para isso:Essa é literalmente uma linha que divide uma string.
Os parênteses extras são um elemento estilístico para indicar que estamos testando intencionalmente o resultado de uma atribuição, não um operador de igualdade
==
.Para que esse padrão funcione,
token
estr
ambos têm tipochar *
. Se você começou com uma cadeia de caracteres literal, convém fazer uma cópia dela primeiro:Se dois delimitadores aparecerem juntos
str
, você obterá umtoken
valor que é a sequência vazia. O valor destr
é modificado em que cada delimitador encontrado é substituído por um byte zero - outro bom motivo para copiar a sequência sendo analisada primeiro.Em um comentário, alguém sugeriu que
strtok
é melhor do questrsep
porquestrtok
é mais portátil. Ubuntu e Mac OS X temstrsep
; é seguro adivinhar que outros sistemas unixy também. O Windows não possuistrsep
, mas possui ostrbrk
que permite essastrsep
substituição curta e agradável:Aqui está uma boa explicação da
strsep
vsstrtok
. Os prós e contras podem ser julgados subjetivamente; no entanto, acho que é um sinal revelador questrsep
foi projetado como um substituto parastrtok
.fonte
tofree
aquele é livre e nãostr
?str
porque seu valor pode ser alterado com chamadas parastrsep()
. O valor detofree
aponta consistentemente para o início da memória que você deseja liberar.O tokenizador de string este código deve colocá-lo na direção certa.
fonte
O método abaixo fará todo o trabalho (alocação de memória, contando o comprimento) para você. Mais informações e descrição podem ser encontradas aqui - Implementação do método Java String.split () para dividir a string C
Como usá-lo:
fonte
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.¢
Aqui estão meus dois centavos:
Uso:
fonte
No exemplo acima, haveria uma maneira de retornar uma matriz de seqüências terminadas nulas (como você deseja) no lugar na sequência. Porém, não seria possível transmitir uma string literal, pois precisaria ser modificada pela função:
Provavelmente existe uma maneira mais clara de fazer isso, mas você entendeu.
fonte
Essa função pega uma string char * e a divide pelo deliminador. Pode haver vários deliminadores seguidos. Observe que a função modifica a string original. Você deve fazer uma cópia da sequência original primeiro se precisar que o original permaneça inalterado. Esta função não usa nenhuma chamada de função cstring, portanto pode ser um pouco mais rápida que outras. Se você não se importa com a alocação de memória, pode alocar sub_strings na parte superior da função com o tamanho strlen (src_str) / 2 e (como a "versão" do c ++ mencionada) pula a metade inferior da função. Se você fizer isso, a função será reduzida para O (N), mas a maneira otimizada de memória mostrada abaixo é O (2N).
A função:
Como usá-lo:
fonte
fonte
Abaixo está minha
strtok()
implementação da biblioteca zString .zstring_strtok()
difere da biblioteca padrãostrtok()
na maneira como trata delimitadores consecutivos.Basta dar uma olhada no código abaixo, para ter uma idéia de como ele funciona (tentei usar o máximo de comentários possível)
Abaixo está um exemplo de uso ...
A biblioteca pode ser baixada no Github https://github.com/fnoyanisi/zString
fonte
Eu acho que a seguinte solução é ideal:
Explicação do código:
token
para armazenar o endereço e os comprimentos dos tokensstr
é composto inteiramente de separadores para que hajastrlen(str) + 1
tokens, todos eles cadeias vaziasstr
registrando o endereço e o comprimento de cada tokenNULL
valor de sentinelamemcpy
o mais rápido possívelstrcpy
e sabemos os comprimentosA
malloc
verificação de notas foi omitida por questões de concisão.Em geral, eu não retornaria uma matriz de
char *
ponteiros de uma função dividida como esta, pois coloca muita responsabilidade no chamador para liberá-los corretamente. Uma interface prefiro é permitir que o chamador para passar uma função de retorno de chamada e chamar isso para cada forma, como eu descrevi aqui: Divide uma string em C .fonte
token
.Tente usar isso.
fonte
Esse método otimizado cria (ou atualiza uma matriz existente) de ponteiros em * result e retorna o número de elementos em * count.
Use "max" para indicar o número máximo de strings que você espera (quando você especificar uma matriz existente ou qualquer outra versão), caso contrário, defina-a como 0
Para comparar com uma lista de delimitadores, defina delim como um caractere * e substitua a linha:
com as duas seguintes linhas:
Aproveitar
Exemplo de uso:
fonte
Minha versão:
fonte
Esta é uma função de divisão de cadeia que pode manipular delimitadores de vários caracteres. Observe que, se o delimitador for maior que a cadeia que está sendo dividida,
buffer
estringLengths
será definido como(void *) 0
enumStrings
será definido como0
.Este algoritmo foi testado e funciona. (Isenção de responsabilidade: não foi testado para cadeias não ASCII e assume que o chamador forneceu parâmetros válidos)
Código de amostra:
Bibliotecas:
fonte
buffer
e deixar a alocação para o chamador, processar elementos de tamanho máximo.Minha abordagem é varrer a string e deixar os ponteiros apontarem para todos os caracteres após os deliminadores (e o primeiro caractere), ao mesmo tempo atribuir as aparências do deliminator na string a '\ 0'.
Primeiro faça uma cópia da string original (uma vez que é constante) e, em seguida, obtenha o número de divisões fazendo a varredura, passando-a para o parâmetro ponteiro len . Depois disso, aponte o ponteiro do primeiro resultado para o ponteiro da sequência de cópia e, em seguida, digitalize a sequência de cópias: uma vez que encontre um deliminador, atribua-o a '\ 0', assim a sequência de resultados anterior será encerrada e aponte o próximo ponteiro da sequência de resultados para o próximo ponteiro de caractere.
fonte
Meu código (testado):
Resultado:
fonte
Explodir e implodir - a sequência inicial permanece intacta, alocação dinâmica de memória
Uso:
fonte
Se você estiver disposto a usar uma biblioteca externa, não posso recomendar
bstrlib
suficiente. É preciso uma configuração extra, mas é mais fácil de usar a longo prazo.Por exemplo, divida a sequência abaixo, primeiro crie um
bstring
com abfromcstr()
chamada. (Abstring
é um invólucro em torno de um buffer de char). Em seguida, divida a string em vírgulas, salvando o resultado em astruct bstrList
, que possui camposqty
e uma matrizentry
, que é uma matriz debstring
s.bstrlib
tem muitas outras funções para operarbstring
sFácil como torta ...
fonte
Ainda outra resposta (isso foi movido aqui a partir daqui ):
Tente usar a função strtok:
veja detalhes sobre este tópico aqui ou aqui
O problema aqui é que você deve processar o
words
imediatamente. Se você deseja armazená-lo em uma matriz, é necessário alocá-correct size
lo, pois isso é desconhecido.Então, por exemplo:
Nota : Utilizamos o mesmo loop e função para calcular as contagens (passe um) e para fazer as cópias (passe dois), a fim de evitar problemas de alocação.
Nota 2 : Você pode usar alguma outra implementação do strtok pelos motivos mencionados em postagens separadas.
Você pode usar isso como:
(Eu não testei, por favor, deixe-me saber se não funciona!)
fonte
Dois problemas que cercam essa pergunta são o gerenciamento de memória e a segurança de threads. Como você pode ver nas inúmeras postagens, essa não é uma tarefa fácil de executar perfeitamente em C. Eu desejava uma solução que fosse:
A solução que eu encontrei atende a todos esses critérios. Provavelmente é um pouco mais trabalhoso de configurar do que algumas outras soluções postadas aqui, mas acho que na prática o trabalho extra vale a pena para evitar as armadilhas comuns de outras soluções.
Abaixo está um exemplo de compilação e saída. Observe que no meu exemplo, eu escrevi propositalmente "ABRIL" para que você possa ver como o erro virtual funciona.
Aproveitar!
fonte
Aqui está outra implementação que operará com segurança para tokenizar um literal de string que corresponda ao protótipo solicitado na pergunta, retornando um ponteiro para ponteiro alocado para char (por exemplo
char **
). A cadeia de caracteres do delimitador pode conter vários caracteres e a cadeia de entrada pode conter qualquer número de tokens. Todas as alocações e realocações são tratadas pormalloc
ourealloc
sem POSIXstrdup
.O número inicial de ponteiros alocados é controlado pela
NPTRS
constante e a única limitação é que seja maior que zero. Ochar **
retornado contém uma sentinelaNULL
após o último token semelhante*argv[]
e na forma utilizável porexecv
,execvp
eexecve
.Assim como
strtok()
vários delimitadores seqüenciais são tratados como um único delimitador,"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
eles serão analisados como se apenas um único fosse','
separado"MAY,JUN"
.A função abaixo é comentada em linha e um curta
main()
foi adicionado dividindo os meses. O número inicial de ponteiros alocados foi definido2
para forçar três realocações durante a tokenização da sequência de entrada:Exemplo de uso / saída
Entre em contato se tiver mais alguma dúvida.
fonte