【问题标题】:How do I split a string without strtok?如何在没有 strtok 的情况下拆分字符串?
【发布时间】:2016-10-31 01:39:19
【问题描述】:

我是 C 的初学者,请不要抨击我。 所以,我有这个函数可以获取“ip/mask”类型的字符串的掩码:

char *getmask(char n[]) {
    char x[255] = { 0 };
    strcpy(x, n);
    char *mask;
    mask = strtok(x, "/");
    mask = strtok(NULL, "/");
    return mask;
}

问题是我需要在一个字符串中的多个“ip/mask”上执行此操作。所以当我这样做时:

net = strtok(x4, " ");
net = strtok(NULL, " ");
while (net != NULL) {
    net = strtok(NULL, " ");
    strcpy(masca, "\n");
    strcpy(masca, getmask(net));
    //some other code
}

问题是 strtok() 出现故障,因为我在此期间首先调用它,但随后在 getmask(net) 中再次调用它。

无论如何要绕过它?如果没有,我还能如何拆分字符串?

【问题讨论】:

  • 你是从net=strtok(NULL," ")开始的吗?问题描述令人困惑。显示具有输入和输出的最小程序。
  • 对不起,第一个 strtok 在 while 之前。我会编辑
  • strtok 作为状态机运行,因此每个调用都带有状态。为了拆分一系列以空格分隔的标记和/ 分隔的标记的子集,您需要防止状态发生冲突。这可以通过迭代两次来完成,或者如@Ludonope 所述
  • @Aaron3468 所以我猜我不能使用 getmask 函数,而是我可以先 strtok(NULL," ") , strtok(NULL,"/") 获取 ip 然后strtok(NULL,"/") 再次返回掩码?
  • 基本上一旦你调用strtok,它就会在你分配给它的最后一个字符串上继续,直到它到达那个字符串的末尾。所以在你完成第一个字符串之前,你不能“喂”它新的字符串。在中途开始一个新字符串是不安全的,因为strtok 会将 NULL 字符添加到原始字符串中。有更好的选择; strtok_r 是最明显的,但您也可以使用 strpbrk 之类的函数来实现相同的行为(但需要更多工作)。

标签: c string strtok c-strings


【解决方案1】:

使用 strtok_r()。它与 strtok 的行为相同,但允许您“同时”处理多个字符串。

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

strtok_r() 函数是一个可重入版本的 strtok()。 saveptr 参数是一个指向 char * 变量的指针,strtok_r() 内部使用该变量来维护解析相同字符串的连续调用之间的上下文。

在第一次调用 strtok_r() 时,str 应该指向要解析的字符串,并且忽略 saveptr 的值。在随后的调用中,str 应为 NULL,并且 saveptr 应自上次调用以来保持不变。

可以使用指定不同 saveptr 参数的 strtok_r() 调用序列同时解析不同的字符串。

来源:Linux 人strtok_r

【讨论】:

  • 恐怕使用strtok_r(),它可能在 OP 的系统上不可用,不会修复 getmask() 中的明显缺陷:将指针返回到本地数组 x 导致未定义的行为。
  • 尝试编写一个类似的函数,它需要一些字符串操作,但这并不难:)
【解决方案2】:

您的函数getmask() 调用未定义的行为:

  • 您将字符串参数复制到本地数组x
  • 您使用 strtok() 解析它,它返回指向同一本地数组 x 的指针。
  • 您将此指针mask 返回给调用者。一旦你退出这个函数,这个指针就失效了。

您应该返回一个指向此数组的已分配副本的指针,以便在您返回给调用者后它仍然有效。

此外,您确实应该避免使用strtok(),因为它不可重入:正如您所注意到的,您无法使用此函数实现嵌套解析器。

其他函数可用于解析字符串:

  • strchr() 定位字符串中的一个字符;
  • strstr() 定位字符串中的子字符串;
  • strspn() 匹配字符串开头的一组字符;
  • strcspn() 匹配字符串开头的一组字符的补码;
  • 您也可以手动解析字符串,循环测试字符。

这是一个例子:

#include <stdlib.h>
#include <string.h>

char *getmask(const char *s) {
    /* allocate a copy of the mask part
     *  the mask starts after the first '/'
     *  and stops at the first space or another '/'
     */
    len - 0;
    s = strchr(s, '/');
    if (s != NULL) {
        s++;
        len = strcspn(s, "/ \t\r\n");
    }
    /* allocate space for the mask string */
    char *mask = malloc(len + 1);
    if (mask != NULL) {
        /* copy the mask string */
        memcpy(mask, s, len);
        mask[len] = '\0';
    }
    return mask;
}

函数虽然繁琐但非常精确。它的行为与您对strtok() 的预期几乎相同,唯一的区别是处理多个连续的/ 字节,strtok() 会跳过而strchr() 不会。

这是sscanf()的替代方案:

#include <stdlib.h>
#include <string.h>

char *getmask(const char *s) {
    char mask[256];
    /* skip characters different from /, then skip slashes, then
     * copy characters until another / or whitespace
     */
    if (sscanf(s, "%*[^/]%*[/]%255[^/ \t\n]", mask) != 1) {
        *mask = '\0';
    }
    return strdup(mask);  /* POSIX function to allocate a copy of a string */
}

这要简单得多,但如果字符串以/ 开头,则会失败。

strdup() 是一个非常有用的函数,用于分配字符串的副本。它在 POSIX 兼容系统上可用。如果你没有它,它可以很容易地实现为:

#include <stdlib.h>
#include <string.h>

char *strdup(const char *s) {
    char *p = malloc(strlen(s));
    if (p != NULL) {
        strcpy(p, s);
    }
    return p;
}

getmask() 分配的字符串在不再需要时应由free() 释放。

在调用getmask()之前,您可以使用类似的方法将输入字符串解析为ip/掩码地址对。

您还可以为getmask() 提供目标缓冲区以避免内存管理的复杂性:

char *getmask(char *dest, size_t size, const char *s) {
    if (dest != NULL && size > 0) {
        char mask[256];
        /* skip characters different from /, then skip slashes, then
         * copy characters until another / or whitespace
         * dest cannot be used directly because size cannot be passed
         * sscanf easily
         */
        *dest = '\0';
        if (sscanf(s, "%*[^/]%*[/]%255[^/ \t\n]", mask) != 1) {
            strncat(dest, mask, size - 1);
        }
    }
    return dest;
}

解析很棘手,因为您必须小心处理所有情况。规范通常不够精确,因此实现者必须针对特殊情况做出选择。 C 库提供的解析工具老旧笨重,尤其是strtok()sscanf()。使用这些时要小心,即使是经验丰富的程序员也会被它们的副作用和缺点所困扰。

【讨论】:

    【解决方案3】:

    strtok() 如果有空值,则不能用于逗号分隔的字符串,例如“一、二、、四、五、、、八”。

    使用strtok(token,",") 将返回“一”、“二”、“四”...您可能认为在“二”之后会返回一个空字符串“”。这让我在尝试解析 GPS 接收器的输出时感到困惑。

    请改用 strsep(),它像 strtok_r() 一样是线程安全的,但会返回零长度字符串,即前面示例中的 "one","two","","four"...。

    strsep() 可能不在某些运行时库中,但您可以通过在线搜索找到实现。

    【讨论】:

      猜你喜欢
      • 2021-08-12
      • 1970-01-01
      • 2012-09-06
      • 2023-03-15
      • 1970-01-01
      • 2018-01-03
      • 1970-01-01
      • 2020-04-09
      相关资源
      最近更新 更多