C语言必知必会-strtok赞歌

128 篇文章 1 订阅
91 篇文章 4 订阅

strtok的赞歌

标记解析(Tokenizing)是最简单也是最常见的解析问题,也就是根据分隔符把一个字符串分割为几个部分。这个定义覆盖了所有这种类型的任务。根据空白分隔符(例如" \t\n\r"之一)分割单词。假设有个像"/usr/include:/usr/local/include:."这样的路径,在冒号处将其分开,形成单独的目录。根据一个简单的换行分隔符"\n"把一个字符串分割为不同的行。可以使用一个配置文件,包含value = key格式的行,在这种情况下分隔符就是"="。在数据文件中以逗号分隔的值当然是以逗号为分隔符。我们可以采取两个层次的分割来分别进行处理。例如读取一个完整的配置文件,首先根据换行符进行分割,然后在每行根据=进行分割。

标记解析出现得非常频繁,有一个C标准库函数strtok(字符串标记化)专门用于完成这个任务。它是那些虽然小巧但能够干净利落地完成任务的函数之一。strtok的基本工作方式是对我们所输入的字符串进行迭代,直到遇到第1个分隔符,然后用一个'\0'覆盖这个分隔符。现在这个输入字符串的第1部分已经是个表示第1个标记的合法字符串,strtok返回一个指向这个子字符串头部的指针供我们使用。这个函数在内部保存源字符串的信息,因此当我们再次调用strtok时,它可以搜索到下一个标记的尾部,将之设置为‘\0’,并以一个合法的字符串的形式返回这个标记。每个子字符串的指针都指向原字符串内的位置,因此标记化操作所执行的数据写入是极少的(只是那些\0),并且不会进行复制操作。它
的直接影响是输入字符串被损坏,由于子字符串是指向源字符串的指针,因此无法释放这个输入字符串,必须等到完成了所有子字符串的使用以后,你才能去释放原始的字符串。(或者,可以使用strdup复制出这些子字符串。)
strtok函数通过一个静态的内部指针保存我们第1次输入的字符串的剩余部分,意味着它被限制为一次只能标记化一个字符串(通过一组分隔符),无法在涉及线程的情况下使用。因此目前strtok已经不推荐使用了。推荐使用strtok_rstrtok_s,它们是strtok的线程友好版本。POSIX标准提供了strtok_rC11标准提供了strtok_s。这两个函数的用法都有点笨拙,因为第1次调用与后续的调用在形式上是不一样的。

第1次调用函数时,把需要解析的字符串作为它的第1个参数。在后续的调用中,把第1个参数设置为NULL。最后一个参数是处理过的字符串,我们并不需要在第1次使用时对它进行初始化,在后续的调用中,它将保存到目前为止所解析的字符串。
下面是个行计数器(事实上是非空白行的计数器,参见后面的警告)。在脚本语言中,标记解析往往只需要一行程序,但是以下已经是strtok_r最精简的用法了。注意用于发送源字符串的if ? then:else只出现在第1次呼叫时传入字符串。

#include <string.h> //strtok_r
int count_lines(char *instring){
    int counter = 0;
    char *scratch, *txt, *delimiter = "\n";
    while ((txt = strtok_r(!counter ? instring : NULL, delimiter, &scratch
    )))
    counter++;
    return counter;
}

C11标准的strtok_s函数的工作方式就像strtok_r一样,但接受一个额外的参数(第2个),它提供了输入字符串的长度,并在后续的调用过程中不断缩短,表示每次调用时剩余字符串的长度。如果输入字符串并不是以\0分隔的,这个额外的参数就非常实用。我们可以用下面的代码重新完成前面那个例子:

#include <string.h> //strtok_s
//first use
size_t len = strlen(instring);
txt = strtok_s(instring, &len, delimiter, &scratch);
//subsequent use:
txt = strtok_s(NULL, &len, delimiter, &scratch);

如果你的系统装安装的有glibc参照下方的实现

string_utilities.h

#include <string.h>
#define _GNU_SOURCE //asks stdio.h to include asprintf
#include <stdio.h>

//Safe asprintf macro
#define Sasprintf(write_to,  ...) {          \
    char *tmp_string_for_extend = write_to;  \
    //asprintf每次调用都会新申请一段内存,所以要讲老的指针释放掉
    asprintf(&(write_to), __VA_ARGS__);      \
    free(tmp_string_for_extend);             \
}

char *string_from_file(char const *filename);

//定义一个标记数组用于对字符串进行标记
typedef struct ok_array {
    //定义成数组指针应该更好一点,但是这里定义成了指向指针的指针
    //因为你事先是无法知道要处理字符串的长度和分割之后字符串的多少的
    char **elements;
    char *base_string;
    int length;
} ok_array;

ok_array *ok_array_new(char *instring, char const *delimiters);

void ok_array_free(ok_array *ok_in);

string_utilities.c[]()

#include <glib.h>
#include <string.h>
#include "string_utilities.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h> //abort

char *string_from_file(char const *filename){
    char *out;
    GError *e=NULL;
    /*尽管这种做法并不在所有场合下都是可行的,但我已经醉心于
        一次把一个完整的文本文件读取到内存中,这是一个很好的使程序员绕
        过心烦问题的例子。如果预计到需要读取到内存的文件过于巨大,可以
        使用mmap(q.v.)来实现相同的效果。*/
    GIOChannel *f  = g_io_channel_new_file(filename, "r", &e);
    if (!f) {
        fprintf(stderr, "failed to open file '%s'.\n", filename);
        return NULL;
    }
    if (g_io_channel_read_to_end(f, &out, NULL, &e) != G_IO_STATUS_NORMAL){
        fprintf(stderr, "found file '%s' but couldn't read it.\n", filename);
        return NULL;
    }
    return out;
}
/*  这是strtok_r的包装器。如果读者是从头看到这里的,应该已经
    熟悉这个while循环,它几乎是必需的。这个函数把从strtok_r所返回的
    结果记录到一个ok_array结构。*/
ok_array *ok_array_new(char *instring, char const *delimiters){
    ok_array *out= malloc(sizeof(ok_array));
    *out = (ok_array){.base_string=instring};
    char *scratch = NULL;
    char *txt = strtok_r(instring, delimiters, &scratch);
    if (!txt) return NULL;
    while (txt) {
        out->elements = realloc(out->elements, sizeof(char*)*++(out->length));
        out->elements[out->length-1] = txt;
        txt = strtok_r(NULL, delimiters, &scratch);
    }
    return out;
}

/* Frees the original string, because strtok_r mangled it, so it
   isn't useful for any other purpose. */
void ok_array_free(ok_array *ok_in){
    if (ok_in == NULL) return;
    free(ok_in->base_string);
    free(ok_in->elements);
    free(ok_in);
}

#ifdef test_ok_array
int main (){
    char *delimiters = " `~!@#$%^&*()_-+={[]}|\\;:\",<>./?\n";
    ok_array *o = ok_array_new(strdup("Hello,  reader. This is text."), delimiters);
    assert(o->length==5);
    assert(!strcmp(o->elements[1], "reader"));
    assert(!strcmp(o->elements[4], "text"));
    ok_array_free(o);
    printf("OK.\n");
}
#endif

啥 你的linux系统中没有glibc

要是没有可以参考下面这个精简版的对strtok进行学习。

加入你有一个字符串Hello, reader. This is text.,需要按照分隔符`` ~!@#$%^&*()_-+={[]}|\;:",<>./?\n`进行分割,分割之后取出被分割的每个字符串。

char **elements;数组指针中的各个指针指向被分割后的各个字符串的起始位置。

注意这里的分割,只是将分隔符位置的字符替换为\0,并不是真的把一个长的字符串截断

#include <string.h>
#include "string_utilities.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h> //abort


ok_array *ok_array_new(char *instring, char const *delimiters){
    ok_array *out= malloc(sizeof(ok_array));
    *out = (ok_array){.base_string=instring};
    char *scratch = NULL;
    char *txt = strtok_r(instring, delimiters, &scratch);
    if (!txt) return NULL;
    while (txt) {
        // 申请一个数组指针,数组指针里面存储的都是指向分隔符替换成'\0'之后的字符的开头
        out->elements = realloc(out->elements, sizeof(char*)*++(out->length));
        out->elements[out->length-1] = txt;
        txt = strtok_r(NULL, delimiters, &scratch);
    }
    return out;
}

/* Frees the original string, because strtok_r mangled it, so it
   isn't useful for any other purpose. */
void ok_array_free(ok_array *ok_in){
    if (ok_in == NULL) return;
    free(ok_in->base_string);
    free(ok_in->elements);
    free(ok_in);
}

int main (){
    int i = 0;

    char *delimiters = " `~!@#$%^&*()_-+={[]}|\\;:\",<>./?\n";
    ok_array *o = ok_array_new(strdup("Hello,  reader. This is text."), delimiters);
    assert(o->length==5);
    assert(!strcmp(o->elements[1], "reader"));
    assert(!strcmp(o->elements[4], "text"));
    for(i = 0; i < o->length; i++)
    {
        printf("o->length[%d] = [%s]\n", i, o->elements[i]);
    }

    ok_array_free(o);
    printf("OK.\n");
}

C缺陷与陷阱
链接:https://pan.baidu.com/s/1QfTa-p2ZlnazDDA92ry_cw
提取码:mnxb
复制这段内容后打开百度网盘手机App,操作更方便哦

微信用户原文链接

"C语言字符串处理库函数大全-简书"是一篇在简书上的教程,介绍了C语言中常用的字符串处理库函数。 该教程详细介绍了C语言中字符串操作的相关函数,包括库函数的使用方法和示例代码。通过该教程,读者可以学习到如何使用C语言中的字符串处理函数来进行字符串的复制、连接、比较、查找、截取等操作。 在这篇教程中,读者可以了解到以下一些常见的字符串处理库函数: 1. strcpy:用于将一个字符串复制到另一个字符串中。 2. strcat:用于将一个字符串连接到另一个字符串的末尾。 3. strlen:用于计算一个字符串的长度。 4. strcmp:用于比较两个字符串的大小。 5. strchr:用于在一个字符串中查找指定字符的第一次出现位置。 6. strstr:用于在一个字符串中查找指定子串的第一次出现位置。 7. strtok:用于将一个字符串按照指定的分隔符分割成多个子串。 8. strncpy:用于将指定长度的字符串复制到目标字符串中。 9. strncmp:用于比较指定长度的两个字符串。 10. sprintf:用于将格式化的字符串输出到一个字符数组中。 这些函数在C语言中非常常用,并且对于字符串的处理非常方便。通过学习和掌握这些字符串处理库函数,可以更加高效地完成C语言程序中的字符串操作。 总之,"C语言字符串处理库函数大全-简书"这篇文章提供了丰富的字符串处理库函数以及示例代码,对于C语言开发者来说是一个很好的参考和学习资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Achou.Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值