【问题标题】:How does the std::string constructor handle char[] of fixed size?std::string 构造函数如何处理固定大小的 char[]?
【发布时间】:2019-03-22 02:03:31
【问题描述】:

char[] 中的实际字符序列可能小于最大大小时,字符串构造函数如何处理固定大小的char[]

char foo[64];//can hold up to 64
char* bar = "0123456789"; //Much less than 64 chars, terminated with '\0'
strcpy(foo,bar); //Copy shorter into longer
std::string banz(foo);//Make a large string

在此示例中,banz 对象字符串的大小将基于原始 char* 长度还是它被复制到的 char[]?

【问题讨论】:

    标签: c++ string c++11


    【解决方案1】:

    首先您必须记住(或知道)C++ 中的 char 字符串实际上称为 null-terminated 字节字符串。 null-terminated 位是一个特殊字符 ('\0'),表示字符串的结尾。

    您必须记住(或知道)的第二件事是数组自然衰减为指向数组第一个元素的指针。对于您示例中的foo,当您使用foo 时,编译器确实会执行&foo[0]

    最后,如果我们看看例如this std::string constructor reference 你会看到有一个重载(编号 5)接受 const CharT*CharTchar 对于普通的 char 字符串)。

    把它们放在一起,用

    std::string banz(foo);
    

    您将指针传递给foo 的第一个字符,std::string 构造函数会将其视为以空值结尾的字节字符串。通过找到空终止符,它知道字符串的长度。数组的实际大小无关紧要,未使用。

    如果要设置std::string 对象的大小,则需要通过传递长度参数(构造函数参考中的变体4)显式进行:

    std::string banz(foo, sizeof foo);
    

    这将忽略空终止符并将banz 的长度设置为数组的大小。请注意,空终止符仍将存储在字符串中,因此将指针(由例如c_str 函数检索)传递给需要空终止字符串的函数,则字符串看起来很短。另请注意,空终止符之后的数据将未初始化并具有不确定内容。您必须在使用该数据之前对其进行初始化,否则您将拥有undefined behavior(在 C++ 中,即使读取不确定的数据也是 UB)。


    正如 MSalters 的评论中所述,读取未初始化和不确定数据的 UB 也用于使用显式大小构造 banz 对象。它通常会起作用并且不会导致任何问题,但它确实违反了 C++ 规范中规定的规则。

    虽然修复它很容易:

    char foo[64] = { 0 };//can hold up to 64
    

    上面会将数组的all初始化为零。下面的strcpy 调用不会触及超出终止符的数组数据,因此将初始化数组的其余部分。

    【讨论】:

    • 可能值得强调的是“您必须在使用之前初始化该数据过去\0”,还包括在`std::string banz(foo , sizeof foo);``
    • @Caleth 我知道 + 对答案的最后一次编辑,我认为不再有可能出错,评论已删除
    • 我不认为阅读不确定字符不是 UB;其他类型则不然。
    • @Yakk-AdamNevraumont:在你的第一句话中,你有双重否定。这是英文的UB。
    • 您也可以在较新的标准中使用char foo[64] = {};;与 C 的重要区别。
    【解决方案2】:

    调用的构造函数是以const char* 作为参数的构造函数。该构造函数尝试复制该指针指向的字符数据,直到到达第一个 NUL 终止符。如果没有这样的 NUL 终止符,那么构造函数的行为是 undefined

    您的foo 类型通过指针衰减 转换为char*,然后在调用站点发生到const char* 的隐式转换。

    也许有一个模板化的std::string 构造函数将const char[N] 作为参数,这将允许插入多个NUL 字符(std::string 类毕竟支持这一点),但它没有引入,现在这样做将是一个突破性的变化;使用

    std::string foo{std::begin(foo), std::end(foo)};
    

    还将复制整个数组foo

    【讨论】:

    • cppreference 提供了一个带有 char* 和 int 的构造函数,以从数组中生成字符串而不是 c 样式的字符串
    • @stefaanv 也有一个char *, int构造函数以及这里提到的char *构造函数。但没有从template <size_t N> foo(char (&str)[N])foo(char *, size_t) 的自动转换
    • @stefaanv:我认为规范的 C+11 方式是 std::string foo{ std::begin(array), std::end(array) };
    猜你喜欢
    • 2015-04-26
    • 1970-01-01
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-04
    相关资源
    最近更新 更多