【问题标题】:Confused with how strlen works in C对 strlen 在 C 中的工作方式感到困惑
【发布时间】:2021-11-15 19:48:26
【问题描述】:

我最近一直在学习C,我遇到了一个strlen的源代码,这让我很困惑,我不得不在其他地方查找它,但还不能真正理解它。

strlen:

#include <stdio.h>

int strlen(const char *str)
{
    const char* eos = str; // 1

    while (*eos++); // 2

    return (eos - str - 1); // 3
}

int main()
{
    int len = strlen("Hello");

    printf("Len: %d" , len);

    return 0;
}

我不明白为什么我们使用局部变量 eos 以及为什么我们在一个空的 while 循环中使用它,然后从 strlen 函数返回最后一行?

【问题讨论】:

  • 如果你问我,这不是strlen 的最佳实现。简而言之,eos 会递增,直到找到 \0 终止符。然后将它与原始指针的差作为长度返回。
  • while 循环有“副作用”。它是while (*eos) { eos++; } 的缩写。这有助于使它更有意义吗?此外,eos 可能是“字符串结尾”的标准。
  • Max,您显示的循环与问题中的循环不同(但实际上更好),因为它在找到空终止符后不会增加 eos。

标签: c c-strings strlen pointer-arithmetic


【解决方案1】:

为什么会有一个空的while循环?循环递增eos,直到它指向空终止符之后的位置。表达式*eos++ 是一种非常简洁的方式,它告诉您的计算机获取eos 当前指向的字符值,然后递增eos 使其指向下一个字符。在这个while循环中不需要body。

为什么要使用局部变量?既然我们是递增一个指针(即eos),而且我们在最终计算中还需要一个指向字符串开头的指针(即@ 987654326@),我们不能简单地将str 用于所有内容。我们至少需要一个其他变量来完成这项工作。

最后一行是如何工作的? 表达式eos - str 执行指针减法,因此它计算这两个指针之间的距离并将其作为整数返回。然后我们减去 1 使答案正确。

【讨论】:

    【解决方案2】:

    指针eos 用于沿着字符串前进。 EOS 是 end-of-string 的缩写。 while 循环是空的,因为它不需要做任何事情——因为只需要推进指针。一旦eos 指向空终止符,循环就会退出。然后函数的最后一行从开始指针中减去结束指针,得到eos 移动过去的字符数。最后一个 -1 是为了纠正这样一个事实,即由于后增量运算符,eos 总是比它应该的多一个字符。

    一个不那么令人困惑的实现应该是:

    int strlen(const char *str)
    {
        const char* eos = str; // 1
    
        while (*eos)eos++; // 2
    
        return (eos - str); // 3
    }
    

    【讨论】:

    • 也可以使用for 循环。 const char *eos; for (eos = str; *eos; eos++);
    【解决方案3】:

    字符串是由空字符终止的字符数组;记住,由于 C 中没有明确的字符串类型,如果要对被视为字符串的字符数组进行操作,则需要有一个指向第一个字符的指针。

    话虽如此,eos 变量被初始化为指向字符串的开头(通过将其等同于 str - 请记住,它们都只是指向 char 的指针,因此等同于它们意味着它们指向同样的事情)。

    现在,“空”while 循环有一个副作用。因为在评估条件中使用了增量 (++) 运算符,所以 eos 会递增 - 因为它是一个指针,这意味着在循环的每次迭代中,eos 指向连续的字符。它基本上是沿着字符串“走”。

    这一直持续到循环条件评估为false。而在 C 中,只有空字符的计算结果为假(对于所有可能的字符值;更一般地说,零为假,非零为真)。所以基本上,eos 将在字符串的末尾停止递增。

    现在到return 声明。此时我们有两个变量 - 一个名为 str 的变量仍然指向字符串的开头,另一个名为 eos 的变量指向字符串的结尾。好吧 - 实际上eos 已经溢出,因为在循环条件下完成了递增。在这里准确地说,它指向字符串末尾空字符之后的内存地址。

    所以,通过更多的指针运算,如果我们从eos 中减去str,然后为溢出而减去 1……好吧,我们得到最后一个字符和第一个字符的地址的差异,即是字符串的长度。

    奇怪的是在循环中使用了后自增运算符。如果它被编码为while (*++eos);,即使用 pre-increment 运算符,那么递增将发生在eos 的评估之前,这意味着它不会t 溢出到字符串的末尾,因此无需在 return 语句中减去额外的 1。哦,好吧。

    【讨论】:

    • 使用*++eos 会导致循环无法检测到第一个位置的空字符。需要进行单独的测试。
    • @EricPostpischil 当然 - 好点。
    【解决方案4】:

    对于初学者,函数返回类型应为size_t

    size_t strlen(const char *str);
    

    如果两个指针指向同一个数组的元素,则指向具有较高索引元素的指针与指向具有较低索引元素的指针之间的差产生两个索引之间的元素数。

    例如,如果你有一个类似的数组

    const char s[] = "12";
    

    还有两个指针

    const char *p1 = &s[0];
    const char *p2 = &s[1];
    

    那么区别

    p2 - p1
    

    产生值1

    原函数中的这个指针

    const char* eos = str;
    

    将在传递的字符串中移动,直到找到终止的零字符。

    while (*eos++);
    

    这个循环可以像这样重写

    while ( *eos++ != '\0' );
    

    后缀表达式*esp++的值是指针递增前被指向的字符的值。

    因此,当找到终止零时,指针eos 将指向终止零字符之后的内存,并且循环停止其迭代。

    因此,现在指针 str 指向传递的字符串的开头,而指针 eos 指向终止零字符 '\0' 之后的内存。

    所以区别

    (eos - str - 1)
    

    给出传递的字符串中终止零字符'\0'之前的字符数。

    【讨论】:

      猜你喜欢
      • 2012-08-24
      • 2019-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多