关于函数strtok和strtok_r的使用要点和实现原理(二)【转】

本文转载自:http://astute11.blog.51cto.com/4404646/1334199

(一)中已经介绍了使用strtok函数的一些注意事项,本篇将介绍strtok的一个应用并引出strtok_r函数。

1.一个应用实例

网络上一个比较经典的例子是将字符串切分,存入结构体中。如,现有结构体


1

2

3

4

5

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,先将子串先一一保存到字符串指针数组中,程序末尾打印指针数组中保存的所有子串,验证程序的正确性。得到的程序应该如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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并不能通过两层循环的办法,解决提取多人信息的问题。有没有其他办法呢? 显然,是有其他途径的。

我给出了一种解决办法。同时以 ‘,’ (逗号) 和 ‘ ’(空格) 为分界符,一层循环解决问题。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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);

下面对strtok的英文说明摘自http://www.linuxhowtos.org/manpages/3/strtok_r.htm,译文是由我给出的。

The strtok_r() function is a reentrant version strtok(). Thesaveptr argument is a pointer to a char *variable that is used internally bystrtok_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 ofsaveptris ignored. In subsequent calls, str should be NULL, andsaveptr 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 different saveptr arguments.

一系列不同的字符串可能会同时连续调用strtok_r进行提取,要为不同的调用传递不同的saveptr参数。

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

strtok函数在提取字符串时使用了静态缓冲区,因此,它是线程不安全的。如果要顾及到线程的安全性,应该使用strtok_r。

strtok_r实际上就是将strtok内部隐式保存的this指针,以参数的形式与函数外部进行交互。由调用者进行传递、保存甚至是修改。需要调用者在连续切分相同源字符串时,除了将str参数赋值为NULL,还要传递上次切分时保存下的saveptr。

举个例子,还记得前文提到的提取结构体的例子么?我们可以使用strtok_r,以双重循环的形式提取出每个人的信息。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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这个参数。增加了函数的灵活性和安全性。

3.strtok和strtok_r的源代码

这两个函数的实现,有众多的版本。我strtok_r来自于GNU C Library,strtok则调用了strtok_r。因此先给出strtok_r的源代码。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

/* 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.  For example:

        char s[] = "-abc-=-def";

        char *sp;

        x = strtok_r(s, "-", &sp);      // x = "abc", sp = "=-def"

        x = strtok_r(NULL, "-=", &sp);  // x = "def", sp = NULL

        x = strtok_r(NULL, "=", &sp);   // x = NULL

                // s = "abc\0-def\0"

*/

char *strtok_r(char *s, const char *delim, char **save_ptr) {

    char *token;

    if (s == NULL) s = *save_ptr;

    /* Scan leading delimiters.  */

    s += strspn(s, delim);

    if (*s == ‘\0‘)

        return NULL;

    /* Find the end of the token.  */

    token = s;

    s = strpbrk(token, delim);

    if (s == NULL)

        /* This token finishes the string.  */

        *save_ptr = strchr(token, ‘\0‘);

    else {

        /* Terminate the token and make *SAVE_PTR point past it.  */

        *s = ‘\0‘;

        *save_ptr = s + 1;

    }

    return token;

}

代码整体的流程如下:

(1)判断参数s是否为NULL,如果是NULL就以传递进来的save_ptr作为起始分解位置;若不是NULL,则以s开始切分。

(2)跳过待分解字符串开始的所有分界符。

(3)判断当前待分解的位置是否为‘\0‘,若是则返回NULL(联系到(一)中所说对返回值为NULL的解释);不是则继续。

(4)保存当前的待分解串的指针token,调用strpbrk在token中找分界符:如果找不到,则将save_ptr赋值为待分解串尾部‘\0‘所在的位置,token没有发生变化;若找的到则将分界符所在位置赋值为‘\0‘,token相当于被截断了(提取出来),save_ptr指向分界符的下一位。

(5)函数的最后(无论找到还是没找到)都将返回。

对于函数strtok来说,可以理解为用一个内部的静态变量将strtok_r中的save_ptr给保存起来,对调用者不可见。其代码如下:


1

2

3

4

5

char *strtok(char *s, const char *delim)

{

    static char *last;

    return strtok_r(s, delim, &last);

}

有了上述两个函数的实现代码,再理解(一)(二)中所讲的一些要点也就不困难了。

花那么多篇幅总结这两个函数,一来是因为很多人对于strtok的误解比较深,网上很少有对于其非常详细的讨论,因此总结一份比较全面的材料,是有必要的;二来这也是自己不断学习的一个过程,总结会得到远比两个函数重要很多的信息。

时间: 2024-11-02 21:24:30

关于函数strtok和strtok_r的使用要点和实现原理(二)【转】的相关文章

关于函数strtok和strtok_r的使用要点和实现原理

strtok函数的使用是一个老生常谈的问题了.该函数的作用很大,争议也很大.以下的表述可能与一些资料有区别或者说与你原来的认识有差异,因此,我尽量以实验为证.交代一下实验环境是必要的,winxp+vc6.0,一个极端平民化的实验环境.本文中使用的源代码大部分来自于网络,我稍加修改作为例证.当然,本人水平有限,有不妥之处在所难免,各位见谅的同时不妨多做实验,以实验为证. strtok的函数原型为char *strtok(char *s, char *delim),功能为“Parse S into

关于函数strtok和strtok_r的使用要点和实现原理(一)【转】

本文转载自:http://astute11.blog.51cto.com/4404646/1334198 strtok函数的使用是一个老生常谈的问题了.该函数的作用很大,争议也很大.以下的表述可能与一些资料有区别或者说与你原来的认识有差异,因此,我尽量以实验为证.交代一下实验环境是必要的,winxp+vc6.0,一个极端平民化的实验环境.本文中使用的源代码大部分来自于网络,我稍加修改作为例证.当然,本人水平有限,有不妥之处在所难免,各位见谅的同时不妨多做实验,以实验为证. strtok的函数原型

字符串分割函数 STRTOK &amp; 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(线程不安全),线程安全函数strtok_r

strtok_r函数---字符串分割函数 函数原型: char *strtok_r(char *str, const char *delim, char **saveptr); 参数: str:被分割的字符串,若str为NULL,则被分割的字符串为*saveptr delim:依据此字符串分割str saveptr:分割后剩余部分的字符串 返回值: 遇到第一个delim时,分割出的字符串,若没有遇到delim,则范围NULL 例程: int main(int argc,char* argv[])

strtok和strtok_r

原型:char *strtok(char *s, char *delim); 功能:分解字符串为一组字符串.s为要分解的字符串,delim为分隔符字符串. 说明:首次调用时,s指向要分解的字符串,之后再次调用要把s设成NULL.        strtok在s中查找包括在delim中的字符并用NULL('/0')来替换,直到找遍整个字符串. 返回值:从s开头開始的一个个被切割的串.当没有被切割的串时则返回NULL.           全部delim中包括的字符都会被滤掉,并将被滤掉的地方设为一

怪怪的函数strtok

// //#include<stdio.h>//strtok函数的利用 //int main() //{ // char p[] = "192.196.23.45"; // char *token=strtok(p, "."); // ///*printf("%s  ",token); // //while (token != NULL) // //{ // // token = strtok(NULL, ".")

C语言分割字符串函数strtok

在编程过程中,有时需要对字符串进行分割.而有效使用这些字符串分隔函数将会给我们带来很多的便利. 下面我将在MSDN中学到的strtok函数做如下翻译. strtok :在一个字符串查找下一个符号 char *strtok( char *strToken, const char *strDelimit ); 返回值:返回指向在strToken字符串找到的下一个符号的指针,当在字符串找不到符号时,将返回NULL.每 次调用都通过用NULL字符替代在strToken字符串遇到的分隔符来修改strTok

VC实现字符串分割的函数strtok

Python网络爬虫-正则表达式 分享第二套C语言源代码合集 vc++6.0如何捕获关机事件并执行我自己的代码 浅谈面向对象--<ThinkinginJava>读书笔记(一) zv5拙侔号http://p.baidu.com/pai/center?uid=e97061626332393034363011aa&type=qq35kx踊泻蓖7tsuhd副战追http://p.baidu.com/pai/center?uid=78e261626333363962616211aa&ty

qsort 函数的使用——对普通数组、指针数组、二维数组中的元素进行排序

在ANSI C中,qsort函数的原型是 #include <stdlib.h> void qsort(void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *)); 解释:qsort函数对含有nmemb个元素的数组进行排序,而base指针指向数组的第一个元素.这个数组的元素个数由size指定. compar函数对qsort的比较操作进行定义,所以可以定制数字的比较,字符串的比较,甚至结构体