strtok_r使用

  • strtok函数

原型:

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

描述:分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。当strtok()在参数s 的字符串中发现到参数delim 的分割字符时则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数s 字符串,往后的调用则将参数s 设置成NULL。每次调用成功则返回下一个分割后的字符串指针。

输入:char *s  -要分解的字符串,strtok在调用的时候会忽略起始位置开始的分隔符

        const char *delim -分隔符字符串

输出:char*    -提取到子串时,返回值是提取到的子串的指针,该指针指向的是子串在源字符串中的起始位置,

                     子串末尾的下一个字符提取前是分隔符,提取后被修改成了'\0'。

                     没有提取到子串,即源字符串中没有分隔符字符串的分隔符,返回的是源字符串的首地址。

                     分解子串时,如果分解已指向源字符串的尾部时,无法再继续分解,此时返回NULL。

strtok实现使用了静态变量,所以该函数是不可重入的,线程安全的函数是strtok_r。

  • strtok_r函数

原型:

char *strtok_r(char *s, const char *delim, char **saveptr);

描述:strtok函数的可重入版本,char **saveptr参数是一个指向char *的指针变量,用来在strtok_r内部保存切分时的上下文,以应对连续调用分解相同源字符串。第一次调用strtok_r时,s参数必须指向待提取的字符串,saveptr参数的值可以忽略。连续调用时,str赋值为NULL,saveptr为上次调用后返回的值,不要修改。

strtok_r实际上就是将strtok内部隐式保存的分隔符下一位的指针,以参数的形式与函数外部进行交互。

输入:char *s               -要分解的字符串 

        const char *delim -分隔符字符串

        char **saveptr    -记录提取子串后,源字符串下次开始提取子串的起始处。

                                  即:提取子串的末尾的下一个字符被修改为'\0',*saveptr指向被修改字符的后一个字符

输出:char *    -同strtok

strtok、strtok_r都是分解字符串,源字符串s会被修改,要继续使用原字符串应该在调用前保存一个副本,源字符串也不能是字符串常量。

delim中的字符均可以作为分隔符,而非严格匹配,可以把delim理解为分隔符的集合。所以在分解字符串的时候,很少使用多个分隔符。

但是使用一个分隔符的时候,也要以字符串的形式作为输入参数,而不能只给一个字符。

下面的一个实现是VxWorks系统的实现:

char * strtok_r
(
    char *       string,	/* string to break into tokens */
    const char * separators,	/* the separators */
    char **      ppLast		/* pointer to serve as string index */
)
{
    if ((string == NULL) && ((string = *ppLast) == NULL))
	return (NULL);

    if (*(string += strspn (string, separators)) == EOS)
	return (*ppLast = NULL);

    if ((*ppLast = strpbrk (string, separators)) != NULL)
	*(*ppLast)++ = EOS;

    return (string);
}

  • strspn函数

原型:

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

描述:计算字符串s里,连续属于字符串accept内的字符个数

输入:const char *s  -原始字符串 

        const char *accept -指定字符串

输出:size_t    -字符串s开头连续包含字符串accept内的字符数目

strpbrk函数

原型:

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

描述:找出参数s 字符串中最先含有指定accept 字符串中的任意字符的位置。

输入:const char *s  -原始字符串 

        const char *accept -指定字符串

输出:char *   -找到第一个指定字符串字符的前一个位置

                     找不到指定字符串的字符返回NULL

今日BUG:

背景:项目里需要解析一个特定命令,字符串是数字,以','或'-'分隔,比如1,3,5 或者1-4,6-8,要解析出每个数字。

代码框架:

定义了一个数据结构,将要分解的字符串,当前分解得到的字串,下一步分解的起始位置都封装到一起。

struct segment_parser
{
  char  orig_str  [CLI_ARGV_MAX_LEN];

  char* start_ptr; /*parse buf start*/
  char* next_ptr;
  char* curr_seg;
};

CLI(int argc,char* argv[])
{
    struct segment_parser seg;
    struct id_range id_range;
    id_list = argv[0];
    ...
    pal_mem_set(&seg, 0, sizeof(struct segment_parser));
    seg.start_ptr = seg.orig_str;
    pal_strcpy(seg.start_ptr, id_list);
    ...
    if( check_format(id_list) != CLI_SUCCESS)
        return CLI_ERROR;
    ...
    while(get_segment_next(&seg) != NULL)
    {
        segment_parse(&seg, &id_range);
        for(id = id_range.start; id <= id_range.end; id++)
        {
            ...
        }
    }
    return CLI_SUCCESS; 
}

在做处理之前要先分解一次,判断每次分解的结果是不是合法。

int check_format(char *input)
{
    struct segment_parser seg;
    struct id_range id_range;
	char *org_str;
   
    pal_mem_set(&seg, 0, sizeof(struct segment_parser));
    pal_mem_set(&id_range, 0, sizeof(struct id_range));
    org_str = seg.orig_str;

    pal_strcpy(org_str, input);

    /*first and last character not be comma or hyphen*/
    if(input = NULL 
      || *input == ',' || *input == '-'
      || *(input+len-1) == ',' || *(input+len-1) == '-')
    {
        ...
        return CLI_ERROR;
    }

    /* all character should be digital or comma or hyphen */
    if(is_legal_id(org_str) != RANGE_PARSER_SUCCESS)
    {
        ...
        return CLI_ERROR;
        
    }
    
    seg.start_ptr = seg.orig_str;
    
    while(cfm_meplist_get_segment_next(&seg) != NULL)
    {
        ...
       
        ret = segment_parse(&seg, &id_range);

        if(ret != RANGE_PARSER_SUCCESS)
            return CLI_ERROR;
    }

    return CLI_SUCCESS;
}

分解的处理流程如下,

char* get_segment_next(struct segment_parser *seg)
{
    char *delim_comma = ",";

    if(seg->start_ptr == NULL)
    {
        return NULL;
    }
        
    seg->curr_seg = strtok_r(seg->start_ptr, delim_comma, &(seg->next_ptr));
    seg->start_ptr = seg->next_ptr;
    
    return seg->curr_seg;
}

原来的delim_comma定义为了字符型而不是字符串,这是不对的,但是刚开始没有发现这个问题,分解居然正确了。但是很快就发现check_format分解正确,而继续第二次解析时发现解析不到。

char delim_comma = ',';/*错误使用*/
...
seg->curr_seg = strtok_r(seg->start_ptr, &delim_comma, &(seg->next_ptr));

这是因为刚好执行分解的时候delim_comma后面的内存内容为0,相当于delim_comma为字符串,多运行几次后面的内存就会被使用, 就会出问题了。

总结:C的字符串以'\0'为结尾来区分,非常容易写代码出问题,使用字符串库函数的时候一定要先搞清输入和输出参数,拿不准的时候就找源码来看。今天的BUG看起来很简单,但是刚开始没意识到的时候挺难发现的,还是最后查看了源码才意识到输入参数是字符串的。


参考:http://blog.csdn.net/liuintermilan/article/details/6280816

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值