“”+在C ++中的东西

我一直在我的代码中发生非常怪异的东西。 我相信我已经把它追踪到标有“here”的部分(当然,代码是简化的):

std::string func() { char c; // Do stuff that will assign to c return "" + c; // Here } 

当我试图说明这个function的结果时,会发生各种各样的事情。 我想我甚至设法得到一些底层的C ++文档,还有许多分段错误 。 我很清楚,这不适用于C + +(我已经诉诸使用stringstream转换string现在),但我想知道为什么。 在使用了很多C#之后,没有C ++,这让我感到很痛苦。

  • ""是一个string文字。 那些有N const char的types数组 。 这个特定的string文字是一个1个const char数组 ,一个元素是空终止符。

  • 数组很容易衰减成指向它们第一个元素的指针,例如在需要指针的expression式中。

  • lhs + rhs没有为数组定义为lhs和整数为rhs 。 但是它是通过指针算术将指针定义为lhs和整数作为rhs。

  • char是C ++核心语言中的一个整型数据types(即被视为一个整数)。

==> string文字+字符因此被解释为指针+整数

expression"" + c大致相当于:

 static char const lit[1] = {'\0'}; char const* p = &lit[0]; p + c // "" + c is roughly equivalent to this expression 

你返回一个std::string 。 expression式"" + c产生一个指向const char指针 。 需要const char*std::string构造const char*期望它是一个指向以null结尾的字符数组的指针。

如果c != 0 ,则expression式"" + c导致未定义的行为:

  • 对于c > 1 ,指针运算产生未定义的行为。 指针运算只在数组上定义,如果结果是同一个数组的元素。

  • 如果char被签名,那么出于同样的原因, c < 0会产生Undefined Behavior。

  • 对于c == 1 ,指针运算不会产生未定义的行为。 这是一个特例。 指向数组的最后一个元素之后的一个元素是允许的(虽然不允许使用它指向的元素)。 它仍然会导致未定义行为,因为这里调用的std::string构造函数要求它的参数是一个指向有效数组的指针(和一个以null结尾的string)。 过去的最后一个元素不是数组本身的一部分。 违反这个要求也导致UB。


现在可能发生的情况是, std::string的构造函数试图通过search数组中等于'\0'的(第一个)字符来确定传递给它的以null结尾的string的大小:

 string(char const* p) { // simplified char const* end = p; while(*end != '\0') ++end; //... } 

这会产生访问冲突,或者它创build的string包含“垃圾”。 编译器假定这种“未定义的行为”永远不会发生,并且会进行一些有趣的优化,这会导致奇怪的行为。


顺便说一句, 铿锵+3.5发出一个很好的警告这个片段:

警告:向string添加'char'不会附加到string[-Wstring-plus-int]

 return "" + c; // Here ~~~^~~ 

注意:使用数组索引来消除这个警告

关于编译器如何解释这段代码有很多解释,但是你可能想知道的是你做错了什么。

你似乎期待从std::string+行为。 问题是这两个操作数实际上都不是一个std::string 。 C ++查看操作数的types,而不是expression式的最终types(这里是返回types, std::string )来parsing重载。 如果它没有看到一个std::string ,它将不会selectstd::string的版本。

如果你对某个操作符有特殊的行为(不pipe是你写的,还是有一个提供它的库),那么这个行为只适用于至less有一个操作数具有类types(或对类types的引用,以及用户定义的枚举数太)。

如果你写了

 std::string("") + c 

要么

 std::string() + c 

要么

 ""s + c // requires C++14 

那么你将得到operator +的std::string行为。

(请注意,这些实际上都不是很好的解决scheme,因为它们都使std::string(1, c)可以避免的短期std::string实例)

function也一样。 这是一个例子:

 std::complex<double> ipi = std::log(-1.0); 

你会得到一个运行时错误,而不是预期的虚数。 这是因为编译器不知道它应该在这里使用复数对数。 重载仅看参数,参数是一个实数(实际上是doubletypes)。

运算符重载ARE函数并遵守相同的规则。

这个返回语句

 return "" + c; 

已validation。 有所谓的指针算术。 string文字“”被转换为指向其第一个字符的指针(在本例中为其终止零),存储在c中的整数值被添加到指针。 所以expression的结果

 "" + c 

有typesconst char *

类std :: string有接受types为const char *参数的转换构造const char * 。 问题是,这个指针可以指向超出string文字。 所以这个函数有不确定的行为。

我没有看到使用这个expression式的任何意义。 如果你想build立一个基于一个字符的string,你可以写例如

 return std::string( 1, c ); 

C ++和C#的区别在于,在C#中,string文字的typesSystem.String已经重载了string和字符(即C#中的Unicode字符)的运算符+。 在C ++中,string是常量字符数组,而数组和整数的运算符+的语义是不同的。 数组被转换为指向其第一个元素的指针,并使用指针算术。

它是标准的类std :: string,它已经为字符重载了operator +。 C ++中的string文字不是types为std :: string的对象。