Sebuah komentar ... titik kunci untuk strtok()fungsi keluarga adalah pemahaman static variablesdalam C. yaitu bagaimana mereka berperilaku antara pemanggilan fungsi berturut-turut di mana mereka digunakan. Lihat kode saya di bawah ini
fnisi
Jawaban:
165
Anda dapat menggunakan strtok()fungsi untuk membagi string (dan menentukan pembatas yang akan digunakan). Catatan yang strtok()akan memodifikasi string yang diteruskan ke dalamnya. Jika string asli diperlukan di tempat lain, buat salinannya dan berikan salinannya strtok().
EDIT:
Contoh (perhatikan itu tidak menangani pembatas berturut-turut, "JAN ,,, FEB, MAR" misalnya):
#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;}
Hai! yang strtokditandai sebagai usang oleh strsep(3)di halaman manual.
osgx
4
Karena ini mungkin pertanyaan / jawaban kanonik pada Stack Overflow untuk ini, bukankah ada beberapa peringatan sehubungan dengan multi-threading menggunakan strtok?
Peter Mortensen
3
@osgx Menurut halaman itu, strsepadalah pengganti untuk strtok, tetapi strtoklebih disukai untuk portabilitas. Jadi, kecuali Anda membutuhkan dukungan untuk bidang kosong atau memecah beberapa string sekaligus, strtokadalah pilihan yang lebih baik.
4
@ Dojo: Itu mengingatnya; itulah salah satu alasannya bermasalah. Akan lebih baik untuk menggunakan strtok_s()(Microsoft, C11 Annex K, opsional) atau strtok_r()(POSIX) daripada biasa strtok(). Dataran strtok()itu jahat dalam fungsi perpustakaan. Tidak ada fungsi yang memanggil fungsi perpustakaan yang dapat digunakan strtok()pada saat itu, dan tidak ada fungsi yang dipanggil oleh fungsi perpustakaan yang dapat dipanggil strtok().
Jonathan Leffler
3
Hanya sebuah catatan yang strtok()tidak aman utas (karena alasan @ JonathanLeffler disebutkan) dan oleh karena itu seluruh fungsi ini tidak aman utas. Jika Anda mencoba menggunakan ini di lingkungan yang beralur, Anda akan mendapatkan hasil yang tidak menentu dan tidak dapat diprediksi. Mengganti strtok()untuk strtok_r()perbaikan masalah ini.
Sean W
70
Saya pikir strsepmasih alat terbaik untuk ini:
while((token = strsep(&str,","))) my_fn(token);
Itu secara harfiah satu baris yang memisahkan sebuah string.
Kurung tambahan adalah elemen gaya untuk menunjukkan bahwa kami sengaja menguji hasil penugasan, bukan operator kesetaraan ==.
Agar pola itu berfungsi, tokendan strkeduanya memiliki tipe char *. Jika Anda mulai dengan string literal, maka Anda ingin membuat salinannya terlebih dahulu:
// 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);
Jika dua pembatas muncul bersamaan str, Anda akan mendapatkan tokennilai yang merupakan string kosong. Nilai strdimodifikasi dalam bahwa setiap pembatas yang ditemui ditimpa dengan byte nol - alasan lain yang baik untuk menyalin string yang diuraikan terlebih dahulu.
Dalam komentar, seseorang menyarankan itu strtoklebih baik daripada strsepkarena strtoklebih portabel. Ubuntu dan Mac OS X miliki strsep; aman untuk menebak bahwa sistem unixy lain juga melakukannya. Windows kekurangan strsep, tetapi memiliki strbrkyang memungkinkan strseppenggantian yang singkat dan manis ini :
Berikut adalah penjelasan yang baik strsepvs strtok. Pro dan kontra dapat dinilai secara subyektif; Namun, saya pikir itu adalah tanda yang strsepdirancang sebagai pengganti strtok.
Lebih tepatnya pada portabilitas: ini bukan POSIX 7 , tetapi BSD yang diturunkan, dan diimplementasikan pada glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Saya baru saja akan bertanya ... Pelle's C memiliki strdup (), tetapi tidak ada strsep ().
rdtsc
1
mengapa tofreeyang gratis dan tidak str?
Sdlion
1
Anda tidak dapat membebaskan strkarena nilainya dapat diubah oleh panggilan ke strsep(). Nilai tofreetitik secara konsisten mengarah ke awal memori yang ingin Anda bebaskan.
Tyler
26
String tokenizer kode ini harus menempatkan Anda ke arah yang benar.
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;}
Bagaimana cara menggunakannya:
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 Programmer bintang tiga :)) Ini terdengar menarik.
Michi
Ketika saya melakukan ini, itu menambah terlalu banyak token terakhir, atau mengalokasikan terlalu banyak memori. Ini adalah outputnya: 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
Contoh ini memiliki banyak kebocoran memori. Bagi siapa pun yang membaca ini, jangan gunakan pendekatan ini. Lebih suka pendekatan strtok atau strsep tokenization.
Jorma Rebane
7
Ini dua sen saya:
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, tiga petunjuk! Saya sudah takut menggunakannya lol itu hanya saya, saya tidak begitu baik dengan pointer di c.
Hafiz Temuri
Terima kasih sobat, semua jawaban strtok di atas tidak berfungsi dalam kasus saya bahkan setelah banyak upaya, dan kode Anda bekerja seperti pesona!
hmmftg
4
Dalam contoh di atas, akan ada cara untuk mengembalikan array string yang diakhiri null (seperti yang Anda inginkan) di tempat dalam string. Itu tidak akan memungkinkan untuk meneruskan string literal, karena itu harus dimodifikasi oleh fungsi:
#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;}
Mungkin ada cara yang lebih rapi untuk melakukannya, tetapi Anda mendapatkan idenya.
Fungsi ini mengambil string char * dan membaginya dengan pembatas. Mungkin ada beberapa pemisah dalam satu baris. Perhatikan bahwa fungsi memodifikasi string orignal. Anda harus membuat salinan string asli terlebih dahulu jika Anda ingin yang asli tetap tidak berubah. Fungsi ini tidak menggunakan panggilan fungsi cstring apa pun sehingga mungkin sedikit lebih cepat daripada yang lain. Jika Anda tidak peduli dengan alokasi memori, Anda dapat mengalokasikan sub_strings di bagian atas fungsi dengan ukuran strlen (src_str) / 2 dan (seperti c ++ "versi" yang disebutkan) melewati bagian bawah fungsi. Jika Anda melakukan ini, fungsi dikurangi menjadi O (N), tetapi cara memori yang dioptimalkan di bawah ini adalah O (2N).
Fungsi:
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;}
Di bawah ini adalah strtok()implementasi saya dari perpustakaan zString .
zstring_strtok()berbeda dari perpustakaan standar strtok()dalam cara memperlakukan pembatas berurutan.
Lihat saja kode di bawah ini, yakin bahwa Anda akan mendapatkan ide tentang cara kerjanya (saya mencoba menggunakan sebanyak mungkin komentar)
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;}
Di bawah ini adalah contoh penggunaan ...
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)
Masuk kembali - yaitu, Anda dapat dengan aman menyebutnya dari mana saja dalam satu utas atau lebih
Portable
Menangani banyak pemisah dengan benar
Cepat dan efisien
Penjelasan kode:
Tetapkan struktur tokenuntuk menyimpan alamat dan panjang token
Alokasikan memori yang cukup untuk ini dalam kasus terburuk, yaitu ketika
strseluruhnya terdiri dari separator sehingga ada strlen(str) + 1
token, semuanya string kosong
Pindai strrekaman alamat dan panjang setiap token
Gunakan ini untuk mengalokasikan larik keluaran dengan ukuran yang benar, termasuk ruang ekstra untuk NULLnilai sentinel
Alokasikan, salin, dan tambahkan token menggunakan informasi awal dan panjang - gunakan memcpykarena lebih cepat dari strcpydan kita tahu panjangnya
Bebaskan alamat token dan larik panjang
Kembalikan array token
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;}
Catatanmalloc memeriksa dihapus untuk singkatnya.
Secara umum, saya tidak akan mengembalikan array char *pointer dari fungsi split seperti ini karena menempatkan banyak tanggung jawab pada penelepon untuk membebaskan mereka dengan benar. Sebuah antarmuka Saya lebih suka adalah untuk memungkinkan penelpon untuk lulus fungsi callback dan panggilan ini untuk setiap token, seperti yang telah saya dijelaskan di sini: Split String di C .
Memindai pemisah dua kali mungkin lebih disarankan daripada mengalokasikan array yang berpotensi besar token.
chqrlie
2
Coba gunakan ini.
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;}
Metode yang dioptimalkan ini membuat (atau memperbarui array yang ada) dari pointer dalam hasil * dan mengembalikan jumlah elemen dalam jumlah *.
Gunakan "maks" untuk menunjukkan jumlah maksimum string yang Anda harapkan (ketika Anda menentukan array yang ada atau alasan lain), jika tidak set ke 0
Untuk membandingkan dengan daftar pembatas, tentukan delim sebagai char * dan ganti baris:
if(str[i]==delim){
dengan dua baris berikut:
char*c=delim;while(*c &&*c!=str[i]) c++;if(*c){
Nikmati
#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;}
Ini adalah fungsi pemisahan string yang dapat menangani pembatas multi-karakter. Perhatikan bahwa jika pembatas lebih panjang dari string yang sedang dipisah, maka bufferdan stringLengthsakan diatur ke (void *) 0, dan numStringsakan diatur ke 0.
Algoritma ini telah diuji, dan berfungsi. (Penafian: Ini belum diuji untuk string non-ASCII, dan mengasumsikan bahwa penelepon memberikan parameter yang valid)
Bagaimana saya menyebutnya dari main? Saya tidak tahu apa yang harus dilewatkan ke buffer.
Aymon Fournier
Logika alokasi salah. realloc () mengembalikan pointer baru dan Anda membuang nilai yang dikembalikan. Tidak ada cara yang tepat untuk mengembalikan penunjuk memori baru - prototipe fungsi harus diubah untuk menerima ukuran yang dialokasikan bufferdan membiarkan alokasi untuk pemanggil, memproses elemen ukuran maks.
Alex
@Alex Tetap, sepenuhnya ditulis ulang, dan diuji. Catatan: tidak yakin apakah ini akan berfungsi untuk non-ASCII atau tidak.
Élektra
Sebagai permulaan, ini bukan kode C. Dan mengapa Anda melewatkan pointer dengan referensi aktual di C ++?
Kamiccolo
@ Kamiccolo Maaf, bagaimana tepatnya ini bukan kode C? Juga, mengapa melewati pointer dengan referensi masalah di sini?
Élektra
1
Pendekatan saya adalah memindai string dan membiarkan pointer menunjuk ke setiap karakter setelah pembatas (dan karakter pertama), pada saat yang sama menetapkan tampilan pembatas dalam string ke '\ 0'.
Pertama buat salinan string asli (karena itu konstan), kemudian dapatkan jumlah pemisahan dengan memindainya meneruskannya ke parameter pointer len . Setelah itu, arahkan pointer hasil pertama ke pointer string copy, lalu pindai string copy: sekali menemui pembatas, tetapkan ke '\ 0' sehingga string hasil sebelumnya diakhiri, dan arahkan pointer string hasil berikutnya ke yang berikutnya penunjuk karakter.
Metode ini salah. Saya baru saja menghapus posting ini, tetapi kemudian saya menyadari bahwa mungkin menarik bagi sebagian dari Anda.
metalcrash
1
Kode saya (diuji):
#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);}
Hasil:
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
Jika Anda bersedia menggunakan perpustakaan eksternal, saya tidak bisa merekomendasikan bstrlibcukup. Dibutuhkan sedikit pengaturan ekstra, tetapi lebih mudah digunakan dalam jangka panjang.
Misalnya, pisahkan string di bawah ini, yang pertama buat bstringdengan bfromcstr()panggilan. (A bstringadalah pembungkus di sekitar buffer char). Selanjutnya, pisahkan string pada koma, simpan hasilnya dalam a struct bstrList, yang memiliki bidang qtydan array entry, yang merupakan array dari bstrings.
bstrlibmemiliki banyak fungsi lain untuk beroperasi pada bstrings
Namun jawaban lain (ini dipindahkan ke sini dari sini ):
Coba gunakan fungsi strtok:
lihat detail tentang topik ini di sini atau di sini
Masalahnya di sini adalah Anda harus wordssegera memprosesnya . Jika Anda ingin menyimpannya dalam array Anda harus mengalokasikan correct sizeuntuk itu penyihir tidak diketahui.
Jadi misalnya:
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);}
Catatan : Kami menggunakan loop dan fungsi yang sama untuk menghitung jumlah (lulus satu) dan untuk membuat salinan (lulus dua), untuk menghindari masalah alokasi.
Catatan 2 : Anda dapat menggunakan beberapa implementasi strtok lainnya dengan alasan yang disebutkan dalam posting terpisah.
Anda bisa menggunakan ini seperti:
int main(void){char**array=Split("Hello World!"," ");// Now you have the array// ...// Then free the memoryFree_Array(array);array= NULL;return0;}
(Saya tidak mengujinya, jadi tolong beri tahu saya jika itu tidak berhasil!)
Dua masalah seputar pertanyaan ini adalah manajemen memori dan keamanan utas. Seperti yang dapat Anda lihat dari banyak posting, ini bukan tugas yang mudah untuk diselesaikan dengan mulus di C. Saya menginginkan solusi yaitu:
Aman utas. (strtok bukan thread aman)
Tidak menggunakan malloc atau turunannya (untuk menghindari masalah manajemen memori)
Memeriksa batas array pada bidang individual (untuk menghindari kesalahan segmen pada data yang tidak diketahui)
Bekerja dengan pemisah bidang multi-byte (utf-8)
mengabaikan bidang tambahan dalam input
menyediakan soft error rutin untuk panjang bidang tidak valid
Solusi yang saya temukan memenuhi semua kriteria ini. Mungkin sedikit lebih banyak pekerjaan yang perlu disiapkan daripada beberapa solusi lain yang diposting di sini, tetapi saya pikir dalam praktiknya, pekerjaan tambahan itu sepadan untuk menghindari perangkap umum dari solusi lain.
Di bawah ini adalah contoh kompilasi dan keluaran. Perhatikan bahwa dalam contoh saya, saya sengaja mengeja "APRIL" sehingga Anda dapat melihat cara kerja kesalahan lunak.
$ 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
Berikut ini adalah implementasi lain yang akan beroperasi dengan aman untuk tokenize string-literal yang cocok dengan prototipe yang diminta dalam pertanyaan mengembalikan pointer-to-pointer yang dialokasikan untuk char (misalnya char **). String pembatas dapat berisi beberapa karakter, dan string input dapat berisi sejumlah token. Semua alokasi dan realokasi ditangani oleh mallocatau realloctanpa POSIX strdup.
Jumlah awal dari pointer yang dialokasikan dikendalikan oleh NPTRSkonstanta dan satu-satunya batasan adalah bahwa itu lebih besar dari nol. Yang char **dikembalikan berisi sentinelNULL setelah token terakhir mirip dengan *argv[]dan dalam bentuk yang dapat digunakan oleh execv, execvpdan execve.
Seperti dengan strtok()beberapa pembatas berurutan yang diperlakukan sebagai pembatas tunggal, demikian "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"juga akan diurai seolah-olah hanya satu pemisah yang ','terpisah "MAY,JUN".
Fungsi di bawah ini dikomentari secara in-line dan sebuah pendek main()ditambahkan untuk membagi bulan. Jumlah awal dari pointer yang dialokasikan ditetapkan 2untuk memaksa tiga realokasi selama tokenizing string input:
#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 */}}
Contoh Penggunaan / Output
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
Beri tahu saya jika Anda memiliki pertanyaan lebih lanjut.
strtok
fungsi dari perpustakaan standar untuk mencapai hal yang sama.strtok()
fungsi keluarga adalah pemahamanstatic variables
dalam C. yaitu bagaimana mereka berperilaku antara pemanggilan fungsi berturut-turut di mana mereka digunakan. Lihat kode saya di bawah iniJawaban:
Anda dapat menggunakan
strtok()
fungsi untuk membagi string (dan menentukan pembatas yang akan digunakan). Catatan yangstrtok()
akan memodifikasi string yang diteruskan ke dalamnya. Jika string asli diperlukan di tempat lain, buat salinannya dan berikan salinannyastrtok()
.EDIT:
Contoh (perhatikan itu tidak menangani pembatas berturut-turut, "JAN ,,, FEB, MAR" misalnya):
Keluaran:
sumber
strtok
ditandai sebagai usang olehstrsep(3)
di halaman manual.strsep
adalah pengganti untukstrtok
, tetapistrtok
lebih disukai untuk portabilitas. Jadi, kecuali Anda membutuhkan dukungan untuk bidang kosong atau memecah beberapa string sekaligus,strtok
adalah pilihan yang lebih baik.strtok_s()
(Microsoft, C11 Annex K, opsional) ataustrtok_r()
(POSIX) daripada biasastrtok()
. Dataranstrtok()
itu jahat dalam fungsi perpustakaan. Tidak ada fungsi yang memanggil fungsi perpustakaan yang dapat digunakanstrtok()
pada saat itu, dan tidak ada fungsi yang dipanggil oleh fungsi perpustakaan yang dapat dipanggilstrtok()
.strtok()
tidak aman utas (karena alasan @ JonathanLeffler disebutkan) dan oleh karena itu seluruh fungsi ini tidak aman utas. Jika Anda mencoba menggunakan ini di lingkungan yang beralur, Anda akan mendapatkan hasil yang tidak menentu dan tidak dapat diprediksi. Menggantistrtok()
untukstrtok_r()
perbaikan masalah ini.Saya pikir
strsep
masih alat terbaik untuk ini:Itu secara harfiah satu baris yang memisahkan sebuah string.
Kurung tambahan adalah elemen gaya untuk menunjukkan bahwa kami sengaja menguji hasil penugasan, bukan operator kesetaraan
==
.Agar pola itu berfungsi,
token
danstr
keduanya memiliki tipechar *
. Jika Anda mulai dengan string literal, maka Anda ingin membuat salinannya terlebih dahulu:Jika dua pembatas muncul bersamaan
str
, Anda akan mendapatkantoken
nilai yang merupakan string kosong. Nilaistr
dimodifikasi dalam bahwa setiap pembatas yang ditemui ditimpa dengan byte nol - alasan lain yang baik untuk menyalin string yang diuraikan terlebih dahulu.Dalam komentar, seseorang menyarankan itu
strtok
lebih baik daripadastrsep
karenastrtok
lebih portabel. Ubuntu dan Mac OS X milikistrsep
; aman untuk menebak bahwa sistem unixy lain juga melakukannya. Windows kekuranganstrsep
, tetapi memilikistrbrk
yang memungkinkanstrsep
penggantian yang singkat dan manis ini :Berikut adalah penjelasan yang baik
strsep
vsstrtok
. Pro dan kontra dapat dinilai secara subyektif; Namun, saya pikir itu adalah tanda yangstrsep
dirancang sebagai penggantistrtok
.sumber
tofree
yang gratis dan tidakstr
?str
karena nilainya dapat diubah oleh panggilan kestrsep()
. Nilaitofree
titik secara konsisten mengarah ke awal memori yang ingin Anda bebaskan.String tokenizer kode ini harus menempatkan Anda ke arah yang benar.
sumber
Metode di bawah ini akan melakukan semua pekerjaan (alokasi memori, menghitung panjang) untuk Anda. Informasi dan deskripsi lebih lanjut dapat ditemukan di sini - Implementasi metode Java String.split () untuk membagi string C
Bagaimana cara menggunakannya:
sumber
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.¢
Ini dua sen saya:
Pemakaian:
sumber
Dalam contoh di atas, akan ada cara untuk mengembalikan array string yang diakhiri null (seperti yang Anda inginkan) di tempat dalam string. Itu tidak akan memungkinkan untuk meneruskan string literal, karena itu harus dimodifikasi oleh fungsi:
Mungkin ada cara yang lebih rapi untuk melakukannya, tetapi Anda mendapatkan idenya.
sumber
Fungsi ini mengambil string char * dan membaginya dengan pembatas. Mungkin ada beberapa pemisah dalam satu baris. Perhatikan bahwa fungsi memodifikasi string orignal. Anda harus membuat salinan string asli terlebih dahulu jika Anda ingin yang asli tetap tidak berubah. Fungsi ini tidak menggunakan panggilan fungsi cstring apa pun sehingga mungkin sedikit lebih cepat daripada yang lain. Jika Anda tidak peduli dengan alokasi memori, Anda dapat mengalokasikan sub_strings di bagian atas fungsi dengan ukuran strlen (src_str) / 2 dan (seperti c ++ "versi" yang disebutkan) melewati bagian bawah fungsi. Jika Anda melakukan ini, fungsi dikurangi menjadi O (N), tetapi cara memori yang dioptimalkan di bawah ini adalah O (2N).
Fungsi:
Bagaimana cara menggunakannya:
sumber
sumber
Di bawah ini adalah
strtok()
implementasi saya dari perpustakaan zString .zstring_strtok()
berbeda dari perpustakaan standarstrtok()
dalam cara memperlakukan pembatas berurutan.Lihat saja kode di bawah ini, yakin bahwa Anda akan mendapatkan ide tentang cara kerjanya (saya mencoba menggunakan sebanyak mungkin komentar)
Di bawah ini adalah contoh penggunaan ...
Perpustakaan dapat diunduh dari Github https://github.com/fnoyanisi/zString
sumber
Saya pikir solusi berikut ini ideal:
Penjelasan kode:
token
untuk menyimpan alamat dan panjang tokenstr
seluruhnya terdiri dari separator sehingga adastrlen(str) + 1
token, semuanya string kosongstr
rekaman alamat dan panjang setiap tokenNULL
nilai sentinelmemcpy
karena lebih cepat daristrcpy
dan kita tahu panjangnyaCatatan
malloc
memeriksa dihapus untuk singkatnya.Secara umum, saya tidak akan mengembalikan array
char *
pointer dari fungsi split seperti ini karena menempatkan banyak tanggung jawab pada penelepon untuk membebaskan mereka dengan benar. Sebuah antarmuka Saya lebih suka adalah untuk memungkinkan penelpon untuk lulus fungsi callback dan panggilan ini untuk setiap token, seperti yang telah saya dijelaskan di sini: Split String di C .sumber
token
.Coba gunakan ini.
sumber
Metode yang dioptimalkan ini membuat (atau memperbarui array yang ada) dari pointer dalam hasil * dan mengembalikan jumlah elemen dalam jumlah *.
Gunakan "maks" untuk menunjukkan jumlah maksimum string yang Anda harapkan (ketika Anda menentukan array yang ada atau alasan lain), jika tidak set ke 0
Untuk membandingkan dengan daftar pembatas, tentukan delim sebagai char * dan ganti baris:
dengan dua baris berikut:
Nikmati
Contoh penggunaan:
sumber
Versi saya:
sumber
Ini adalah fungsi pemisahan string yang dapat menangani pembatas multi-karakter. Perhatikan bahwa jika pembatas lebih panjang dari string yang sedang dipisah, maka
buffer
danstringLengths
akan diatur ke(void *) 0
, dannumStrings
akan diatur ke0
.Algoritma ini telah diuji, dan berfungsi. (Penafian: Ini belum diuji untuk string non-ASCII, dan mengasumsikan bahwa penelepon memberikan parameter yang valid)
Kode sampel:
Perpustakaan:
sumber
buffer
dan membiarkan alokasi untuk pemanggil, memproses elemen ukuran maks.Pendekatan saya adalah memindai string dan membiarkan pointer menunjuk ke setiap karakter setelah pembatas (dan karakter pertama), pada saat yang sama menetapkan tampilan pembatas dalam string ke '\ 0'.
Pertama buat salinan string asli (karena itu konstan), kemudian dapatkan jumlah pemisahan dengan memindainya meneruskannya ke parameter pointer len . Setelah itu, arahkan pointer hasil pertama ke pointer string copy, lalu pindai string copy: sekali menemui pembatas, tetapkan ke '\ 0' sehingga string hasil sebelumnya diakhiri, dan arahkan pointer string hasil berikutnya ke yang berikutnya penunjuk karakter.
sumber
Kode saya (diuji):
Hasil:
sumber
Explode & implode - string awal tetap utuh, alokasi memori dinamis
Pemakaian:
sumber
Jika Anda bersedia menggunakan perpustakaan eksternal, saya tidak bisa merekomendasikan
bstrlib
cukup. Dibutuhkan sedikit pengaturan ekstra, tetapi lebih mudah digunakan dalam jangka panjang.Misalnya, pisahkan string di bawah ini, yang pertama buat
bstring
denganbfromcstr()
panggilan. (Abstring
adalah pembungkus di sekitar buffer char). Selanjutnya, pisahkan string pada koma, simpan hasilnya dalam astruct bstrList
, yang memiliki bidangqty
dan arrayentry
, yang merupakan array daribstring
s.bstrlib
memiliki banyak fungsi lain untuk beroperasi padabstring
sMudah seperti pai ...
sumber
Namun jawaban lain (ini dipindahkan ke sini dari sini ):
Coba gunakan fungsi strtok:
lihat detail tentang topik ini di sini atau di sini
Masalahnya di sini adalah Anda harus
words
segera memprosesnya . Jika Anda ingin menyimpannya dalam array Anda harus mengalokasikancorrect size
untuk itu penyihir tidak diketahui.Jadi misalnya:
Catatan : Kami menggunakan loop dan fungsi yang sama untuk menghitung jumlah (lulus satu) dan untuk membuat salinan (lulus dua), untuk menghindari masalah alokasi.
Catatan 2 : Anda dapat menggunakan beberapa implementasi strtok lainnya dengan alasan yang disebutkan dalam posting terpisah.
Anda bisa menggunakan ini seperti:
(Saya tidak mengujinya, jadi tolong beri tahu saya jika itu tidak berhasil!)
sumber
Dua masalah seputar pertanyaan ini adalah manajemen memori dan keamanan utas. Seperti yang dapat Anda lihat dari banyak posting, ini bukan tugas yang mudah untuk diselesaikan dengan mulus di C. Saya menginginkan solusi yaitu:
Solusi yang saya temukan memenuhi semua kriteria ini. Mungkin sedikit lebih banyak pekerjaan yang perlu disiapkan daripada beberapa solusi lain yang diposting di sini, tetapi saya pikir dalam praktiknya, pekerjaan tambahan itu sepadan untuk menghindari perangkap umum dari solusi lain.
Di bawah ini adalah contoh kompilasi dan keluaran. Perhatikan bahwa dalam contoh saya, saya sengaja mengeja "APRIL" sehingga Anda dapat melihat cara kerja kesalahan lunak.
Nikmati!
sumber
Berikut ini adalah implementasi lain yang akan beroperasi dengan aman untuk tokenize string-literal yang cocok dengan prototipe yang diminta dalam pertanyaan mengembalikan pointer-to-pointer yang dialokasikan untuk char (misalnya
char **
). String pembatas dapat berisi beberapa karakter, dan string input dapat berisi sejumlah token. Semua alokasi dan realokasi ditangani olehmalloc
ataurealloc
tanpa POSIXstrdup
.Jumlah awal dari pointer yang dialokasikan dikendalikan oleh
NPTRS
konstanta dan satu-satunya batasan adalah bahwa itu lebih besar dari nol. Yangchar **
dikembalikan berisi sentinelNULL
setelah token terakhir mirip dengan*argv[]
dan dalam bentuk yang dapat digunakan olehexecv
,execvp
danexecve
.Seperti dengan
strtok()
beberapa pembatas berurutan yang diperlakukan sebagai pembatas tunggal, demikian"JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
juga akan diurai seolah-olah hanya satu pemisah yang','
terpisah"MAY,JUN"
.Fungsi di bawah ini dikomentari secara in-line dan sebuah pendek
main()
ditambahkan untuk membagi bulan. Jumlah awal dari pointer yang dialokasikan ditetapkan2
untuk memaksa tiga realokasi selama tokenizing string input:Contoh Penggunaan / Output
Beri tahu saya jika Anda memiliki pertanyaan lebih lanjut.
sumber