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

请给我倒杯茶 请给我倒杯茶     2022-08-07     759

关键词:

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

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 = " ",即空格为分界符。

技术分享

上图的代码会产生这样的结果:

技术分享

首先,buffer发生了变化。如果此时打印buffer的值,会显示“,Fred”,而后面" male 25…16”不翼而飞了。实际上,strtok函数根据delim中的分界符,找到其首次出现的位置,即Fred后面那个空格(buffer[5]),将其修改成了‘/0’。其余位置不变。这就很好解释为什么打印buffer的值只能出现“,Fred”,而非buffer中的全部内容了。因此,使用strtok时一定要慎重,以防止源字符串被修改。

理解了buffer的变化,就很好解释函数的返回值了。返回值buf为分界符之前的子串(其实这个说法并不确切,详见"3”中对于返回值的详细说明)。注意,由变量的地址可知,buf依然指向源字符串。

技术分享

分界符delim没有发生变化,就不再截图了。

 

2.若要在第一次提取子串完毕之后,继续对源字符串s进行提取,应在其后(第二次,第三次。。。第n次)的调用中将strtok的第一个参数赋为空值NULL。

技术分享

第一次调用的结果如前文所述,提取出了",Fred”。我们还想继续以空格为分界,提取出后面的"male”等。由上图可以看到,第一次之后的调用我们都给strtok的第一个参数传递了空值NULL(表示函数继续从上一次调用隐式保存的位置,继续分解字符串;对于上述的第二次调用来说,第一次调用结束前用一个this指针指向了分界符的下一位,即‘m’所在的位置),这样可依次提取出

技术分享 ,技术分享 。。。。以此类推。。。。。

至于为什么要赋空值,要么你就记住结论,要么去查strtok的源代码。本文的最后会有一些介绍。

当然也有部分爱钻牛角尖的人,非不按套路出牌,要看看不赋空值继续赋值为buffer会有什么结果。其实,答案想也能想的到。再一次传递buffer,相当于还从字符串的开头查找分界符delim,而且此时buffer已经被修改(可见的部分只剩下",Fred”),因此,其结果必然是找不到分界符delim。

 

3.关于函数返回值的探讨

由"1”中所述,在提取到子串的情况下,strtok的返回值(假设返回值赋给了指针buf)是提取出的子串的指针。这个指针指向的是子串在源字符串中的起始位置。子串末尾的下一个字符在提取前为分隔符,提取后被修改成了‘/0’。因此,若打印buf的值,可以成功的输出子串的内容。

在没有提取到子串的情况下,函数会返回什么值呢?

技术分享

由上图可以看到buffer中并不包含分界符delim。调用strtok后buf的值为

技术分享

因为没有找到,源字符串buffer没有发生改变,buf指向源字符串的首地址,打印输出的值为整个字符串的完整值。

什么时候函数的返回值为空值NULL呢?

百度百科上说,“当没有被分割的串时则返回NULL。”这是一个很模棱两可的说法。如果想要确切的了解清楚这个问题,可能需要看一下strtok的实现原理。这里先以实验说明。

技术分享

第一次调用strtok,毫无疑问,buf指向",Fred”。

第二次调用strtok,由于第一个参数为NULL,表示函数继续以上次调用所保存的this指针的位置开始分解,即对"male 25”分解。分解完毕后,buf指向"male”。

第三次调用strtok,参数继续设定为NULL,此时即对第二次保存的this指针的位置开始分解,即对"25”分解。因为无法找到包含分隔符delim的子串,所以buf指向"25”。

技术分享

第四次调用,参数仍为NULL,此时第三次调用保存的this指针已指向字符串的末尾‘/0’,已无法再进行分解。因此函数返回NULL,这也就是百度百科中所提到的“当没有被分割的串时函数返回NULL。”

技术分享

 

4.参数 分隔符delim的探讨(delim是分隔符的集合)

很多人在使用strtok的时候,都想当然的以为函数在分割字符串时完整匹配分隔符delim,比如delim=”ab”,则对于"acdab”这个字符串,函数提取出的是"acd”。至少我在第一次使用的时候也是这么认为的。其实我们都错了,我是在看函数的源代码时才发现这个问题的,且看下面的例子。

技术分享

源字符串为buffer,分隔符delim为 逗号和空格,按照一般的想法我们会以为调用函数后,buf的值为"Fred,male,25”,结果是这样么?

技术分享

第一次调用之后的结果竟然是"Fred”,而非我们所想的结果。这是为什么呢?

我们回到GNU C Library中对strtok的功能定义:“Parse S into tokens separated by characters in DELIM”。也就是说包含在delim中的字符均可以作为分隔符,而非严格匹配。可以把delim理解为分隔符的集合。这一点是非常重要的~

当然,我们在分解字符串的时候,很少使用多个分隔符。这也导致,很多人在写例子的时候只讨论了一个分隔符的情况。有更多的人在看例子的时候也就错误的认识了delim的作用。

 

 

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

首字符为分隔符不能算作一个很特殊的情况。按照常规的分解思路也能正确分解字符串。

我想说明的是,strtok对于这种情况采用了比常规处理更快的方式。

技术分享

如上图例子所示。仅用一次调用就可以得到以逗号分隔的字符串"Fred male 25”,而F前面的‘,‘被忽略了。由此可见,strtok在调用的时候忽略了起始位置开始的分隔符。这一点,可以从strtok的源代码得到证实。

 

6.不能向第一个参数传递字符串常量!

本文中所举的例子都将源字符串保存为字符串数组变量。若你将源字符串定义成字符串常量,可想而知,程序会因为strtok函数试图修改源字符串的值,而抛出异常。

技术分享

 

好了,本文详细介绍了使用strtok的注意事项,(二)中我将详细介绍strtok不能实现的一些功能并引出strtok_r函数,最后介绍一下两个函数的实现。

字符串分割函数strtok(线程不安全),线程安全函数strtok_r

strtok_r函数---字符串分割函数函数原型:    char*strtok_r(char*str,constchar*delim,char**saveptr);参数:str:被分割的字符串,若str为NULL,则被分割的字符串为*saveptrdelim:依据此字符串分割strsaveptr:分割后剩余部分的字符串... 查看详情

strtok_r 导致“赋值使指针从整数不进行强制转换”

】strtok_r导致“赋值使指针从整数不进行强制转换”【英文标题】:strtok_rcausing"assignmentmakespointerfromintegerwithoutacast"【发布时间】:2013-06-0514:41:24【问题描述】:我正在尝试在C中标记字符串并使用strtok_r将标记保存到多个... 查看详情

Strtok_r 返回 NULL

】Strtok_r返回NULL【英文标题】:Strtok_rreturningNULL【发布时间】:2019-06-1914:47:47【问题描述】:我正在尝试对从文件中提取的字符串进行标记。strtok_r在第一个子字符串上正常工作,然后返回null(以及分段错误,因为我尝试将strndu... 查看详情

c语言源码剖析与实现——strtok()系列函数实现(代码片段)

...外几个函数strspn()、strpbrk()、strcspn()strspn()strcspn()strpbrk()strtok_r()源代码实现(不依赖其他函数库)完全自己实现strtok()设计方案代码实现性能分析测试用例源码剖析与实践strtok()源代码实现由于是用的static实现的全局变量存储地址... 查看详情

使用strtok从字符串中解析空标记(代码片段)

...nt与多线程无关.strtok已经使用嵌套循环中断了。可以使用strtok_r,但它不是那么便携。)另一答案这是strtok的限制。设计师考虑到了以空格分隔的标记。strtok无论如何都做不了多少;只需滚动你自己的解析器。CFAQhasanexample。另一... 查看详情

函数内部还是不要使用strtok()

...的时候,突然想起可能是 strtok()引起的,查找调用的函数,果然发现在函数中使用了 strtok()。而现在的问题就是在另一段代码中先使用了 strtok(),然后在没有结束前,又调用了一个内部使用 strtok()的函数,导致了&nb... 查看详情

c语言最短时间带你实现strtok,字符串分割函数,建议收藏!!!(代码片段)

...结↗️↗️↗️建议三连,以防丢失前言字符串分割函数strtok,大家可能都知道他怎么使用,一旦要用的时候就会心生疑惑,不知道它的内部的实现,废话不多说,本篇就来带大家看看strtok的基本使用和实... 查看详情

strtok函数的使用注意事项

1.函数原型及其基本应用   strtok函数是用来分解字符串的,其原型是: [cpp] viewplain copy char *strtok(char str[], const char *delim);    其中str是要分解的字符 查看详情

关于function和task的说明

 1. 关于函数function调用,总结两个要点:      1. 函数调用一般产生一个值,这个值被赋值给某个变量      2. 函数所返回的值只能是一个,不可以是多个,不能像C语言中... 查看详情

c基础函数的使用(代码片段)

目录一、库函数的使用1.1随机数rand与srand1.2scanf函数1.3gets函数1.4fgets函数1.5puts函数1.6strlen函数1.7strcat函数和strncat函数1.8strcmp和strncmp1.9strcpy和strncpy1.10sprintf函数1.11sscanf函数1.12strchr和strstr函数1.13strtok函数1.14atoi和atof, 查看详情

如何将 atoi 和 strtok 与多维数组一起使用?

】如何将atoi和strtok与多维数组一起使用?【英文标题】:Howtoconvertusingatoiandstrtokwithmultidimesionalarrays?【发布时间】:2021-03-0819:55:55【问题描述】:Okaysoihavethismltidimarrayfilledwiththesenumbers,我希望在这些位置将它们转换为整数所以它... 查看详情

关于androidstudio调用高德地图的简单流程和要点

一,账号与Key的申请注册成为高德开发者需要分三步:第一步,注册高德开发者;第二步,去控制台创建应用;第三步,获取Key。前2步都比较简单,这里说下第三步。获取Key1、进入控制台,创建一个新应用。如果您之前已经创... 查看详情

关于mapstate和mapmutations和mapgetters和mapactions辅助函数的用法及作用-----mapstate

一、通过mapState函数的对象参数来赋值:<p>{{count}}</p><p>{{count1}}</p><p>{{count2}}<p>//导入import{mapState}from‘vuex‘exportdefault{data(){return{msg:‘vuex理解要点‘,id:1}},store,//方法二: 查看详情

strtok() 可以安全使用吗?

...布时间】:2015-06-0109:50:45【问题描述】:我正在阅读很多关于strtok()的负面信息,有人说它已经过时,有人说它不是线程安全的,等等。那么真相是什么,我可以用strtok()吗?它是线程安全的吗?注意:我使用的是VisualC++。【问... 查看详情

个税小游戏要点之节流函数(提高动画流畅性)(代码片段)

...用频率的控制器,这里只做简单的介绍,如果想了解更多关于这两个定义的细节可以看下后文给出的一张图片,或者阅读一下lodash的文档。throttle:将一个函数的调用频率限制在一定阈值内,例如1s内一个函数不能被调用两次。de... 查看详情

为啥当我使用不同版本的 GCC 时使用 strtok 函数时出现此错误?

】为啥当我使用不同版本的GCC时使用strtok函数时出现此错误?【英文标题】:WhyisthiserroronusingstrtokfunctionshowingwhenIuseadifferentversionofGCC?为什么当我使用不同版本的GCC时使用strtok函数时出现此错误?【发布时间】:2020-08-2823:56:54【... 查看详情

strtok()出现segmentfault的错误(代码片段)

...通过空格分割成一个个字符串参数,这里我使用了strtok()函数,然后遇到了segmentfault的错误。出现问题的代码如下:终于寻找到原因:strtok(char*string,char*delim)函数的实现逻辑是函数是在s中查找包含在delim中的字符并用NULL(’/0′)... 查看详情

strtok的使用(代码片段)

/*strtok函数的使用*/#include<stdio.h>#include<stdlib.h>#include<string.h>//函数原型://char*strtok(char*str,constchar*delim)//参数://str--要被分解成一组小字符串的字符串//delim--包含分隔符的C字符串//返回值//该函数返回被分解的第一... 查看详情