要将类似“a a a,b b b,c c c,d d d,e e e,f f f”这样的字符串一一分割出来,如何实现?
1、使用strtok()存在的缺陷
strtok()函数嵌套使用,无论是在同一个函数中或者在主调和被调函数中同时使用strtok的开始语句如strtok(str,","); 都极易出现问题。看下面案例:
- int _tmain(int argc, _TCHAR* argv[])
- {
- char buffer[] = "a a a,b b b,c c c,d d d,e e e,f f f";
- char *buf = buffer;
- char *p[30];
- int i = 0;
- printf("Before strtok,len of buffer = %d\n",strlen(buffer));
-
- while((p[i]=strtok(buf,",")) != NULL)
- {
- buf = p[i];
- printf("%s\n",buf);
- while( (p[i]=strtok(buf," ")) != NULL )
- {
- printf("%s\n",p[i]);
- i++;
- buf = NULL;
- }
- printf("========================\n");
- buf = NULL;
- }
-
- printf("after strtok,len of buffer = %d",strlen(buffer));
- getchar();
- return 0;
- }
上面代码的运行效果如下:
由上面的运行效果可以看出,这个并不是我们想要的效果。因为它只分割了buffer数组第一个逗号前面的字符串。
很明显,因为内循环中使用了strtok同时也使用了静态指针变量,使得内循环执行结束时,外循环的strtok函数给静态变量所赋的值被修改了;而我们想要的是第一次内循环执行结束时,这个静态变量应该存储的是字符串”b b b,c c c,d d d,e e e,f f f”的首地址。
图3 外循环进行一次分割
图4 内循环运行结束
注意,虽然号称为分割,实际上整个buffer的存储空间内初始元素是不变的。分割的实质利用的一个向前移动的分割指针,凡是遇到分割符,就将该置置\0并返回分割符前面的字符串指针,图6证明了这个说法。上面程序中,对buffer执行一次外循环、一次内循环前后的对比结果,见图5:
图6
问题:为什么外循环只执行了一次就结束了呢?
我们假设,strtok需要与一个静态的指针变量一起搭配使用。上面代码所使用使用了两套strtok,而保存地址的指针变量只有一个。当外循环第一次执行时,指针变量存储的是字符’b’所在的地址。而因为下面又执行了内循环,当内循环的strtok执行结束,指针变量此时存储的是”a\0a\0a\0”,第三个’a’的地址。
当外循环的strtok从第三个’a’地址开始往后遍历时,刚一移动,就遇到了”\0”,没办法。
所谓出师未捷身先死,就出现本例图1所示的效果了。
2、使用strtok_s()函数(linux下是strtok_r())就能实现我们想要的效果
- int _tmain(int argc, _TCHAR* argv[])
- {
- char buffer[] = "a a a,b b b,c c c,d d d,e e e,f f f";
- char *buf = buffer;
- char *p[30];
- int i = 0;
- char *outer_ptr = NULL;
- char *inner_ptr = NULL;
- printf("Before strtok,len of buffer = %d\n",strlen(buffer));
-
- while((p[i]=strtok_s(buf,",",&outer_ptr)) != NULL)
- {
- buf = p[i];
- printf("%s\n",buf);
- while( (p[i]=strtok_s(buf," ",&inner_ptr)) != NULL )
- {
- printf("%s\n",p[i]);
- i++;
- buf = NULL;
- }
- printf("========================\n");
- buf = NULL;
- }
-
- printf("after strtok,len of buffer = %d",strlen(buffer));
- getchar();
- return 0;
- }
strtok_s()函数是如何实现这个功能的呢?这又如何解释呢?
图8
由图8,因为每个strtok各自维护一个pointer,所以无论进行分割后进行什么操作,这些pointer都会很忠实的记住下一次开始的地址。从而避免出现1中的由于strtok嵌套使用引入的问题。
参考博客:
http://www.cnblogs.com/hoys/archive/2011/09/19/2180999.html
PS: 参考博客的博主hoy同时也是在下的老友,向hoy的探索和贡献精神致敬!