1. 程式人生 > >strtok、strtok_r 字串分割函式

strtok、strtok_r 字串分割函式

1.一個應用例項

typedef struct person{
char name[25];
char sex[10];
char age[4];
}Person;

需從字串 char buffer[INFO_MAX_SZ]=”Fred male 25,John male 62,Anna female 16”; 中提取出人名、性別以及年齡。

一種可行的思路是設定兩層迴圈。外迴圈,先以 ‘,’ (逗號) 為分界符,將三個人的資訊分開,然後對於每一個子串,再以 ’ ’(空格) 為分界符分別得到人名、性別和年齡。

按照這個思路,理應能夠實現所要的功能。為了簡化步驟,我們呼叫strtok,先將子串先一一儲存到字串指標陣列中,程式末尾列印指標陣列中儲存的所有子串,驗證程式的正確性。得到的程式應該如下:

#define INFO_MAX_SZ 256
int in=0;  
char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16";      
char *p[20];  
char *buf = buffer;  
while((p[in]=strtok(buf,","))!=NULL)   
{  
    buf=p[in];  
    while((p[in]=strtok(buf," "))!=NULL)   
    {  
        in++;  
        buf=NULL;  
    }  
    buf=NULL
; } printf("Here we have %d strings/n", in); for (int j=0; j<in; j++) { printf(">%s</n",p[j]); }

執行的結果是,僅僅提取出了第一個人的資訊。看來程式的執行並沒有按照我們的預想。原因是什麼?

原因是:在第一次外迴圈中,strtok將”Fred male 25,”後的這個逗號,改為了’/0’,這時strtok內部的this指標指向的是逗號的後一個字元’J’。經過第一次的內迴圈,分別提取出了“Fred” “male” “25”。提取完”25”之後,函式內部的this指標被修改指向了”25”後面的’/0’。內迴圈結束後(內迴圈實際執行了4次),開始第二次的外迴圈,由於函式第一個引數被設定為NULL,strtok將以this指標指向的位置作為分解起始位置。很遺憾,此時this指標指向的是’/0’,strtok對一個空串無法切分,返回NULL。外迴圈結束。所以,我們只得到瞭如圖所示的第一個人的資訊。

看來使用strtok並不能通過兩層迴圈的辦法,解決提取多人資訊的問題。有沒有其他辦法呢? 顯然,是有其他途徑的。

我給出了一種解決辦法。同時以 ‘,’ (逗號) 和 ’ ’(空格) 為分界符,一層迴圈解決問題。

in = 0;  
while ((p[in] = strtok(buf, " ,")) != NULL)  
{  
    switch (in % 3)  
    {  
    case 0:  
        printf("第%d個人:Name!/n", in/3+1);  
        break;  
    case 1:  
        printf("第%d個人:Sex!/n", in/3+1);  
        break;  
    case 2:  
        printf("第%d個人:Age!/n", in/3+1);  
        break;  
    }  
    in++;  
    buf = NULL;  
}  
printf("Here we have %d strings/n", in);  
for (int j=0; j<in; j++)  
{     
    printf(">%s</n",p[j]);  
}  

程式雖然可以達到理想的結果,但不是一個太好解決方案。程式要求你在提取之前必須要知道一個結構體中究竟包含了幾個資料成員。明顯不如雙重迴圈那樣直觀。

倘若一定要採用二重迴圈那種結構提取,有沒有合適的函式能夠代替strtok呢? 有的,它就是strtok_r。

2.strtok_r及其使用

strtok_r是linux平臺下的strtok函式的執行緒安全版。windows的string.h中並不包含它。要想使用這個函式,上網搜其linux下的實現原始碼,複製到你的程式中即可。別的方式應該也有,比如使用GNU C Library。我下載了GNU C Library,在其原始碼中找到了strtok_r的實現程式碼,複製過來。可以看作是第一種方法和第二種方法的結合。

strtok的函式原型為 char *strtok_r(char *str, const char *delim, char **saveptr);

The strtok_r() function is a reentrant version strtok(). The saveptr argument is a pointer to a char * variable that is used internally by strtok_r() in order to maintain context between successive calls that parse the same string.

strtok_r函式是strtok函式的可重入版本。char *saveptr引數是一個指向char 的指標變數,用來在strtok_r內部儲存切分時的上下文,以應對連續呼叫分解相同源字串。

On the first call to strtok_r(), str should point to the string to be parsed, and the value of saveptr is ignored. In subsequent calls, str should be NULL, and saveptr should be unchanged since the previous call.

第一次呼叫strtok_r時,str引數必須指向待提取的字串,saveptr引數的值可以忽略。連續呼叫時,str賦值為NULL,saveptr為上次呼叫後返回的值,不要修改。

Different strings may be parsed concurrently using sequences of calls to strtok_r() that specify differentsaveptr arguments.

一系列不同的字串可能會同時連續呼叫strtok_r進行提取,要為不同的呼叫傳遞不同的saveptr引數。

The strtok() function uses a static buffer while parsing, so it’s not thread safe. Use strtok_r() if this matters to you.

strtok函式在提取字串時使用了靜態緩衝區,因此,它是執行緒不安全的。如果要顧及到執行緒的安全性,應該使用strtok_r。

strtok_r實際上就是將strtok內部隱式儲存的this指標,以引數的形式與函式外部進行互動。由呼叫者進行傳遞、儲存甚至是修改。需要呼叫者在連續切分相同源字串時,除了將str引數賦值為NULL,還要傳遞上次切分時儲存下的saveptr。

舉個例子,還記得前文提到的提取結構體的例子麼?我們可以使用strtok_r,以雙重迴圈的形式提取出每個人的資訊。

int in=0;  
char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16";  
char *p[20];  
char *buf=buffer;  
char *outer_ptr=NULL;  
char *inner_ptr=NULL;  
while((p[in] = strtok_r(buf, ",", &outer_ptr))!=NULL)   
{  
    buf=p[in];  
    while((p[in]=strtok_r(buf, " ", &inner_ptr))!=NULL)   
    {  
        in++;  
        buf=NULL;  
    }  
    buf=NULL;  
}  
printf("Here we have %d strings/n",in);  
for (int j=0; j<in; j++)  
{     
    printf(">%s</n",p[j]);  
}  

呼叫strtok_r的程式碼比呼叫strtok的程式碼多了兩個指標,outer_ptr和inner_ptr。outer_ptr用於標記每個人的提取位置,即外迴圈;inner_ptr用於標記每個人內部每項資訊的提取位置,即內迴圈。具體過程如下:

(1)第1次外迴圈,outer_ptr忽略,對整個源串提取,提取出”Fred male 25”,分隔符’,’ 被修改為了’/0’,outer_ptr返回指向’J’。

(2)第一次內迴圈,inner_ptr忽略,對第1次外迴圈的提取結果”Fred male 25”進行提取,提取出了”Fred”,分隔符’ ‘被修改為了’/0’,inner_ptr返回指向’m’。

(3)第二次內迴圈,傳遞第一次內迴圈返回的inner_ptr,第一個引數為NULL,從inner_ptr指向的位置’m’開始提取,提取出了”male”,分隔符 ’ ‘被修改為了’/0’,inner_ptr返回指向’2’。

(4)第三次內迴圈,傳遞第二次內迴圈返回的inner_ptr,第一個引數為NULL,從inner_ptr指向的位置’2’開始提取,提取出了”25”,因為沒有找到’ ‘,inner_ptr返回指向25後的’/0’。

(5)第四次內迴圈,傳遞第三次內迴圈返回的inner_ptr,第一個引數為NULL,因為inner_ptr指向的位置為’/0’,無法提取,返回空值。結束內迴圈。

(6)第2次外迴圈,傳遞第1次外迴圈返回的outer_ptr,第一個引數為NULL,從outer_ptr指向的位置’J’開始提取,提取出”John male 62”,分隔符’,’被修改為了’/0’,outer_ptr返回指向’A’。(呼叫strtok則卡死在了這一步)

……以此類推,外迴圈一次提取一個人的全部資訊,內迴圈從外迴圈的提取結果中,二次提取個人單項資訊。

可以看到strtok_r將原內部指標顯示化,提供了saveptr這個引數。增加了函式的靈活性和安全性。