strtok函数的使用是一个老生常谈的问题了。该函数的作用很大,争议也很大。以下的表述可能与一些资料有区别或者说与你原来的认识有差异,因此,我尽量以实验为证。交代一下实验环境是必要的,win7+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 = " ",即空格为分界符。

 #include <stdio.h>
#include <string.h> int main()
{
char buffer[] = ",Fred male 25,John male 62,Anna female 16";
char *buff;
char *delima = " "; buff = strtok(buffer, delima); return ;
}

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

首先,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。

 #include <stdio.h>
#include <string.h> int main()
{
char buffer[] = ",Fred male 25,John male 62,Anna female 16";
char *buff;
char *delima = " "; buff = strtok(buffer, delima);
buff = strtok(NULL, delima);
buff = strtok(NULL, delima);
buff = strtok(NULL, delima); return ;
}

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

  

以此类推。。。。。

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

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

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

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

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

 #include <stdio.h>
#include <string.h> int main()
{
char buffer[] = ",Fred male 25,John male 62,Anna female 16";
char *buff;
char *delima = "+"; buff = strtok(buffer, delima); return ;
}

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

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

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

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

第一次调用strtok,毫无疑问,buf指向",Fred”。此时this指针指向buffer中的字符‘m’。

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

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

第四次调用,参数仍为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函数,最后介绍一下两个函数的实现。

C语言处理CSV文件的方法(二)的更多相关文章

  1. C语言处理CSV文件的方法(一)

    什么是CSV文件 CSV是 Comma-separated values (逗号分隔值)的首字母缩写,它通常是以逗号且不仅限于逗号分隔各个值,我们都叫他CSV. 看下面的例子: China, Shan ...

  2. C语言对文件的读写操作以及处理CSV文件的方法

    #include <stdio.h> #define F_PATH "d:\myfile\file.txt" int main(void) { FILE *fp = N ...

  3. C语言进行csv文件数据的读取

    C语言进行csv文件数据的读取: #include <stdio.h> #include <string.h> #include <malloc.h> #inclu ...

  4. ArcGIS API For Javascript :读取 CSV 文件的方法

    我们临时会遇到一些测试数据,通常从数据库中以 CSV 格式导出.最简单实用的方法就是使用 ajax 去读取文件,记得引入 jQuery . 例如,在<ArcGIS JS API :新增一个热力图 ...

  5. Python中读取csv文件内容方法

    gg 224@126.com 85 男 dd 123@126.com 52 女 fgf 125@126.com 23 女 csv文件内容如上图,首先导入csv包,调用csv中的方法reader()创建 ...

  6. php上传excle文件,csv文件解析为二维数组

    解析上传的CSV文件不是什么难事,直接读取转成你想要的数组样子就OK了. public function putStoreStockIn ($filePath = '') { $file = fope ...

  7. python读写csv文件的方法(还没试,先记录一下)

    该csv模块定义了以下功能: csv.reader(csvfile,dialect ='excel',** fmtparams ) 返回一个reader对象,它将迭代给定csvfile中的行. csv ...

  8. scala/java等其他语言从CSV文件中读取数据,使用逗号','分割可能会出现的问题

    众所周知,csv文件默认以逗号","分割数据,那么在scala命令行里查询的数据: 可以看见,字段里就包含了逗号",",那接下来切割的时候,这本应该作为一个整体 ...

  9. PHP导出数据到CSV文件函数/方法

    如果不清楚什么是CSV文件,可看如下文章介绍  CSV格式的是什么文件?CSV是什么的缩写? /** * 导出数据到CSV文件 * @param array $data 数据 * @param arr ...

随机推荐

  1. Swift的初始化方法

    我们在深入初始化方法之前,不妨先再想想Swift中的初始化想要达到一种怎样的目的. 其实就是安全.在Objective-C中,init方法是非常不安全的:没有人能保证init只被调用一次,也没有人保证 ...

  2. NLS_COMP和NLS_SORT参数

    Oracle默认是采用binary进行排序,这对于例如中文的排序来说,是不恰当的.使用这两个参数可以指定排序的方法,比如拼音或是,要注意可能会引起性能问题.解决方法是使用NLSSORT函数来建立一个函 ...

  3. LINQ 基本子句之一 (select/where/group/into)

    特别喜欢同事看到我写了一句小排序的时候说,他当然喜欢Linq了,虽然我只是baidu之,不知其然也不知其所以然. 基本格式 var<变量> = from <项目> in < ...

  4. input的样式简介

    <input type="text" autocomplete="off" placeholder="" x-webkit-speec ...

  5. .net程序员转战android第二篇---牛刀小试

    上篇说道如何搭建android的开发环境,这一篇我们将牛刀小试一下, 完成我们第一个android APP应用. 我就从新建项目说起吧. 首先打开Eclipse,选择顶部的File(文件)——new( ...

  6. 自定义不等高的cell-(纯代码)frame

    给模型增加frame数据 所有子控件的frame cell的高度 @interface XMGStatus : NSObject /**** 文字\图片数据 ****/ // ..... /**** ...

  7. HDU 1004 - Let the Balloon Rise(map 用法样例)

    Problem Description Contest time again! How excited it is to see balloons floating around. But to te ...

  8. mysql server advanced 5.6基于oracle linux 6.6的安装

    mysql 安装有两种,rpm安装和源码包安装,两种包都可以从www.mysql.com官网下载,这次我测试下rpm安装方式. 1.安装环境以及mysql版本: 1.1vcenter 虚拟机环境 1. ...

  9. jquery ajax提交及请求

    jQuery.ajax({ url: dataURL, success: function(results) { var parsedJson = jQuery.parseJSON(results); ...

  10. tornado 使用过程中提示‘no module name ioloop’

    千万不要将程序文件命名为tornado.py 不然永远不会运行,因为运行后会在tornado.py里查找ioloop信息,永远找不到...