Комментарий ... ключевой момент для strtok()семейной функции - это понимание static variablesна языке C. то есть как они ведут себя между последовательным вызовом функции, в котором они используются. Смотрите мой код ниже
fnisi
Ответы:
165
Вы можете использовать strtok()функцию, чтобы разделить строку (и указать используемый разделитель). Обратите внимание, что strtok()изменит переданную строку. Если оригинальная строка требуется в другом месте, сделайте ее копию и передайте копию strtok().
РЕДАКТИРОВАТЬ:
Пример (обратите внимание, что он не обрабатывает последовательные разделители, например, «JAN ,,, FEB, MAR»):
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<assert.h>char** str_split(char* a_str,constchar a_delim){char** result =0;size_t count =0;char* tmp = a_str;char* last_comma =0;char delim[2];
delim[0]= a_delim;
delim[1]=0;/* Count how many elements will be extracted. */while(*tmp){if(a_delim ==*tmp){
count++;
last_comma = tmp;}
tmp++;}/* Add space for trailing token. */
count += last_comma <(a_str + strlen(a_str)-1);/* Add space for terminating null string so caller
knows where the list of returned strings ends. */
count++;
result = malloc(sizeof(char*)* count);if(result){size_t idx =0;char* token = strtok(a_str, delim);while(token){
assert(idx < count);*(result + idx++)= strdup(token);
token = strtok(0, delim);}
assert(idx == count -1);*(result + idx)=0;}return result;}int main(){char months[]="JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";char** tokens;
printf("months=[%s]\n\n", months);
tokens = str_split(months,',');if(tokens){int i;for(i =0;*(tokens + i); i++){
printf("month=[%s]\n",*(tokens + i));
free(*(tokens + i));}
printf("\n");
free(tokens);}return0;}
Здравствуй! strtokотмечен как устарела, strsep(3)в человеке странице.
osgx
4
Поскольку это может быть каноническим вопросом / ответом по переполнению стека для этого, нет ли некоторых предостережений в отношении многопоточности с использованием strtok?
Питер Мортенсен
3
@osgx Согласно этой странице, strsepявляется заменой strtok, но strtokпредпочтительнее для мобильности. Поэтому, если вам не нужна поддержка пустых полей или разбиение нескольких строк одновременно, strtokэто лучший выбор.
4
@ Dojo: Он помнит это; это одна из причин, по которой это проблематично. Было бы лучше использовать strtok_s()(Microsoft, C11 Приложение K, необязательно) или strtok_r()(POSIX), чем обычный strtok(). Равнина strtok()это зло в библиотечной функции. Никакая функция, вызывающая библиотечную функцию, не может использоваться strtok()в данный момент, и никакая функция, вызываемая библиотечной функцией, не может вызывать strtok().
Джонатан Леффлер
3
Просто примечание, strtok()которое не является поточно-ориентированным (по причинам, упомянутым @JonathanLeffler), и, следовательно, вся эта функция не является поточно-ориентированной. Если вы попытаетесь использовать это в поточной среде, вы получите ошибочные и непредсказуемые результаты. Замена strtok()для strtok_r()исправления этой проблемы.
Шон Вт
70
Я думаю, что strsepвсе еще лучший инструмент для этого:
while((token = strsep(&str,","))) my_fn(token);
Это буквально одна строка, которая разбивает строку.
Дополнительные скобки - это стилистический элемент, указывающий на то, что мы намеренно тестируем результат присваивания, а не оператор равенства ==.
Чтобы этот шаблон работал, tokenи strоба имеют тип char *. Если вы начали со строкового литерала, то сначала вам нужно сделать его копию:
// 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);
Если два разделителя появляются вместе str, вы получите tokenзначение, которое является пустой строкой. Значение strмодифицируется тем, что каждый встреченный разделитель перезаписывается нулевым байтом - еще одна веская причина для копирования строки, которая анализируется первой.
В комментарии кто-то предположил, что strtokэто лучше, чем strsepпотому, что strtokон более портативный. Ubuntu и Mac OS X имеют strsep; можно с уверенностью догадаться, что и другие Unixy системы делают то же самое. Windows не хватает strsep, но она имеет, strbrkчто позволяет эту короткую и приятную strsepзамену:
Вот хорошее объяснение strsepпротив strtok. О плюсах и минусах можно судить субъективно; Тем не менее, я думаю, что это показательный знак, который strsepбыл разработан в качестве замены strtok.
Точнее о переносимости: это не POSIX 7 , а BSD-производная, реализованная на glibc .
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
Я как раз собирался спросить ... В C Пелле есть strdup (), но нет strsep ().
rdtsc
1
почему tofreeтот бесплатный и нет str?
Sdlion
1
Вы не можете освободить, strпотому что его значение может быть изменено с помощью звонков strsep(). Значение tofreeпостоянно указывает на начало памяти, которую вы хотите освободить.
Тайлер
26
Строковый токенизатор, этот код должен направить вас в правильном направлении.
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;}
Как это использовать:
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;}
Да, трехзвездочный программист :)) Звучит интересно.
Мичи
Когда я делаю это, он либо добавляет слишком много к последнему токену, либо выделяет ему слишком много памяти. Это вывод: 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
Этот пример имеет несколько утечек памяти. Для тех, кто читает это, не используйте этот подход. Вместо этого предпочтите подходы strtok или strsep tokenization.
Йорма Ребане
7
Вот мои два цента:
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;}
о бой, три указателя! Я уже боюсь его использовать, только его я, я не очень хорош с указателями в c.
Хафиз Темури
Спасибо, мужик, все вышеперечисленные ответы strtok не сработали в моем случае даже после многих усилий, и твой код работает как шарм!
Hmmftg
4
В приведенном выше примере был бы способ вернуть массив строк с нулевым символом в конце (как вы хотите) на месте в строке. Это не позволило бы передать буквальную строку, поскольку это должно было бы быть изменено функцией:
#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;}
Вероятно, есть более аккуратный способ сделать это, но вы поняли идею.
Эта функция берет строку char * и разбивает ее по разделителю. В строке может быть несколько разделителей. Обратите внимание, что функция изменяет оригинальную строку. Сначала вы должны сделать копию оригинальной строки, если вам нужно, чтобы оригинал оставался неизменным. Эта функция не использует вызовы функций cstring, поэтому она может быть немного быстрее, чем другие. Если вас не волнует распределение памяти, вы можете выделить sub_strings в верхней части функции с размером strlen (src_str) / 2 и (как упоминалось в «c ++» версии «c») пропустить нижнюю половину функции. Если вы сделаете это, функция уменьшится до O (N), но показанный ниже способ оптимизации памяти будет O (2N).
Функция:
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;}
Ниже моя strtok()реализация из библиотеки zString .
zstring_strtok()отличается от стандартной библиотеки strtok()тем, как она обрабатывает последовательные разделители.
Просто взгляните на приведенный ниже код и убедитесь, что вы получите представление о том, как он работает (я постарался использовать как можно больше комментариев)
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;}
Ниже приведен пример использования ...
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)
Повторный вход - то есть вы можете безопасно позвонить из любого места в одном или нескольких потоках
портативный
Обрабатывает несколько разделителей правильно
Быстро и эффективно
Пояснение к коду:
Определите структуру tokenдля хранения адреса и длины токенов
Выделите достаточно памяти для них в худшем случае, когда
strон полностью состоит из разделителей, поэтому есть strlen(str) + 1
токены, все они являются пустыми строками
Сканирование strзаписи адреса и длины каждого токена
Используйте это для выделения выходного массива правильного размера, включая дополнительное пространство для NULLзначения часового
Выделяйте, копируйте и добавляйте токены, используя информацию о начале и длине - используйте memcpyэто быстрее, чем strcpyмы знаем длины
Освободить адресный токен и массив длины
Вернуть массив токенов
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;}
Примечаниеmalloc проверка опущена для краткости.
В общем, я бы не стал возвращать массив char *указателей из функции split, подобной этой, так как это накладывает большую ответственность на вызывающего, чтобы освободить их правильно. Интерфейс Я предпочитаю , чтобы позволить абоненту передать функцию обратного вызова и называть это для каждых маркеров, как я описал здесь: разбить строку в C .
Сканирование на разделители дважды, вероятно, более целесообразно, чем выделение потенциально большого массива token.
Chqrlie
2
Попробуйте использовать это.
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;}
Этот оптимизированный метод создает (или обновляет существующий) массив указателей в * result и возвращает количество элементов в * count.
Используйте «max», чтобы указать максимальное количество ожидаемых строк (когда вы указываете существующий массив или любую другую причину), иначе установите его на 0
Чтобы сравнить со списком разделителей, определите delim как char * и замените строку:
if(str[i]==delim){
с двумя следующими строками:
char*c=delim;while(*c &&*c!=str[i]) c++;if(*c){
наслаждаться
#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;}
Это функция разделения строк, которая может обрабатывать многосимвольные разделители. Обратите внимание, что если разделитель длиннее разделяемой строки, то bufferи stringLengthsбудет установлено значение (void *) 0, и numStringsбудет установлено значение0 .
Этот алгоритм был проверен и работает. (Отказ от ответственности: он не был протестирован для строк, не относящихся к ASCII, и предполагает, что вызывающая сторона задала допустимые параметры)
Как мне позвонить с основного? Я не знаю, что передать в буфер.
Аймон Фурнье
Логика распределения неверна. realloc () возвращает новый указатель, и вы отбрасываете возвращенное значение. Нет правильного способа вернуть новый указатель памяти - прототип функции должен быть изменен, чтобы принимать размер выделенного bufferи оставлять выделение вызывающей стороне , обрабатывать элементы максимального размера.
Alex
@Alex Исправлено, полностью переписано и протестировано. Примечание: не уверен, будет ли это работать для не ASCII или нет.
Электра
Для начала, это не код Си. И почему вы передаете указатели по фактической ссылке в C ++?
Kamiccolo
@ Kamiccolo Извините, как именно это не код C? Кроме того, почему передача указателей по ссылке здесь проблема?
Электра
1
Мой подход состоит в том, чтобы сканировать строку и позволить указателям указывать на каждый символ после разделителя (и первого символа), в то же время присваивая появления разделителя в строке «\ 0».
Сначала сделайте копию исходной строки (так как она постоянна), затем найдите количество разбиений, отсканировав его, передав его параметру указателя len . После этого наведите первый указатель результата на указатель строки копирования, затем просканируйте строку копирования: как только встретите разделитель, назначьте его на \ 0, таким образом, предыдущая строка результата завершается, и укажите указатель следующей строки результата на следующий символьный указатель
Этот метод неверен. Я только что удалил этот пост, но потом понял, что это может быть интересно для некоторых из вас.
metalcrash
1
Мой код (проверено):
#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);}
Результат:
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
Если вы хотите использовать внешнюю библиотеку, я не могу рекомендовать bstrlib достаточно. Это требует немного дополнительной настройки, но легче использовать в долгосрочной перспективе.
Например, разделить строку ниже, один сначала создает bstringс помощью bfromcstr()вызова. (A bstringявляется оберткой вокруг буфера символов). Затем разделите строку на запятые, сохранив результат в a struct bstrList, который имеет поля qtyи массивentry , который является массивом bstrings.
bstrlib имеет много других функций для работы bstring с
Проблема здесь в том, что вы должны обработать wordsнемедленно. Если вы хотите сохранить его в массиве, вы должны выделитьcorrect size ведь он неизвестен.
Так, например:
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);}
Примечание . Мы используем один и тот же цикл и функцию для вычисления количества (один проход) и для создания копий (второй этап), чтобы избежать проблем с выделением.
Примечание 2 : Вы можете использовать некоторые другие реализации strtok, причины которых указаны в отдельных постах.
Вы можете использовать это как:
int main(void){char**array=Split("Hello World!"," ");// Now you have the array// ...// Then free the memoryFree_Array(array);array= NULL;return0;}
(Я не тестировал его, поэтому, пожалуйста, дайте мне знать, если он не работает!)
Этот вопрос связан с двумя проблемами: управлением памятью и безопасностью потоков. Как вы можете видеть из многочисленных постов, эту задачу нелегко решить в C. Я хотел найти решение, которое:
Поток безопасно. (strtok не является потокобезопасным)
Не использует malloc или его производные (чтобы избежать проблем с управлением памятью)
Проверяет границы массивов для отдельных полей (чтобы избежать ошибок сегментов в неизвестных данных)
Работает с многобайтовыми разделителями полей (utf-8)
игнорирует дополнительные поля во входных данных
предоставляет процедуру мягкой ошибки для недопустимых длин полей
Решение, которое я нашел, отвечает всем этим критериям. Это, вероятно, немного больше работы по настройке, чем некоторые другие решения, опубликованные здесь, но я думаю, что на практике дополнительная работа стоит того, чтобы избежать распространенных ошибок других решений.
Ниже приведен пример компиляции и вывода. Обратите внимание, что в моем примере я специально обозначил «АПРЕЛЬ», чтобы вы могли увидеть, как работает мягкая ошибка.
$ 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
Вот еще одна реализация, которая будет безопасно работать для токенизации строкового литерала, соответствующего прототипу, запрошенному в вопросе, возвращающему выделенный указатель на указатель на char (например char **). Строка разделителя может содержать несколько символов, а входная строка может содержать любое количество токенов. Все распределения и перераспределения обрабатываются POSIX mallocили reallocбез него strdup.
Начальное количество выделенных указателей контролируется NPTRSконстантой, и единственным ограничением является то, что оно будет больше нуля. char **Возвращаются содержит часовойNULL после последних маркера аналогичны *argv[]и в форме , используемой execv, execvpиexecve .
Как и в случае с strtok()несколькими последовательными разделителями, они рассматриваются как один разделитель, поэтому "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"анализируются так, как если бы ','разделялся только один элемент "MAY,JUN".
Приведенная ниже функция прокомментирована в виде строки и добавлено короткое main()разделение по месяцам. Первоначальное количество выделенных указателей было установлено на 2три перераспределения во время токенизации входной строки:
#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 */}}
Пример использования / Вывод
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
Дайте мне знать, если у вас есть дополнительные вопросы.
strtok
функцию из стандартной библиотеки, чтобы добиться того же.strtok()
семейной функции - это пониманиеstatic variables
на языке C. то есть как они ведут себя между последовательным вызовом функции, в котором они используются. Смотрите мой код нижеОтветы:
Вы можете использовать
strtok()
функцию, чтобы разделить строку (и указать используемый разделитель). Обратите внимание, чтоstrtok()
изменит переданную строку. Если оригинальная строка требуется в другом месте, сделайте ее копию и передайте копиюstrtok()
.РЕДАКТИРОВАТЬ:
Пример (обратите внимание, что он не обрабатывает последовательные разделители, например, «JAN ,,, FEB, MAR»):
Вывод:
источник
strtok
отмечен как устарела,strsep(3)
в человеке странице.strsep
является заменойstrtok
, ноstrtok
предпочтительнее для мобильности. Поэтому, если вам не нужна поддержка пустых полей или разбиение нескольких строк одновременно,strtok
это лучший выбор.strtok_s()
(Microsoft, C11 Приложение K, необязательно) илиstrtok_r()
(POSIX), чем обычныйstrtok()
. Равнинаstrtok()
это зло в библиотечной функции. Никакая функция, вызывающая библиотечную функцию, не может использоватьсяstrtok()
в данный момент, и никакая функция, вызываемая библиотечной функцией, не может вызыватьstrtok()
.strtok()
которое не является поточно-ориентированным (по причинам, упомянутым @JonathanLeffler), и, следовательно, вся эта функция не является поточно-ориентированной. Если вы попытаетесь использовать это в поточной среде, вы получите ошибочные и непредсказуемые результаты. Заменаstrtok()
дляstrtok_r()
исправления этой проблемы.Я думаю, что
strsep
все еще лучший инструмент для этого:Это буквально одна строка, которая разбивает строку.
Дополнительные скобки - это стилистический элемент, указывающий на то, что мы намеренно тестируем результат присваивания, а не оператор равенства
==
.Чтобы этот шаблон работал,
token
иstr
оба имеют типchar *
. Если вы начали со строкового литерала, то сначала вам нужно сделать его копию:Если два разделителя появляются вместе
str
, вы получитеtoken
значение, которое является пустой строкой. Значениеstr
модифицируется тем, что каждый встреченный разделитель перезаписывается нулевым байтом - еще одна веская причина для копирования строки, которая анализируется первой.В комментарии кто-то предположил, что
strtok
это лучше, чемstrsep
потому, чтоstrtok
он более портативный. Ubuntu и Mac OS X имеютstrsep
; можно с уверенностью догадаться, что и другие Unixy системы делают то же самое. Windows не хватаетstrsep
, но она имеет,strbrk
что позволяет эту короткую и приятнуюstrsep
замену:Вот хорошее объяснение
strsep
противstrtok
. О плюсах и минусах можно судить субъективно; Тем не менее, я думаю, что это показательный знак, которыйstrsep
был разработан в качестве заменыstrtok
.источник
tofree
тот бесплатный и нетstr
?str
потому что его значение может быть изменено с помощью звонковstrsep()
. Значениеtofree
постоянно указывает на начало памяти, которую вы хотите освободить.Строковый токенизатор, этот код должен направить вас в правильном направлении.
источник
Метод ниже сделает всю работу (выделение памяти, подсчет длины) за вас. Дополнительную информацию и описание можно найти здесь - Реализация метода Java String.split () для разделения строки C
Как это использовать:
источник
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.¢
Вот мои два цента:
Использование:
источник
В приведенном выше примере был бы способ вернуть массив строк с нулевым символом в конце (как вы хотите) на месте в строке. Это не позволило бы передать буквальную строку, поскольку это должно было бы быть изменено функцией:
Вероятно, есть более аккуратный способ сделать это, но вы поняли идею.
источник
Эта функция берет строку char * и разбивает ее по разделителю. В строке может быть несколько разделителей. Обратите внимание, что функция изменяет оригинальную строку. Сначала вы должны сделать копию оригинальной строки, если вам нужно, чтобы оригинал оставался неизменным. Эта функция не использует вызовы функций cstring, поэтому она может быть немного быстрее, чем другие. Если вас не волнует распределение памяти, вы можете выделить sub_strings в верхней части функции с размером strlen (src_str) / 2 и (как упоминалось в «c ++» версии «c») пропустить нижнюю половину функции. Если вы сделаете это, функция уменьшится до O (N), но показанный ниже способ оптимизации памяти будет O (2N).
Функция:
Как это использовать:
источник
источник
Ниже моя
strtok()
реализация из библиотеки zString .zstring_strtok()
отличается от стандартной библиотекиstrtok()
тем, как она обрабатывает последовательные разделители.Просто взгляните на приведенный ниже код и убедитесь, что вы получите представление о том, как он работает (я постарался использовать как можно больше комментариев)
Ниже приведен пример использования ...
Библиотеку можно скачать с Github https://github.com/fnoyanisi/zString
источник
Я думаю, что следующее решение идеально:
Пояснение к коду:
token
для хранения адреса и длины токеновstr
он полностью состоит из разделителей, поэтому естьstrlen(str) + 1
токены, все они являются пустыми строкамиstr
записи адреса и длины каждого токенаNULL
значения часовогоmemcpy
это быстрее, чемstrcpy
мы знаем длиныПримечание
malloc
проверка опущена для краткости.В общем, я бы не стал возвращать массив
char *
указателей из функции split, подобной этой, так как это накладывает большую ответственность на вызывающего, чтобы освободить их правильно. Интерфейс Я предпочитаю , чтобы позволить абоненту передать функцию обратного вызова и называть это для каждых маркеров, как я описал здесь: разбить строку в C .источник
token
.Попробуйте использовать это.
источник
Этот оптимизированный метод создает (или обновляет существующий) массив указателей в * result и возвращает количество элементов в * count.
Используйте «max», чтобы указать максимальное количество ожидаемых строк (когда вы указываете существующий массив или любую другую причину), иначе установите его на 0
Чтобы сравнить со списком разделителей, определите delim как char * и замените строку:
с двумя следующими строками:
наслаждаться
Пример использования:
источник
Моя версия:
источник
Это функция разделения строк, которая может обрабатывать многосимвольные разделители. Обратите внимание, что если разделитель длиннее разделяемой строки, то
buffer
иstringLengths
будет установлено значение(void *) 0
, иnumStrings
будет установлено значение0
.Этот алгоритм был проверен и работает. (Отказ от ответственности: он не был протестирован для строк, не относящихся к ASCII, и предполагает, что вызывающая сторона задала допустимые параметры)
Образец кода:
Библиотеки:
источник
buffer
и оставлять выделение вызывающей стороне , обрабатывать элементы максимального размера.Мой подход состоит в том, чтобы сканировать строку и позволить указателям указывать на каждый символ после разделителя (и первого символа), в то же время присваивая появления разделителя в строке «\ 0».
Сначала сделайте копию исходной строки (так как она постоянна), затем найдите количество разбиений, отсканировав его, передав его параметру указателя len . После этого наведите первый указатель результата на указатель строки копирования, затем просканируйте строку копирования: как только встретите разделитель, назначьте его на \ 0, таким образом, предыдущая строка результата завершается, и укажите указатель следующей строки результата на следующий символьный указатель
источник
Мой код (проверено):
Результат:
источник
Explode & implode - исходная строка остается неизменной, динамическое распределение памяти
Использование:
источник
Если вы хотите использовать внешнюю библиотеку, я не могу рекомендовать
bstrlib
достаточно. Это требует немного дополнительной настройки, но легче использовать в долгосрочной перспективе.Например, разделить строку ниже, один сначала создает
bstring
с помощьюbfromcstr()
вызова. (Abstring
является оберткой вокруг буфера символов). Затем разделите строку на запятые, сохранив результат в astruct bstrList
, который имеет поляqty
и массивentry
, который является массивомbstring
s.bstrlib
имеет много других функций для работыbstring
сПроще простого...
источник
Еще один ответ (это было перенесено сюда отсюда ):
Попробуйте использовать функцию strtok:
подробности по этой теме смотрите здесь или здесь
Проблема здесь в том, что вы должны обработать
words
немедленно. Если вы хотите сохранить его в массиве, вы должны выделитьcorrect size
ведь он неизвестен.Так, например:
Примечание . Мы используем один и тот же цикл и функцию для вычисления количества (один проход) и для создания копий (второй этап), чтобы избежать проблем с выделением.
Примечание 2 : Вы можете использовать некоторые другие реализации strtok, причины которых указаны в отдельных постах.
Вы можете использовать это как:
(Я не тестировал его, поэтому, пожалуйста, дайте мне знать, если он не работает!)
источник
Этот вопрос связан с двумя проблемами: управлением памятью и безопасностью потоков. Как вы можете видеть из многочисленных постов, эту задачу нелегко решить в C. Я хотел найти решение, которое:
Решение, которое я нашел, отвечает всем этим критериям. Это, вероятно, немного больше работы по настройке, чем некоторые другие решения, опубликованные здесь, но я думаю, что на практике дополнительная работа стоит того, чтобы избежать распространенных ошибок других решений.
Ниже приведен пример компиляции и вывода. Обратите внимание, что в моем примере я специально обозначил «АПРЕЛЬ», чтобы вы могли увидеть, как работает мягкая ошибка.
Наслаждайтесь!
источник
Вот еще одна реализация, которая будет безопасно работать для токенизации строкового литерала, соответствующего прототипу, запрошенному в вопросе, возвращающему выделенный указатель на указатель на char (например
char **
). Строка разделителя может содержать несколько символов, а входная строка может содержать любое количество токенов. Все распределения и перераспределения обрабатываются POSIXmalloc
илиrealloc
без негоstrdup
.Начальное количество выделенных указателей контролируется
NPTRS
константой, и единственным ограничением является то, что оно будет больше нуля.char **
Возвращаются содержит часовойNULL
после последних маркера аналогичны*argv[]
и в форме , используемойexecv
,execvp
иexecve
.Как и в случае с
strtok()
несколькими последовательными разделителями, они рассматриваются как один разделитель, поэтому"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
анализируются так, как если бы','
разделялся только один элемент"MAY,JUN"
.Приведенная ниже функция прокомментирована в виде строки и добавлено короткое
main()
разделение по месяцам. Первоначальное количество выделенных указателей было установлено на2
три перераспределения во время токенизации входной строки:Пример использования / Вывод
Дайте мне знать, если у вас есть дополнительные вопросы.
источник