本文转载自: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的误解比较深,网上很少有对于其非常详细的讨论,因此总结一份比较全面的材料,是有必要的;二来这也是自己不断学习的一个过程,总结会得到远比两个函数重要很多的信息。

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

  1. 关于函数strtok和strtok_r的使用要点和实现原理(一)

    strtok函数的使用是一个老生常谈的问题了.该函数的作用很大,争议也很大.以下的表述可能与一些资料有区别或者说与你原来的认识有差异,因此,我尽量以实验为证.交代一下实验环境是必要的,winxp+vc ...

  2. C语言字符串截取函数strtok和strtok_r

    在看源码的时候需要将一段并排的IPs转化成为一系列的IP,将"10.0.0.1;10.0.0.2;10.0.0.3;10.0.0.4;10.0.0.5"转换成为单独的"1 ...

  3. mysql strtok_c函数: strtok 和 strtok_r 详解

    函数名:   strtok 功     能:   查找由在第二个串中指定的分界符分隔开的单词 用     法:   char   *strtok(char   *str1,   char   *str ...

  4. 字符串分割函数strtok和strsep使用注意事项

    转载自 https://blog.csdn.net/astrotycoon/article/details/50813959 为什么写本文 最近工作中经常需要解析字符串,并且这些字符串都有一个共同的特 ...

  5. 字符串切割函数strtok、strtok_s、strtok_r的区别

    strtok函数 头文件:#include <string.h> 函数原型:char * strtok (char *str, const char * delimiters); 参数:s ...

  6. 函数 —— strtok() 例如:Fred male 25,John male 62,Anna female 16两层循环

    //char *strtok(char *str, const char *delim) /*功能:  * 函数用来将字符串分割成一个个片段*/ /*参数:  * str -- 要被分解成一组小字符串 ...

  7. strtok和strtok_r

    strtok和strtok_r 原型:char *strtok(char *s, char *delim); 功能:分解字符串为一组字符串.s为要分解的字符串,delim为分隔符字符串. 说明:首次调 ...

  8. 线程安全——strtok VS strtok_r

    #include<string.h> char *strtok(char* restrict s1,const char* restrict s2); strtok将字符串分隔成标记.对s ...

  9. 【C语言】字符串函数strtok 按照指定字符串分割

    C语言字符串函数 strtok() 函数原型 char *strtok(char *str,const char *delimiters); 参数 str,待分割的字符串 delimiters,分隔符 ...

最新文章

  1. 思科:全球近75%的物联网项目失败
  2. Cuboid特征提取算法
  3. 连续连通域检测算法的MATLAB仿真
  4. 第二章 Spark RDD以及编程接口
  5. IWMS实现频道页面的方法
  6. Python 3.6模拟输入并爬取百度前10页密切相关链接
  7. ThinkPHP 使用极光推送给ios推送消息
  8. Linux下PHP开启Oracle支持(oci8)
  9. Python语言应该加上块标识(开始、结束)的特性
  10. 时间序列(二):时间序列平稳性检测
  11. 5G UPF与MEC
  12. 【bat】做个一键连接网络打印机的bat
  13. npm WARN using --force Recommended protections disabled.
  14. 湖北大学98届计算机网络,我校学子在“H3C”杯全国大学生网络技术大赛中获一等奖...
  15. 开源项目_C语言学生个人信息管理系统课程设计_chenjie的博客
  16. MyBatis中insert用法
  17. Hankson 的趣味题
  18. STM32名字含义以及其与ARM公司的关系
  19. Java多线程系列--【JUC锁04】-公平锁(二)
  20. 考虑蒙特卡洛考虑风光不确定性的配电网运行风险 评估 由于风电光伏出力的不确定性,造成配电网运行风险,运用蒙特卡洛概率潮流计算分析电压和线路支路越限

热门文章

  1. Gemsouls借助亚马逊云科技,将虚拟人社交平台遍布世界各地
  2. html设置使图片自动移动,css怎么移动图片?
  3. JS写的数字拼图小游戏
  4. 制作RPG独立游戏练习(二)内置渲染管线中实现风格化PBR效果
  5. [转]【Gaming NB】如何解决部分搭载联发科MT7921无线网卡的TUF/ROG NB机型出现掉网络的情况?(或:华硕飞行堡垒等WiFi消失怎么办?)
  6. 在线编辑office服务器搭建,Office Online Server2016搭建与部署(在线编辑)
  7. 【java基础】为什么重写toString()方法?
  8. VirtualBox安装Ubuntu18
  9. C#PropertyGrid下拉框和类型转换如何使用
  10. Springboot集成Flyway(适用于多数据源)