strtok和strtok_r

1.strtok()函數的用法

函數原型char *strtok(char *s, const char *delim);
Function分解字符串爲一組字符串。s爲要分解的字符串,delim爲分隔符字符串。
Description:strtok()用來將字符串分割成一個個片斷。參數s指向欲分割的字符串,參數delim則爲分割字符串,當strtok()在參數s的字符串中發現到參數delim的分割字符時 則會將該字符改成\0 字符。在第一次調用時,strtok()必需給予參數s字符串,日後的調用則將參數s設置成NULL。每次調用成功則返回被分割出片斷的指針。html

1若是stork函數的第一個參數不是NULL函數將找到的字符串的第一個標記。同時保存它在字符串中的位置ios

2若是strtok函數的第一個參數是NULL,函數就在同一個字符串中從這個被保存的位置開始向前面同樣查找下一個標記。c++

下面是一個使用實例:算法

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main(int argc, char * argv[])
 5 {
 6     //時間格式 2010/08/11 10:38:22
 7     char strEventTime[] = "2010/08/11 10:38:22";
 8     char *token = NULL;
 9     
10      token = strtok(strEventTime, "/");
11     char *year = token;
12     if (token != NULL)
13     {
14         token = strtok(NULL, "/");
15     }
16     char *month = token;
17     if (token != NULL)
18     {
19         token = strtok(NULL, " ");
20     }
21     char *day = token;
22     if (token != NULL)
23     {
24         token = strtok(NULL, ":");
25     }
26     char *hour = token;
27     if (token != NULL)
28     {
29         token = strtok(NULL, ":");
30     }
31     char *minute = token;
32 
33     if (token != NULL)
34     {
35         token = strtok(NULL, ":");
36     }
37     char *second = token;
38 
39     printf("%s %s %s %s %s %s %s\n", year, month, day, hour, minute, second);
40     return 0;
41 }

/*  
 * strtok是一個線程不安全的函數,由於它使用了靜態分配的空間來存儲被分割的字符串位置   
 * 線程安全的函數叫strtok_r  
 * 運用strtok來判斷ip或者mac的時候務必要先用其餘的方法判斷'.'或':'的個數
 * 由於用strtok截斷的話,好比:"192..168.0...8..."這個字符串,strtok只會截取四次,中間的...不管多少都會被看成一個key
 */安全

2.strtok()函數的實現

(1)NetBSD實現:函數

1: char*  strtok_r(char* string_org,const char* demial,char** last)
   2: {
   3: const char* spanp; //span表示分隔,p表示指針
   4: char c, sc; //c表示char字符,sc表示 span char
   5: char* tok;  //token表示分隔的段
   6:  
   7: //當開始結尾都爲NULL的時候,說明沒有字符被查找,因此返回NULL
   8: if (string_org == NULL  && (string_org = *last) == NULL)
   9:     {
  10:     return (NULL);
  11:     }
  12:  
  13: //由goto組成的循環是在掃描字符串的時候,當遇到所須要匹配的字符時,略過這個字符。        
  14: cont:
  15: c = *string_org++;
  16:     
  17: for (spanp = demial; (sc = *spanp++) != 0; )
  18:     {
  19:     if (c == sc)
  20:         {
  21:         goto cont;
  22:         }
  23:     }
  24:  
  25: //下一個字符爲0,則表示到達了搜索結果,把last置爲NULL,並返回NULL            
  26: if (c == 0)
  27:     {
  28:     *last = NULL;
  29:     return (NULL);
  30:     }
  31:  
  32: //把原始的字符串指針回退。            
  33: tok = string_org -1;
  34:  
  35: //開始掃描字符串中是否含有要匹配的字符,以後把這個匹配字符以前的部分返回。
  36: //這看似是個無限循環,但當源字符串和匹配字符串都走到結尾時,也就是string_org和sc都爲NULL時,最外層循環最後會走到return(tok)結束循環。
  37: for (;;)
  38:     {
  39:     c = *string_org++;
  40:     spanp = demial;
  41:     
  42:     do 
  43:         {
  44:         if ((sc = *spanp++) == c) 
  45:             {
  46:             if (c == 0)
  47:                 {
  48:                 string_org = NULL;
  49:                 }
  50:             else
  51:                 {
  52:                 string_org[-1] = 0;
  53:                 }
  54:             *last = string_org;
  55:             return (tok);
  56:             }
  57:         } while (sc != 0);
  58:     }
  59:     
  60: }

(2)在NetBSD中strtok的實現:post

   1: //把last設置爲一個靜態局部變量來保存餘下內容的地址。
   2: char *
   3: strtok(char *s, const char *delim)
   4:     {
   5:     static char *lasts;
   6:  
   7:     return strtok_r(s, delim, &lasts);
   8:     }

 

(3)微軟的實現:測試

   1: char*  strtok_r(char* string_org,const char* demial)
   2: {
   3: static unsigned char* last; //保存分隔後剩餘的部分
   4: unsigned char* str;         //返回的字符串
   5: const unsigned char* ctrl = (const unsigned char*)demial;//分隔字符
   6:  
   7: //把分隔字符放到一個索引表中。定義32是由於ASCII字符表最可能是0~255個,也是說用最大的255右移3位,也就是除以8必定會是32中的一個數。
   8: unsigned char map[32]; 
   9: int count;
  10:  
  11: //把map所有清爲0,以後相與的操做,與0的都爲0
  12: for (count =0; count <32; count++)
  13:     {
  14:     map[count] = 0;
  15:     }
  16:  
  17: //把匹配字符放入表中
  18: //放入的算法是把匹配字符右移3位,至關於除以8,的數值 並上(加上)
  19: //匹配字符與7,獲得低3位,得出的結果,是把1左移的位數。最大左移位數是7,也就是所表示的最大值是128,    
  20: do 
  21:     {
  22:     map[*ctrl >> 3] |= (1 << (*ctrl & 7));
  23:     } while (*ctrl++);
  24:     
  25: //原始字符串是否爲空,若是爲空表示第二次獲取剩餘字符的分隔部分。    
  26: if (string_org)
  27:     {
  28:     str = (unsigned char*)string_org;
  29:     } 
  30: else
  31:     {
  32:     str = last;
  33:     }
  34:  
  35: //在表中查找是否有匹配的字符,若是有略過    
  36: while ((map[*str >> 3] & (1 << (*str & 7)))  && *str)
  37:     {
  38:     str++;
  39:     }
  40:  
  41: //重置須要掃描的字符串    
  42: string_org = (char*)str;
  43:  
  44: //開始掃描
  45: for (;*str; str++)
  46:     {
  47:     if ( map[*str >> 3] & (1 << (*str & 7)))
  48:         {
  49:         *str++ = '\0';//當找到時,把匹配字符填爲0,而且把str指向下一位。
  50:         break; //退出循環             
  51:         }
  52:             
  53:     }
  54:     
  55: last =str; // 把剩餘字符串的指針保存到靜態變量last中。
  56:     
  57: if (string_org == (char*)str)
  58:     {
  59:     return NULL; //沒有找到,也就是沒有移動指針的位置,返回NULL
  60:     }
  61: else
  62:     {
  63:     return string_org; //找到了,返回以前字符串的頭指針
  64:     }
  65: }

 

(4)對比:url

  1.NetBSD的方法是節約了空間,犧牲了時間(它的時間複雜度爲N2)
  2.而微軟的方法是節約了時間(它的時間複雜度爲N),犧牲了空間(開了一個32個8位的空間spa

 

 

3.strtok()本身的理解和註釋

  本身在NetBSD的基礎上加了一些幫助理解的註釋:

 

 1 #ifndef STRTOK_H  2 #define STRTOK_H
 3 
 4 
 5 #include <stdio.h>
 6 
 7 // NetBSD:
 8 char *cat_strtok_r(char *src, const char *delim, char **last) {  9     const char *spanp; // span表示分割,p表示指針
10     char c, sc; // c表示char字符(保存src中的字符),sc表示span char(保存delim的字符)
11     char *tok;  // 表示分隔的段 12 
13     // 當開始、結尾都爲NULL時,說明沒有字符查找
14     if (NULL == src && NULL == (src = *last)) 15         return NULL; 16 
17     // 由goto組成的循環是在掃描字符串的時候,當遇到所須要匹配的字符時,略過這個字符
18 cont: 19     c = *src++; 20 
21     for (spanp = delim; (sc = *spanp++) != 0; ) { 22         if (c == sc) 23             goto cont; // cat:這裏的意思應該是:src字符串頭部匹配delim的部分都忽略掉,由於分割了也沒意義,匹配部分前面已經沒有其餘字符了
24  } 25 
26     // 下一個字符爲0,則表示到達了搜索結果,把last置爲NULL,並返回NULL 27     // cat:其實就是到達src的尾部,也就意味着delim和src是相等的,就無從分割了
28     if (0 == c) { 29         *last = NULL; 30         return NULL; 31  } 32 
33     // 把原始的字符串指針回退
34     tok = src - 1; // cat:由於c = *src++;最後一步就是不匹配仍是會執行++操做 35 
36     // 開始掃描字符串中是否含有壓迫匹配的字符,以後把該匹配字符以前的部分返回 37     // 當源字符串和匹配字符串當走到結尾時,即src和sc都爲NULL時,最外層循環最後會return(tok)結束循環
38     for ( ; ; ) { 39         c = *src++; 40         spanp = delim; 41         do { 42             if ((sc = *spanp++) == c) { 43                 if (0 == c) 44                     src = NULL; 45                 else
46                     src[-1] = 0; 47                     // 好比src爲"12:34",匹配字符爲":"。當匹配到":"時,src++致使src指向了"3",因此須要減1,把匹配到的位置(即把src中的":")設爲'\0' 48                     //*(src - 1) = 0;// 等價於這行代碼
49 
50                 *last = src; 51 
52                 return tok; 53  } 54         } while (sc != 0); 55  } 56 } 57 
58 // 把last設置爲一個靜態局部變量來保存餘下內容的地址
59 char *cat_strtok(char *src, const char *delim) { 60     static char *lasts; 61     return cat_strtok_r(src, delim, &lasts); 62 } 63 
64 #endif

 

測試代碼:

 1 #include "strtok.h"
 2 
 3 
 4 void test_strtok();  5 
 6 int main() {  7 
 8  test_strtok();  9 
10     return 0; 11 } 12 
13 void test_strtok() { 14     //char *src = "111:222:333:444";
15     char *src = "123:456"; 16     char *ret = cat_strtok(src, ":"); 17 
18     printf("%s\n%s\n", ret, src); 19 }

  而後咱們會發現程序運行到src[-1] = 0;這一步的時候出錯。爲何呢?

  先看C語言中內存分佈狀況(更多請查閱《C語言char[]和char*比http://www.cnblogs.com/lingshaohu/p/3956239.html ):

char s[]="abc";   //
char *p2;         //
char *p3="123456";   //123456\0在常量區,p3在棧上。

  也就是說,char *src = "123:456";  "123:456"是一個常量!常量不能修改,而咱們src[-1] = 0;嘗試修改它,因此報錯!

 

  所以,改爲這樣就能夠了

void test_strtok() {
    //char *src = "111:222:333:444";
    //char *src = "123:456";
    char src[] = "123:456";
    char *ret = cat_strtok(src, ":");

    printf("%s\n%s\n", ret, src);
}

 

 

 

 

 

 

ref:

http://www.cnblogs.com/aduck/articles/2245364.html

http://www.cppblog.com/yinquan/archive/2009/06/01/86411.html

相關文章
相關標籤/搜索