对于一些高级语言而言,使用某个字符分割字符串是很基础的功能。而对于编程语言中的瑞士军刀C,该功能自然也是有的,我们使用标准C提供的strtok函数即可实现这个功能。这个函数在使用上面看上去有些怪,我们从一个例子开始吧。

一,strtok基本使用

#include <stdio.h>
#include <string.h>

int main()
{
    char *token;
    char line[] = "ab,c:d e fngh";
    char *delims = ",: tn";

    /* 第一次调用strtok时第一个参数为要处理的字符串 */
    token = strtok(line, delims);
    while (token != NULL)
    {
        printf ( "%sn", token );
        /* 后续调用传NULL */
        token = strtok(NULL, delims); 
    }

    return 0;
}

程序执行结果如下:

ab
c
d
e
f
gh

然后再看下strtok的函数原型:

char *strtok(char *str, const char *delim);

strtok的第一个参数毫无疑问就是我们想要处理的字符串,第二个参数是分割符。这里注意第二个参数是字符串,而不是字符。因为strtok支持使用多个分割字符分割字符串,甚至在每次调用strtok的时候可以重新指定分割符。

strtok的使用方法:strtok第一次调用的时候str参数传我们要处理的字符串,后续再调用的时候第一个参数传NULL。然后我们多次调用strtok,直到返回NULL则解析结束。每一次调用,返回分割得到的字符串,该字符串包含字符串结束符。

二,strtok实现细节及使用时的注意事项

我们看一下glic中strtok的实现:

#include <string.h>

static char *olds;

#undef strtok

#ifndef STRTOK
# define STRTOK strtok
#endif

/* Parse S into tokens separated by characters in DELIM.
   If S is NULL, the last string strtok() was called with is
   used.  For example:
    char s[] = "-abc-=-def";
    x = strtok(s, "-");     // x = "abc"
    x = strtok(NULL, "-=");     // x = "def"
    x = strtok(NULL, "=");      // x = NULL
        // s = "abc=-def"
*/
char *
STRTOK (char *s, const char *delim)
{
  char *token;

  if (s == NULL)
    s = olds;

  /* Scan leading delimiters.  */
  s += strspn (s, delim);
  if (*s == '')
    {
      olds = s;
      return NULL;
    }

  /* Find the end of the token.  */
  token = s;
  s = strpbrk (token, delim);
  if (s == NULL)
    /* This token finishes the string.  */
    olds = __rawmemchr (token, '');
  else
    {
      /* Terminate the token and make OLDS point past it.  */
      *s = '';
      olds = s + 1;
    }
  return token;
}

可以看到,其实strtok就是一个循环字符串解析的过程,解析过程这里就不详细分析了,有兴趣的可以自己研究一下这段代码。但从这个实现中我们可以得出一些使用strtok的注意点:

1, strtok会改变传给它的字符串(代码里面的s = ''),细心的人可能已经发现它的第一个参数不是const的。所以我们一定得保证传的字符串是可以更改的。最常见的错误是给strtok传一个char的字符串字面常量(这样可能会导致段错误):

char *str1 = "hello world";
char str2[] = "hello world";

注意,上面的str1就是字符串字面常量,这种定义方法定义的字符串是常量字符串,是const的,是不可以改变的。诸如str1[1] = 'c';的赋值操作都是不允许的。而str2的定义方法则只是一个普通的数组,是可以更改的。

2, 连续的分割符都会被跳过。这个是什么意思呢?举个例子:比如对于字符串"ab::cd:",我们使用分割符":"分割的时候,返回值是"ab"、"cd"、NULL,而不是"ab"、""(空字符串)、"cd"、NULL。

3, 如果目标字符串是空或者只包含分割符,那么我们第一次调用strtok的时候,就会返回NULL。

最后简单介绍以下上面glic实现strtok时使用的几个库函数:

#include <string.h>

size_t strspn(const char *s, const char *accept);

size_t strcspn(const char *s, const char *reject);

char *strpbrk(const char *s, const char *accept);

strspn()从参数s 字符串的开头计算连续的字符,而这些字符都完全是accept 所指字符串中的字符。简单的说,若strspn()返回的数值为n,则代表字符串s 开头连续有n 个字符都是属于字符串accept内的字符。

strcspn()从参数s 字符串的开头计算连续的字符, 而这些字符都完全不在参数reject 所指的字符串中. 简单地说, 若strcspn()返回的数值为n, 则代表字符串s 开头连续有n 个字符都不含字符串reject 内的字符.

strpbrk是在源字符串(s1)中找出最先含有搜索字符串(s2)中任一字符的位置并返回,若找不到则返回空指针。