这个问题在这里已有答案:
我的程序中有一个奇怪的错误,经过几个小时的调试后,我发现了以下非常愚蠢的行:
int a = b * (c * d * + e)
如果你没有看到它:在d
和e
之间我写了* +
,其中只是一个+
的意图。
为什么这会编译,它实际上意味着什么?
+
被解释为一元加运算符。它只返回其操作数的promoted值。
一元+
返回提升值。
一元-
返回否定:
int a = 5;
int b = 6;
unsigned int c = 3;
std::cout << (a * +b); // = 30
std::cout << (a * -b); // = -30
std::cout << (1 * -c); // = 4294967293 (2^32 - 3)
这是因为+
被解释为一元加号,它将对积分或枚举类型执行整数提升,结果将具有提升操作数的类型。
假设e
是一个整数或无范围的枚举类型,最终会应用积分促销,因为*
将通常的算术转换应用于其操作数,最终为整数类型的整体促销。
从草案C ++标准5.3.1
[expr.unary.op]:
一元+运算符的操作数应具有算术,无范围枚举或指针类型,结果是参数的值。对整数或枚举操作数执行整体提升。结果的类型是提升的操作数的类型。
积分促销在4.5
[conv.prom]部分中介绍,如果变量e
是bool, char16_t, char32_t, or wchar_t
以外的类型且转换等级小于int,那么它将被段落1
覆盖:
如果int可以表示源类型的所有值,则除了bool,char16_t,char32_t或wchar_t之外的整数类型的prvalue(整数转换等级(4.13)小于int的等级)可以转换为int类型的prvalue ;否则,源prvalue可以转换为unsigned int类型的prvalue。
对于一整套案例,我们可以看看cppreference。
在一些情况下,一元加上也可以用来解决歧义,一个有趣的案例来自Resolving ambiguous overload on function pointer and std::function for a lambda using +。
请注意,对于那些答案,参考一元-
和负值,这是误导,因为这个例子显示:
#include <iostream>
int main()
{
unsigned x1 = 1 ;
std::cout << -x1 << std::endl ;
}
这导致:
4294967295
现场直播using gcc on wandbox。
值得注意的是,从 Rationale for International Standard—Programming Languages—C开始,将一元加号添加到C99中以获得一元减号的对称性:
C89委员会从几个实施中采用了一元加,用于对称与一元减。
而且我无法想出一个好的案例,即铸造不足以实现同样的理想促销/转换。我在上面引用的lambda示例,使用一元加号强制将lambda表达式转换为函数指针:
foo( +[](){} ); // not ambiguous (calls the function pointer overload)
可以使用显式强制转换完成:
foo( static_cast<void (*)()>( [](){} ) );
并且可以认为这个代码更好,因为意图是明确的。
值得注意的是Annotated C++ Reference Manual(ARM)它有以下评论:
一元加上是一次历史性事故,一般都没用。
正如他们所解释的那样,(+)和( - )只是用作一元运算符:
Unary operators只对表达式中的一个操作数起作用
int value = 6;
int negativeInt = -5;
int positiveInt = +5;
cout << (value * negativeInt); // 6 * -5 = -30
cout << (value * positiveInt); // 6 * +5 = 30
cout << (value * - negativeInt); // 6 * -(-5) = 30
cout << (value * + negativeInt); // 6 * +(-5) = -30
cout << (value * - positiveInt); // 6 * -(+5) = -30
cout << (value * + positiveInt); // 6 * +(+5) = 30
所以从你的代码:
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int a = b * (c * d * + e)
//result: 2 * (3 * 4 * (+5) ) = 120
为什么编译?它编译因为+
被解析为一元加运算符,而不是加法运算符。编译器尝试尽可能地解析而不生成语法错误。所以这:
d * + e
被解析为:
d
(操作数)*
(乘法运算符)+
(一元加运算符)
e
(操作数)然而,这个:
d*++e;
被解析为:
d
(操作数)*
(乘法运算符)++
(预增量运算符)
e
(操作数)此外,这:
d*+++e;
被解析为:
d
(操作数)*
(乘法运算符)++
(预增量运算符)
+
(一元加运算符)
e
(操作数)请注意,它不会创建语法错误,但会产生“LValue requrired”编译器错误。
为了进一步扭曲这里已经给出的正确答案,如果使用-s标志进行编译,C编译器将输出一个汇编文件,其中可以检查实际生成的指令。使用以下C代码:
int b=1, c=2, d=3, e=4;
int a = b * (c * d * + e);
生成的程序集(使用gcc,为amd64编译)开头于:
movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl $3, -12(%ebp)
movl $4, -8(%ebp)
所以我们可以将各个记忆位置-20(%ebp)识别为变量b,将-8(%ebp)识别为变量e。 -4(%epp)是变量a。现在,计算呈现为:
movl -16(%ebp), %eax
imull -12(%ebp), %eax
imull -8(%ebp), %eax
imull -20(%ebp), %eax
movl %eax, -4(%ebp)
因此,正如其他人回复所评论的那样,编译器只是将“+ e”视为一元正面操作。第一个movl指令将变量e的内容放入EAX累加器寄存器,然后迅速乘以变量d或-12(%ebp)等的内容。
这只是基本的数学。例如:
5 * -4 = -20
5 * +4 = 5 * 4 = 20
-5 * -4 = 20
负*负=正
正*负=负
正*正=正
这是最简单的解释。
减号( - )和加号(+)只是告诉数字是正数还是负数。
d和e之间的+运算符将被视为一元+运算符,它将仅确定e的符号。所以编译器会看到如下声明:
int a = b*(c*d*e) ;