關於函數strtok和strtok_r的使用要點和實現原理(一)

strtok函數的使用是一個老生常談的問題了。該函數的作用很大,爭議也很大。以下的表述可能與一些資料有區別或者說與你原來的認識有差異,因此,我儘量以實驗爲證。交代一下實驗環境是必要的,winxp+vc6.0,一個極端平民化的實驗環境。本文中使用的源代碼大部分來自於網絡,我稍加修改作爲例證。當然,本人水平有限,有不妥之處在所難免,各位見諒的同時不妨多做實驗,以實驗爲證。

strtok的函數原型爲char *strtok(char *s, char *delim),功能爲“Parse S into tokens separated by characters in DELIM.If S is NULL, the saved pointer in SAVE_PTR is used as the next starting point. ” 翻譯成漢語就是:作用於字符串s,以包含在delim中的字符爲分界符,將s切分成一個個子串;如果,s爲空值NULL,則函數保存的指針SAVE_PTR在下一次調用中將作爲起始位置。

函數的返回值爲從指向被分割的子串的指針。

這個定義和國內一些網站上的說法有一些差別,正是這些差別導致很多人對strtok沒有一個正確的認識。希望讀者在調用一些函數前,最好能夠讀一讀官方的文檔(多半都是英文的),而非看一些以訛傳訛的資料。

 

使用strtok需要注意的有以下幾點:

1.函數的作用是分解字符串,所謂分解,即沒有生成新串,只是在s所指向的內容上做了些手腳而已。因此,源字符串s發生了變化!

設源字符串s爲 char buffer[INFO_MAX_SZ]=",Fred male 25,John male 62,Anna female 16";  過濾字符串delim爲 char *delim = " ",即空格爲分界符。

 image

上圖的代碼會產生這樣的結果:

image

首先,buffer發生了變化。如果此時打印buffer的值,會顯示“,Fred”,而後面" male 25…16”不翼而飛了。實際上,strtok函數根據delim中的分界符,找到其首次出現的位置,即Fred後面那個空格(buffer[5]),將其修改成了'/0’。其餘位置不變。這就很好解釋爲什麼打印buffer的值只能出現“,Fred”,而非buffer中的全部內容了。因此,使用strtok時一定要慎重,以防止源字符串被修改。 

理解了buffer的變化,就很好解釋函數的返回值了。返回值buf爲分界符之前的子串(其實這個說法並不確切,詳見"3”中對於返回值的詳細說明)。注意,由變量的地址可知,buf依然指向源字符串。

 image

分界符delim沒有發生變化,就不再截圖了。

 

2.若要在第一次提取子串完畢之後,繼續對源字符串s進行提取,應在其後(第二次,第三次。。。第n次)的調用中將strtok的第一個參數賦爲空值NULL。

 image

第一次調用的結果如前文所述,提取出了",Fred”。我們還想繼續以空格爲分界,提取出後面的"male”等。由上圖可以看到,第一次之後的調用我們都給strtok的第一個參數傳遞了空值NULL(表示函數繼續從上一次調用隱式保存的位置,繼續分解字符串;對於上述的第二次調用來說,第一次調用結束前用一個this指針指向了分界符的下一位,即'm’所在的位置),這樣可依次提取出

image ,image 。。。。以此類推。。。。。

至於爲什麼要賦空值,要麼你就記住結論,要麼去查strtok的源代碼。本文的最後會有一些介紹。

當然也有部分愛鑽牛角尖的人,非不按套路出牌,要看看不賦空值繼續賦值爲buffer會有什麼結果。其實,答案想也能想的到。再一次傳遞buffer,相當於還從字符串的開頭查找分界符delim,而且此時buffer已經被修改(可見的部分只剩下",Fred”),因此,其結果必然是找不到分界符delim。

 

3.關於函數返回值的探討

由"1”中所述,在提取到子串的情況下,strtok的返回值(假設返回值賦給了指針buf)是提取出的子串的指針。這個指針指向的是子串在源字符串中的起始位置。子串末尾的下一個字符在提取前爲分隔符,提取後被修改成了'/0’。因此,若打印buf的值,可以成功的輸出子串的內容。

在沒有提取到子串的情況下,函數會返回什麼值呢?

 image

由上圖可以看到buffer中並不包含分界符delim。調用strtok後buf的值爲

 image

因爲沒有找到,源字符串buffer沒有發生改變,buf指向源字符串的首地址,打印輸出的值爲整個字符串的完整值。

什麼時候函數的返回值爲空值NULL呢?

百度百科上說,“當沒有被分割的串時則返回NULL。”這是一個很模棱兩可的說法。如果想要確切的瞭解清楚這個問題,可能需要看一下strtok的實現原理。這裏先以實驗說明。

 image

第一次調用strtok,毫無疑問,buf指向",Fred”。

第二次調用strtok,由於第一個參數爲NULL,表示函數繼續以上次調用所保存的this指針的位置開始分解,即對"male 25”分解。分解完畢後,buf指向"male”。

第三次調用strtok,參數繼續設定爲NULL,此時即對第二次保存的this指針的位置開始分解,即對"25”分解。因爲無法找到包含分隔符delim的子串,所以buf指向"25”。

image

第四次調用,參數仍爲NULL,此時第三次調用保存的this指針已指向字符串的末尾'/0’,已無法再進行分解。因此函數返回NULL,這也就是百度百科中所提到的“當沒有被分割的串時函數返回NULL。”

image

 

4.參數 分隔符delim的探討(delim是分隔符的集合)

很多人在使用strtok的時候,都想當然的以爲函數在分割字符串時完整匹配分隔符delim,比如delim=”ab”,則對於"acdab”這個字符串,函數提取出的是"acd”。至少我在第一次使用的時候也是這麼認爲的。其實我們都錯了,我是在看函數的源代碼時才發現這個問題的,且看下面的例子。

image

源字符串爲buffer,分隔符delim爲 逗號和空格,按照一般的想法我們會以爲調用函數後,buf的值爲"Fred,male,25”,結果是這樣麼?

image

第一次調用之後的結果竟然是"Fred”,而非我們所想的結果。這是爲什麼呢?

我們回到GNU C Library中對strtok的功能定義:“Parse S into tokens separated by characters in DELIM”。也就是說包含在delim中的字符均可以作爲分隔符,而非嚴格匹配。可以把delim理解爲分隔符的集合。這一點是非常重要的~

當然,我們在分解字符串的時候,很少使用多個分隔符。這也導致,很多人在寫例子的時候只討論了一個分隔符的情況。有更多的人在看例子的時候也就錯誤的認識了delim的作用。

5.待分解的字符串,首字符就爲分隔符

首字符爲分隔符不能算作一個很特殊的情況。按照常規的分解思路也能正確分解字符串。

我想說明的是,strtok對於這種情況採用了比常規處理更快的方式。

image

如上圖例子所示。僅用一次調用就可以得到以逗號分隔的字符串"Fred male 25”,而F前面的','被忽略了。由此可見,strtok在調用的時候忽略了起始位置開始的分隔符。這一點,可以從strtok的源代碼得到證實。

 

6.不能向第一個參數傳遞字符串常量!

本文中所舉的例子都將源字符串保存爲字符串數組變量。若你將源字符串定義成字符串常量,可想而知,程序會因爲strtok函數試圖修改源字符串的值,而拋出異常。

image

 

好了,本文詳細介紹了使用strtok的注意事項,(二)中我將詳細介紹strtok不能實現的一些功能並引出strtok_r函數,最後介紹一下兩個函數的實現。



來源:http://blog.csdn.net/liuintermilan/article/details/6280816

發佈了17 篇原創文章 · 獲贊 33 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章