在C++中获取const char*的长度的最佳方法

22

我知道两种方法来获取const char *的长度

const char * str = "Hello World !";
int Size = 0;
while (str[Size] != '\0') Size++;

另一种方法非常简单

const char * str = "Hello World !";
size_t Size = strlen(str);

但我不想使用 strlen 等字符串库函数,而且我认为这个函数也会像我的第一种方法一样行事。因为在 PC 世界中,当我们想要计算某些东西时,我们需要逐个计数每个块,并且没有什么神奇的方法可以一次性获取长度,所以我认为第一种方法是获取 const char * 长度的最佳选项。另一方面,我认为第一种方法可能对于大字符串来说太重了。所以我很困惑,哪种方法更好?为什么另一种方法不好呢?


5
标准库中的函数通常比你自己编写的相同功能的代码更快,并且肯定会更少出现错误。 - Mark Ransom
2
这两个示例是等效的,但是 strlen 可能会更快,因为它具有编译器特定的优势。无论如何,最好使用已经为您编写的代码。 - Etienne de Martel
1
为什么你不想使用 strlen() 函数? - Galik
8
既然这是 C++,只需使用 std::string,然后就可以使用它的 size() 方法。 - Etienne de Martel
1
或者使用新的std::string_view与您的C字符串一起使用。 - user2672107
1
const char* 是一个指针。它本身没有长度。如果它指向以空字符结尾的 char 数组,你可以谈论该数组的长度。不要混淆指针和数组;这会导致无尽的混乱。 - Pete Becker
3个回答

38

让我们检查这两个方法的汇编列表。

#include <cstddef>
#include <cstring>

int string_size_1()
{
    const char * str = "Hello World !";
    int Size = 0;
    while (str[Size] != '\0') Size++;
    return Size;
}

int string_size_2()
{
    const char * str = "Hello World !";
    size_t Size = strlen(str);
    return Size;
}

使用标志 -std=c++14 -O2 的 Clang 4.0.0。

string_size_1():                     # @string_size_1()
        mov     eax, 13
        ret

string_size_2():                     # @string_size_2()
        mov     eax, 13
        ret

链接:https://godbolt.org/g/5S6VSZ

两种方法最终生成完全相同的汇编代码。同时,编译器会优化掉一切不必要的代码并返回一个常量,因为字符串文字在编译时已知。因此,从性能上来说,它们是同样好的。

但从可读性的角度来看,strlen(str) 明显更好。函数调用通过函数名表明了意图,而循环则不能做到这一点。


此外,在许多情况下,std::stringstd::string_view 比 C 字符串更可取。请考虑使用它们。


1
如果你在字符串中放置\0,你会得到相同的编译代码吗?那将是一个错误。 - Mark Ransom
这是一些相当奇怪的代码生成方式,但我认为它最终会得出正确的答案。 - Mark Ransom
@MarkRansom 这两个函数都计算数字13,你为什么认为优化编译器会认为这很奇怪? - Caleth
@Caleth 第一个示例比我预期的优化器在编译时要复杂一些,但是老实说,我不记得为什么会在4年前留下那个评论了。 - Mark Ransom

3
在这种情况下,答案在编译时已知:
template <std::size_t S>
constexpr std::size_t string_length
(
    char const (&)[S]
)
{
    return S - 1;
}

用法:

std::cout << string_length("example") << std::endl;

当字符串不是编译时常量时,如果只有字符串指针可用,请使用strlen;如果开始和结束的两个指针都可用,请使用std::distance;如果你正在处理std::string,则使用.size()。


他们特别要求输入为 const char *,而你的代码不能使用。 - M.M
我的回答也涉及了char *。他的例子也处理了char []类型,但只是通过不使用auto来强制衰减。问题和答案的精神都很清楚。 - Michael Maniscalco

3

虽然晚了3年,但总比没有好。

简短回答

#define length(array) ((sizeof(array)) / (sizeof(array[0])))

长回答

因此,使用 sizeof(array) 会返回 数组类型的大小 * 元素数量。了解了这点,我们可以实现以下操作:

#define length(array) ((sizeof(array)) / (sizeof(array[0])))

你可以像这样使用它:

type yourArray[] = {your, values};
length(yourArray);    // returns length of yourArray

例如:
#include <stdlib.h>
#include <stdio.h>

#define length(array) ((sizeof(array)) / (sizeof(array[0])))


int main()
{
    const char *myStrings[] = {"Foo", "Bar", "Hello, World!"};    // 3 elements
    int myNums[] = {0, 1, 5, 7, 11037};    // 5 elements
    char myChars[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};    // 7 elements

    printf("Length of myStrings array: %lu\n", length(myStrings));
    printf("Length of myNums array: %lu\n", length(myNums));
    printf("Length of myChars array: %lu\n", length(myChars));

    return 0;

    /* Output:
           Length of myStrings array: 3
           Length of myNums array: 5
           Length of myChars array: 7 */
}

我测试过它,而且它也可以与未初始化的数组一起使用,可能是因为它们包含了同一类型的垃圾(来自未初始化)。整数未初始化数组包含随机整数,const char*未初始化数组包含(null),这被视为const char*。
现在,这只适用于栈上的数组。指向堆中预留空间用作数组的指针会产生意想不到的结果。例如:
int *myNums = (int *)malloc(3 * sizeof(int));    // Space for 3 integers
printf("Length of myNums: %lu\n", length(myNums));  // Outputs 2 instead of 3

所以请注意。谁会在堆上使用数组呢,反正不要管。

注意:这与此问题相关,因为它可以像请求的那样使用const char *。它也适用于其他类型。


1
当输入 const char * str = "Hello World !"; length(str) 时,这种方法无法给出期望的答案13。而且,“未初始化的整数数组包含随机整数,未初始化的 const char* 数组包含 (null),被视为 const char*。” 这种说法是误导性的。读取未初始化值的程序具有未定义行为,您可能会观察到(在某个时间点)存在任意值的情况。其他症状也是可能的。 - Caleth
@Caleth 这是因为这个宏的目的是获取堆栈中数组的长度,而不是指针。对于 const char *str[] = {"Hello World !"};,它返回1的正确长度。无论如何,我刚意识到我误解了问题,所以那是我的错。谢谢你指出来。 - Clara
是的,所以这并没有回答提出的问题。 - Caleth
这并没有真正帮助。最好的方法是在我们仍然知道它的点处提供大小的附加参数。 - Attis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接