为什么不用+++++ b在C中工作?
int main () { int a = 5,b = 2; printf("%d",a+++++b); return 0; }
此代码给出了以下错误:
错误:左值需要作为增量操作数
但是,如果我把整个a++ +
和++b
,那么它工作正常。
int main () { int a = 5,b = 2; printf("%d",a++ + ++b); return 0; }
第一个例子中的错误是什么意思?
printf("%d",a+++++b);
根据Maximal Munch Rule被解释为(a++)++ + b
! 。
++
(后缀)不计算为lvalue
但它需要其操作数是一个lvalue
。
! 6.4 / 4表示下一个预处理令牌是可以构成预处理令牌的最长字符序列“
编译器分阶段编写。 第一阶段称为词法分析器,将字符转换为符号结构。 所以“++”就像是一个enum SYMBOL_PLUSPLUS
。 稍后,解析器阶段将其转换为抽象语法树,但不能更改符号。 您可以通过插入空格来影响词法分析器(除非用引号括起来,否则结束符号)。
普通的词法是贪婪的(有一些例外),所以你的代码被解释为
a++ ++ +b
解析器的输入是一串符号,所以你的代码就像这样:
[ SYMBOL_NAME(name = "a"), SYMBOL_PLUS_PLUS, SYMBOL_PLUS_PLUS, SYMBOL_PLUS, SYMBOL_NAME(name = "b") ]
解析器认为语法错误。 (基于评论的编辑:语义错误,因为你不能将++应用于一个r值,这是一个++的结果)
a+++b
是
a++ +b
没关系。 你的其他例子也是如此。
词法分析器使用通常所说的“最大蒙克”算法来创建令牌。 这意味着当阅读字符时,它会一直阅读字符,直到遇到不能与已有字符相同的东西(例如,如果它已经读取了数字,那么它就是一个数字,如果遇到一个A
,它知道不能成为数字的一部分,所以它停止并把输入缓冲区中的A
作为下一个记号的开始)。 然后它将该令牌返回给解析器。
在这种情况下,这意味着+++++
会a ++ ++ + b
得到lexed。 由于第一次增量产生一个右值,第二个不能被应用,编译器会给出一个错误。
只要FWIW,在C ++中,你可以重载operator++
来产生一个左值,这可以使它工作。 例如:
struct bad_code { bad_code &operator++(int) { return *this; } int operator+(bad_code const &other) { return 1; } }; int main() { bad_code a, b; int c = a+++++b; return 0; }
编译和运行(尽管它什么都不做)我有方便的(VC ++,g ++,Comeau)的C ++编译器。
您的编译器拼命尝试解析a+++++b
,并将其解释为(a++)++ +b
。 现在,后递增( a++
)的结果不是左值 ,即不能再次递增。
请不要在生产质量程序中编写这样的代码。 想想那些需要解释代码的可怜的家伙。
这个确切的例子在C99标准草案 ( C11中的相同细节 )中有介绍。第6.4
节中的词汇元素 :
如果输入流已经被解析成预处理标记直到给定字符,则下一个预处理标记是可以构成预处理标记的最长字符序列。 […]
这也被称为最大蒙克规则 ,用于词法分析,以避免歧义,并通过采取尽可能多的元素,因为它可以形成一个有效的标记。
该段也有两个例子,第二个是你的问题完全匹配,如下所示:
示例2程序片段x +++++ y被解析为x ++ ++ + y,这违反了增量运算符上的约束,即使解析x ++ + ++ y可能会产生正确的表达式。
这告诉我们:
a+++++b
将被解析为:
a ++ ++ + b
这违反了增量后的约束,因为第一个后增量的结果是一个右值,后增量需要一个左值。 这在第6.5.2.4
节中说明( 强调我的 ) 后缀增量和减量运算符 :
后缀增量或减量运算符的操作数应具有合格或不合格的实数或指针类型,并应为可修改的左值。
和
后缀++运算符的结果是操作数的值。
这本书的C ++陷阱也涵盖了这种情况下Gotcha #17
最大蒙克问题这是在C + +的同样的问题,也给出了一些例子。 它解释了在处理以下字符集时:
->*
词法分析器可以做三件事之一:
- 将它视为三个令牌:
-
,>
和*
- 将其视为两个令牌:
->
和*
- 把它当作一个令牌:
->*
最大的蒙克规则允许它避免这些歧义。 作者指出它( 在C ++上下文中 ):
解决了更多的问题,但是在两种常见的情况下,这是一个烦恼。
第一个例子是模板参数也是模板的模板( 在C ++ 11中解决 ),例如:
list<vector<string>> lovos; // error! ^^
这将解释闭角尖括号作为移位运算符 ,因此需要一个空格来消除歧义:
list< vector<string> > lovos; ^
第二种情况涉及指针的默认参数,例如:
void process( const char *= 0 ); // error! ^^
将被解释为*=
赋值运算符,这种情况下的解决方案是在声明中指定参数。
(a++)++ +b
a ++返回以前的值,一个右值。 你不能增加这个。
因为它会导致未定义的行为。
哪一个?
c = (a++)++ + b c = (a) + ++(++b) c = (a++) + (++b)
是的,你和编译器都不知道。
编辑:
真正的原因是其他人所说的:
它被解释为(a++)++ + b
。
但后递增需要一个左值(这是一个名称的变量),但(一个++)返回一个右值不能增加,从而导致你得到的错误消息。
向其他人指出这一点。
我认为编译器将其视为
c =((a ++)++)+ b
++
必须有一个可以修改的值作为操作数。 a是一个可以修改的值。 然而, a++
是一个“右值”,它不能被修改。
顺便说一下,我在GCC C上看到的错误是相同的,但措辞不同: lvalue required as increment operand
。