C语言字符串处理 --- strtok函数

本文深入探讨C/C++中strtok函数的常见误区,包括其不可重入性、修改源字符串、处理连续分隔符及忽略字符串首尾分隔符等问题,解析函数内部工作原理,提供规避陷阱的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

strtok函数

https://blog.youkuaiyun.com/farmwang/article/details/78884791

在用C/C++实现字符串处理逻辑时,strtok函数的使用非常广泛,其主要作用是按照给定的字符集分隔字符串,并返回各子字符串。由于该函数的使用有诸多限制,如果使用不当就会造成很多“坑”,因此本文首先介绍那些经常误踩的坑,然后通过分析源代码,解读该函数的诸多隐含特性,以便对该函数有个全面的理解,不再被坑。

那些年一起踩过的坑

TOP1 不可重入

目前大部分程序都是在多线程环境下运行的,因此函数的可重入性就显得尤为重要。下面实例的本意是,先按照;分隔句子并输出,然后再按照空格分隔单词并输出,可结果呢?

 
  1. #include <stdio.h>

  2. #include <string.h>

  3.  
  4. int main(void)

  5. {

  6. char szTest[] = "Hello world;I'm Pele"; /* 以;作为句子的分隔符,以空格作为单词的分隔符 */

  7. char *pSentence = NULL;

  8. char *pWord = NULL;

  9.  
  10. /* 先分隔句子 */

  11. pSentence = strtok(szTest, ";");

  12. while (NULL != pSentence)

  13. {

  14. printf("The sentence is %s.\n", pSentence);

  15.  
  16. /* 再分隔单词 */

  17. pWord = strtok(pSentence, " ");

  18. while (NULL != pWord)

  19. {

  20. printf("The word is %s.\n", pWord);

  21. pWord = strtok(NULL, " ");

  22. }

  23.  
  24. pSentence = strtok(NULL, ";");

  25. }

  26.  
  27. return 0;

  28. }

预期结果如下:

$ gcc test_strtok.c
$ ./a.out
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world.
The <span class="hljs-property">word</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello.
The <span class="hljs-property">word</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> world.
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> I'm Pele.
The <span class="hljs-property">word</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> I'm.
The <span class="hljs-property">word</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Pele.

 

 

 

可实际结果如下:

$ gcc test_strtok.c
$ ./a.out
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world.
The <span class="hljs-property">word</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello.
The <span class="hljs-property">word</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> world.

实际告诉我们:strtok不可重入。

TOP2 源字符串会被修改

如果一个字符串在我们的视线之外被修改了,那么可能会发生一些列诡异的事,而我们却全然不知。请看如下案例:

 
  1. #include <stdio.h>

  2. #include <string.h>

  3.  
  4. int main(void)

  5. {

  6. char szTest[] = "Hello world;I'm Pele";

  7. char *pSentence = NULL;

  8.  
  9. printf("The original string is %s.\n", szTest);

  10.  
  11. pSentence = strtok(szTest, ";");

  12. while (NULL != pSentence)

  13. {

  14. pSentence = strtok(NULL, ";");

  15. }

  16.  
  17. printf("The final string is %s.\n", szTest);

  18.  
  19. return 0;

  20. }

预期结果如下:

$ gcc test_strtok.c
$ ./a.<span class="hljs-keyword" style="color:rgb(0,0,136);">out</span>
The original <span class="hljs-typename" style="color:rgb(102,0,102);">string</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world;I<span class="hljs-attribute">'m</span> Pele.
The final <span class="hljs-typename" style="color:rgb(102,0,102);">string</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world;I<span class="hljs-attribute">'m</span> Pele.

可实际结果如下:

$ gcc test_strtok.c
$ ./a.<span class="hljs-keyword" style="color:rgb(0,0,136);">out</span>
The original <span class="hljs-typename" style="color:rgb(102,0,102);">string</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world;I<span class="hljs-attribute">'m</span> Pele.
The final <span class="hljs-typename" style="color:rgb(102,0,102);">string</span> <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world.

实际告诉我们:你传入的字符串会被strtok修改。

TOP3 连续的分隔符被当做一个分隔符处理

如果两个分隔符连续出现,那么在分隔的时候,你是希望分隔出一个空字符串,还是希望strtok忽略掉多余的分隔符呢?请看strtok给我们的答案:

 
  1. #include <stdio.h>

  2. #include <string.h>

  3.  
  4. int main(void)

  5. {

  6. char szTest[] = "Hello world;;I'm Pele"; /* 连续使用两个;分隔语句 */

  7. char *pSentence = NULL;

  8.  
  9. pSentence = strtok(szTest, ";");

  10. while (NULL != pSentence)

  11. {

  12. printf("The sentence is %s.\n", pSentence);

  13. pSentence = strtok(NULL, ";");

  14. }

  15.  
  16. return 0;

  17. }

实际结果如下:

$ gcc test_strtok.c
$ ./a.<span class="hljs-keyword" style="color:rgb(0,0,136);">out</span>
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world.
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> I<span class="hljs-attribute">'m</span> Pele.

实际告诉我们:连续出现的分隔符只被处理一次。

TOP4 字符串首尾的分隔符会被忽略

你希望将字符串首尾的分隔符忽略掉吗?如果不希望,那么请慎用strtok,请看:

 
  1. #include <stdio.h>

  2. #include <string.h>

  3.  
  4. int main(void)

  5. {

  6. char szTest[] = ";Hello world;I'm Pele;;";

  7. char *pSentence = NULL;

  8.  
  9. pSentence = strtok(szTest, ";");

  10. while (NULL != pSentence)

  11. {

  12. printf("The sentence is %s.\n", pSentence);

  13. pSentence = strtok(NULL, ";");

  14. }

  15.  
  16. return 0;

  17. }

实际结果如下:

$ gcc test_strtok.c
$ ./a.<span class="hljs-keyword" style="color:rgb(0,0,136);">out</span>
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> Hello world.
The sentence <span class="hljs-keyword" style="color:rgb(0,0,136);">is</span> I<span class="hljs-attribute">'m</span> Pele.

实际告诉我们:字符串首尾的分隔符会被忽略。

真相只有一个

下面的代码取自glibc-2.20的strtok.c文件,未做任何删改,中文注释为笔者添加,用于说明造成以上坑的原因。

 
  1. #include <string.h>

  2.  
  3.  
  4. static char *olds; /* 使用了全局变量,因此该函数不可重入--TOP1坑 */

  5.  
  6. #undef strtok

  7.  
  8. #ifndef STRTOK

  9. # define STRTOK strtok

  10. #endif

  11.  
  12. /* Parse S into tokens separated by characters in DELIM.

  13. If S is NULL, the last string strtok() was called with is

  14. used. For example:

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

  16. x = strtok(s, "-"); // x = "abc"

  17. x = strtok(NULL, "-="); // x = "def"

  18. x = strtok(NULL, "="); // x = NULL

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

  20. */

  21. char *

  22. STRTOK (char *s, const char *delim)

  23. {

  24. char *token;

  25.  
  26. if (s == NULL)

  27. s = olds;

  28.  
  29. /* 跳过了字符串前面的分隔符,如果字符串只剩下尾部的分隔符,跳过前导符相当于忽略尾部的分隔符--TOP4坑 */

  30. /* Scan leading delimiters. */

  31. s += strspn (s, delim);

  32. if (*s == '\0')

  33. {

  34. olds = s;

  35. return NULL;

  36. }

  37.  
  38. /* Find the end of the token. */

  39. token = s;

  40. s = strpbrk (token, delim);

  41. if (s == NULL)

  42. /* This token finishes the string. */

  43. olds = __rawmemchr (token, '\0');

  44. else /* 找到一个分隔符就返回,下次进入该函数会跳过前导分隔符,此为TOP3坑 */

  45. {

  46. /* Terminate the token and make OLDS point past it. */

  47. *s = '\0'; /* 将分隔符所在位置置0,此为TOP2坑 */

  48. olds = s + 1;

  49. }

  50. return token;

  51. }

总结

  • 尽量使用可重入版的strtok,Windows平台下为strtok_s,Linux平台下为strtok_r。
  • 牢记strtok函数族的分隔规则:忽略字符串前后的分隔符,连续的分隔符被当做一个处理。
  • 在使用strtok前,请对源字符串进行备份,除非你可以接受字符串被修改这一事实。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值