C语言必知必会-strtok赞歌
strtok的贊歌
標記解析(Tokenizing)是最簡單也是最常見的解析問題,也就是根據分隔符把一個字符串分割為幾個部分。這個定義覆蓋了所有這種類型的任務。根據空白分隔符(例如" \t\n\r"之一)分割單詞。假設有個像"/usr/include:/usr/local/include:."這樣的路徑,在冒號處將其分開,形成單獨的目錄。根據一個簡單的換行分隔符"\n"把一個字符串分割為不同的行。可以使用一個配置文件,包含value = key格式的行,在這種情況下分隔符就是"="。在數據文件中以逗號分隔的值當然是以逗號為分隔符。我們可以采取兩個層次的分割來分別進行處理。例如讀取一個完整的配置文件,首先根據換行符進行分割,然后在每行根據=進行分割。
標記解析出現得非常頻繁,有一個C標準庫函數strtok(字符串標記化)專門用于完成這個任務。它是那些雖然小巧但能夠干凈利落地完成任務的函數之一。strtok的基本工作方式是對我們所輸入的字符串進行迭代,直到遇到第1個分隔符,然后用一個'\0'覆蓋這個分隔符。現在這個輸入字符串的第1部分已經是個表示第1個標記的合法字符串,strtok返回一個指向這個子字符串頭部的指針供我們使用。這個函數在內部保存源字符串的信息,因此當我們再次調用strtok時,它可以搜索到下一個標記的尾部,將之設置為‘\0’,并以一個合法的字符串的形式返回這個標記。每個子字符串的指針都指向原字符串內的位置,因此標記化操作所執行的數據寫入是極少的(只是那些\0),并且不會進行復制操作。它
的直接影響是輸入字符串被損壞,由于子字符串是指向源字符串的指針,因此無法釋放這個輸入字符串,必須等到完成了所有子字符串的使用以后,你才能去釋放原始的字符串。(或者,可以使用strdup復制出這些子字符串。)
strtok函數通過一個靜態的內部指針保存我們第1次輸入的字符串的剩余部分,意味著它被限制為一次只能標記化一個字符串(通過一組分隔符),無法在涉及線程的情況下使用。因此目前strtok已經不推薦使用了。推薦使用strtok_r或strtok_s,它們是strtok的線程友好版本。POSIX標準提供了strtok_r,C11標準提供了strtok_s。這兩個函數的用法都有點笨拙,因為第1次調用與后續的調用在形式上是不一樣的。
第1次調用函數時,把需要解析的字符串作為它的第1個參數。在后續的調用中,把第1個參數設置為NULL。最后一個參數是處理過的字符串,我們并不需要在第1次使用時對它進行初始化,在后續的調用中,它將保存到目前為止所解析的字符串。
下面是個行計數器(事實上是非空白行的計數器,參見后面的警告)。在腳本語言中,標記解析往往只需要一行程序,但是以下已經是strtok_r最精簡的用法了。注意用于發送源字符串的if ? then:else只出現在第1次呼叫時傳入字符串。
C11標準的strtok_s函數的工作方式就像strtok_r一樣,但接受一個額外的參數(第2個),它提供了輸入字符串的長度,并在后續的調用過程中不斷縮短,表示每次調用時剩余字符串的長度。如果輸入字符串并不是以\0分隔的,這個額外的參數就非常實用。我們可以用下面的代碼重新完成前面那個例子:
#include <string.h> //strtok_s //first use size_t len = strlen(instring); txt = strtok_s(instring, &len, delimiter, &scratch); //subsequent use: txt = strtok_s(NULL, &len, delimiter, &scratch);如果你的系統裝安裝的有glibc參照下方的實現
string_utilities.h
#include <string.h> #define _GNU_SOURCE //asks stdio.h to include asprintf #include <stdio.h>//Safe asprintf macro #define Sasprintf(write_to, ...) { \char *tmp_string_for_extend = write_to; \//asprintf每次調用都會新申請一段內存,所以要講老的指針釋放掉asprintf(&(write_to), __VA_ARGS__); \free(tmp_string_for_extend); \ }char *string_from_file(char const *filename);//定義一個標記數組用于對字符串進行標記 typedef struct ok_array {//定義成數組指針應該更好一點,但是這里定義成了指向指針的指針//因為你事先是無法知道要處理字符串的長度和分割之后字符串的多少的char **elements;char *base_string;int length; } ok_array;ok_array *ok_array_new(char *instring, char const *delimiters);void ok_array_free(ok_array *ok_in);string_utilities.c[]()
#include <glib.h> #include <string.h> #include "string_utilities.h" #include <stdio.h> #include <assert.h> #include <stdlib.h> //abortchar *string_from_file(char const *filename){char *out;GError *e=NULL;/*盡管這種做法并不在所有場合下都是可行的,但我已經醉心于一次把一個完整的文本文件讀取到內存中,這是一個很好的使程序員繞過心煩問題的例子。如果預計到需要讀取到內存的文件過于巨大,可以使用mmap(q.v.)來實現相同的效果。*/GIOChannel *f = g_io_channel_new_file(filename, "r", &e);if (!f) {fprintf(stderr, "failed to open file '%s'.\n", filename);return NULL;}if (g_io_channel_read_to_end(f, &out, NULL, &e) != G_IO_STATUS_NORMAL){fprintf(stderr, "found file '%s' but couldn't read it.\n", filename);return NULL;}return out; } /* 這是strtok_r的包裝器。如果讀者是從頭看到這里的,應該已經熟悉這個while循環,它幾乎是必需的。這個函數把從strtok_r所返回的結果記錄到一個ok_array結構。*/ ok_array *ok_array_new(char *instring, char const *delimiters){ok_array *out= malloc(sizeof(ok_array));*out = (ok_array){.base_string=instring};char *scratch = NULL;char *txt = strtok_r(instring, delimiters, &scratch);if (!txt) return NULL;while (txt) {out->elements = realloc(out->elements, sizeof(char*)*++(out->length));out->elements[out->length-1] = txt;txt = strtok_r(NULL, delimiters, &scratch);}return out; }/* Frees the original string, because strtok_r mangled it, so itisn't useful for any other purpose. */ void ok_array_free(ok_array *ok_in){if (ok_in == NULL) return;free(ok_in->base_string);free(ok_in->elements);free(ok_in); }#ifdef test_ok_array int main (){char *delimiters = " `~!@#$%^&*()_-+={[]}|\\;:\",<>./?\n";ok_array *o = ok_array_new(strdup("Hello, reader. This is text."), delimiters);assert(o->length==5);assert(!strcmp(o->elements[1], "reader"));assert(!strcmp(o->elements[4], "text"));ok_array_free(o);printf("OK.\n"); } #endif啥 你的linux系統中沒有glibc庫
要是沒有可以參考下面這個精簡版的對strtok進行學習。
加入你有一個字符串Hello, reader. This is text.,需要按照分隔符`` ~!@#$%^&*()_-+={[]}|\;:",<>./?\n`進行分割,分割之后取出被分割的每個字符串。
char **elements;數組指針中的各個指針指向被分割后的各個字符串的起始位置。
注意這里的分割,只是將分隔符位置的字符替換為\0,并不是真的把一個長的字符串截斷
#include <string.h> #include "string_utilities.h" #include <stdio.h> #include <assert.h> #include <stdlib.h> //abortok_array *ok_array_new(char *instring, char const *delimiters){ok_array *out= malloc(sizeof(ok_array));*out = (ok_array){.base_string=instring};char *scratch = NULL;char *txt = strtok_r(instring, delimiters, &scratch);if (!txt) return NULL;while (txt) {// 申請一個數組指針,數組指針里面存儲的都是指向分隔符替換成'\0'之后的字符的開頭out->elements = realloc(out->elements, sizeof(char*)*++(out->length));out->elements[out->length-1] = txt;txt = strtok_r(NULL, delimiters, &scratch);}return out; }/* Frees the original string, because strtok_r mangled it, so itisn't useful for any other purpose. */ void ok_array_free(ok_array *ok_in){if (ok_in == NULL) return;free(ok_in->base_string);free(ok_in->elements);free(ok_in); }int main (){int i = 0;char *delimiters = " `~!@#$%^&*()_-+={[]}|\\;:\",<>./?\n";ok_array *o = ok_array_new(strdup("Hello, reader. This is text."), delimiters);assert(o->length==5);assert(!strcmp(o->elements[1], "reader"));assert(!strcmp(o->elements[4], "text"));for(i = 0; i < o->length; i++){printf("o->length[%d] = [%s]\n", i, o->elements[i]);}ok_array_free(o);printf("OK.\n"); }C缺陷與陷阱
鏈接:https://pan.baidu.com/s/1QfTa-p2ZlnazDDA92ry_cw
提取碼:mnxb
復制這段內容后打開百度網盤手機App,操作更方便哦
微信用戶原文鏈接
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的C语言必知必会-strtok赞歌的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 5G领域最权威绿宝书迎来中文版啦!
- 下一篇: 作者:李海生(1974-),男,博士,食