【问题标题】:What is the difference between these two cases of adding a string?这两种添加字符串的情况有什么区别?
【发布时间】:2015-09-13 22:26:05
【问题描述】:

我注意到当我初始化一个字符串时,编译器报告了一个我没有预料到的错误。

例如:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s1 = "Hello", s2 = "World!"; // ok
    string s3 = s1 + ", " + "World!"; // ok
    string s4 = "Hello" + ", " + s2; // error
    cout << s1 + " " + s2 << endl; //ok
    return 0;
}

对我来说,如果 s3 工作得很好,s4 也应该这样做。

为什么会出现该错误?这两个初始化字符串(s3s4)有什么区别?

【问题讨论】:

  • 这只是C++的一点不便。您可以使用std::string() + "Hello" + ... 使其编译而不会造成重大损失。
  • 如果您来自其他语言背景,您会惊讶地发现"Hello" 不是string。这就是你所有(不仅仅是)麻烦的原因。
  • @NeilKirk 这有点奇怪,但感谢您提供的信息。你知道为什么会这样吗?
  • @Walter 现在你真的引起了我的注意。你能多谈谈吗?
  • 嗯,它是一个“字符串”,这取决于您所说的“字符串”是什么意思。它是存储在字符数组const char[6] 中的C 风格字符串。但它不是std::string

标签: c++ string c++11


【解决方案1】:

"Hello" 不是std::string(而是const char[6],而", "const char[3])并且++ 运算符不适用。

这是 C++ 的一个小不便,源于它的 C 祖先。这意味着在通过+ 连接字符串时,您必须确保其两个 操作数中的至少一个实际上是std::string,如

auto s = std::string{"Hello"} + ", " + "World";

其中第一个+ 有一个std::string 作为它的第一个操作数,因此产生一个std::string,所以第二个+ 也有一个std::string 作为它的第一个操作数(因为+ 被处理从左到右)。


Edit1T.C. 的评论提示,我提到如果仅用空格分隔,C 字符串文字会自动连接:

std::string s = "Hello" ", " "World";

这种行为也继承自 C:预处理器将上面的代码呈现给

std::string s = "Hello, World";

在编译器正确处理它之前(更准确地说,字符串连接发生在翻译的phase 6 中,就在编译之前)。这实际上是连接原始字符串文字的最简单也是最方便的方法。但请注意,我必须声明s 的类型,因为auto 的扣除会给出const char*


Edit2PaperBirdMaster 的评论提示,我提到从C++14 开始,您可以通过在字符串后面简单地添加s 来直接形成std::string 文字,如果你拉入关联的operator""s(或周围的命名空间)。

using std::literals::operator""s;                // pull in required operator
const auto s = "Hello"s + ", " + "World";        // std::string

请参阅this post,了解为什么所需的operator""s 隐藏在嵌套命名空间中。另请注意,除了using std::literals::operator""s;,您还可以拉入周围的namespace:以下任一声明都可以。

using namespace std::string_literals;
using namespace std::literals::string_literals;
using namespace std::literals;

没有普通的using namespace std;那么糟糕(和该死)。

【讨论】:

  • 这以非常清晰和快速的方式解释了很多。谢谢你,@Walter!
  • 或者,你知道,放弃+,让字符串文字连接发挥它的魔力:)
  • 我认为值得一提的是UDL的使用,以下是有效的:string s4 = "Hello"s + ", " + s2;只要我们是using namespace std::string_literals;(仅限C++14)
  • @bku_drytt 我的回答是/是正确的,尽管对于不知情的人来说这可能并不明显。关键是operator+ 正好有2 个参数,其中至少一个必须是std::string。我澄清了示例代码中操作数的含义的答案。
  • 至于连接原始字符串文字,在翻译的“第 6 阶段”中完成,here 我们已经解释了所有阶段。
【解决方案2】:

下一行尝试将 2 个指针相加。

string s4 = "Hello" + ", " + s2; // error

"Hello" 是一个const char[6],它衰减为const char*", " 是一个常量char[3],它衰减为const char*

您试图将两个指针相加分配给std::string,这是无效的。此外,两个指针相加也是无效的。

正如其他人所说,这与std::string 类的operator+ 的重载无关,因为同样的原因,以下代码也无法编译:

string s4 = "Hello" + ", ";

我添加了用户 @dyp 提供的一些说明。

您可以通过使用 C++ 标准库“用户定义”字符串文字来解决您的问题。该功能由std::literals::string_literals::operator""s 提供,它位于&lt;string&gt; 标头中。您可能必须使用 using-directive 或 -declaration;这在我的编译器上不是必需的,包含 &lt;string&gt; 标头就足够了。示例:

string s4 = "Hello"s + ", "s + s2;

这相当于:

string s4 = std::string("Hello") + std::string(", ") + s2;

当您将 std::string 的实例作为第一个操作数时,表达式的其余部分将使用该类型,因为 operator+ 的从左到右关联性将确保 std::string 的实例作为左侧返回- 每次最多的操作数,而不是const char*。这就是你在线没有错误的原因:

string s3 = s1 + ", " + "World!";

那一行相当于:

string s3 = ((s1 + ", ") + "World!");

【讨论】:

  • 谢谢你@bku_drytt,现在你的答案对我来说很清楚了!
  • 感谢您的评论,我已更新我的答案以包含您的知识。
  • std::literalsstd::literals::string_literals 都是内联命名空间,因此使用指令using namespace std; 足以使operator""s 可见。
【解决方案3】:

字符串和 C 字符串

在 C++ 中,字符串通常表示为 std::stringC-string,其类型为 char[N],其中 N 是 C 字符串中的字符数加一作为终止字符(或 null字符)\0.

当将字符串文字如 eg"Hello" 传递给函数时,您实际上是在传递一个 char[6],它将 衰减 为指针 @987654331 @,因为 C++ 不允许您按值传递数组。此外,C++ 不允许 允许指向字符串文字的非常量指针,因此参数类型必须变为 const char*

串联

std::string 具有现有的 binary addition operator 重载,允许您将两个 std::string 对象连接为一个,以及将 std::string 对象与 C 字符串连接。

std::string s1 = "Hello";
std::string s2 = "World";

// Uses 'std::string operator+(const std::string&, const std::string&)'.
std::string s3 = s1 + s2;

// Uses 'std::string operator+(const std::string&, const char*)'.
std::string s4 = s1 + "World";

// Also ok.
std::string s4 = "Hello" + s2;

但是,连接两个 C 字符串没有重载。

// Error. No overload for 'std::string operator+(const char*, const char*)' found.
std::string s5 = "Hello" + "World";

从左到右

在 C++ 中执行 left to right 加法,因此,长连接表达式需要以 std::string 对象开头。

std::string s6 = "Hello" + ", " + s2; // Error. Addition is performed left-to-right and
                                      // you can't concatenate two C-strings.

std::string s7 = s1 + ", " + "World"; // Ok. 's1 + ", "' returns a new temporary
                                      // 'std::string' object that "World" is concat'ed to.

一个简单的解决方案

使用 C++14 标准文字,您可以从文字表达式创建 std::string 对象(注意文字表达式中的尾随 s)。

using namespace std::literals;
std::string s8 = "Hello"s + ", " + "World"; // Ok

您也可以使用预处理器自动连接用空格分隔的文字字符串。

std::string s9 = "Hello" ", " "World"; // Ok

但是,您也可以完全跳过空格并使用单个字符串文字。

【讨论】:

  • 感谢@Snps 的贡献,多了解 C++14 总是好的!
【解决方案4】:

首先要了解的是,由于它在 C 中的祖先,像 C++ 中的"Hello" 这样的字符串文字实际上不是类型为string。它的类型实际上是一个字符数组。

所以s3s4 的区别在于,当你进行s3 构造时,首先完成的操作是s1 + ", "s1是字符串类型,所以使用标准库的字符串组件提供的+操作符,返回一个新的字符串添加到"World!"中。

现在对于s4,要执行的第一个操作是"Hello" + ", " 或添加两个字符串文字。这个操作实际上会被编译器视为将两个字符数组加在一起,这是语言不提供的功能,因此会导致您看到的错误。

最简单的解决方法是不要将+ 用于字符串文字,只需将它们放在同一个字符串中即可。或者,您可以从第一个文字创建 string 并让操作从那里开始 (std::string("Hello") + ", " + s2)

【讨论】:

    【解决方案5】:

    + 运算符在 std::string 中被重载。 运算符重载应用于操作数,因此 s1 + ... 通过在 s1 上调用重载的 + 运算符来工作。

    文字"Hello"const char *"," 也是。 const char * 没有重载的 + 运算符,因此出现错误。 试试string("Hello") + ...,它会起作用的。

    感谢@interjay 的更正

    【讨论】:

    • 运算符重载不仅仅应用于左操作数。还有一个const char * + std::string 过载。这里的问题是尝试添加两个const char *
    • @interjay 说了什么。 string s4 = "Hello" + s2; 也可以。
    • "Hello" 不是const char *。这是一个const char[6],可以衰减到const char *
    猜你喜欢
    • 1970-01-01
    • 2020-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多