a++,++a再解析

文章目录


问题

我们都知道:

  • ++a,是先增后用
  • a++,是先用后增

那么做一些下面这道题。

    public static void main(String argc[]){
        int a=0;
        a=a++;
        System.out.println(a);
    }

由于a++是先用后增,于是a的值0先用,赋给a为0,然后a后增,此时a的值为1。所以结果就为1了。

相信很多人都会得出1这个结果,可结果是:0

在开始解析之前,我们先说一下为什么。

  • ++a,先增后用,增是直接操作占局部变量表的,后用是将变量a的值放入操作数栈中。对应与iinc指令。
  • a++,先用后增。先用是把a的值放到操作数栈中,后增是将局部变量表中a的值加一。

解析

a++

    public static void main(String argc[]){
        int a=0;
        a=a++;
        System.out.println(a);
    }

使用javap -verbose反汇编的结果。

 		 0: iconst_0
         1: istore_1
         2: iload_1 // 先用
         3: iinc          1, 1 // 后加
         6: istore_1 //赋值算符=
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_1
        11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        14: return

每一个函数的调用都伴随这一个栈帧的入栈与出栈。栈帧中有操作数栈、局部变量表等结构。栈帧结构可以参考虚拟机字节码执行引擎——运行时栈帧结构

  • iconst_0。将一个常量0添加到操作数栈中。
    在这里插入图片描述
图1 iconst_0
  • istore_1。将一个操作数栈顶数0拿出来放到局部变量表第1个slot槽中。
    在这里插入图片描述
图2 istore_1
  • iload_1。将局部变量表中槽1的局部变量的值0放到操作数栈中。
    在这里插入图片描述
图3 iload_1
  • iinc 1, 1。将局部变量表中槽1的值加1,变量a的值为1。对应的是a++指令。
    在这里插入图片描述
图4 iinc 1, 1
  • istore_1。将操作数栈顶元素0拿出来放到局部变量表中槽1位置,意思是a的值1被覆盖了。其实这条指令对应与a=a++中的=
    在这里插入图片描述
图5 istore_1

++a

++a代码。

class A{
    public static void main(String argc[]){
         int a=0;
        a=++a;
        System.out.println(a);//1
    }
}

使用javap -verbose反汇编的结果。

 0: iconst_0
         1: istore_1
         2: iinc          1, 1 // 先加
         5: iload_1 // 后用
         6: istore_1 // 算符=
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_1
        11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        14: return

例子
public static void main(String argc[]){
int j=0;
for(int i=0;i<100;++i){
j=j++;
}
System.out.println(j);
}
这个例子的值为0

总结

++a。先增有用。增是直接操作局部变量表中的a,让a的值加1(iinc操作的不是操作数栈,而是局部变量表)。用是将局部变量表中的a的值放入操作数栈定。
a++。先用后增。用是将局部变量表中a的值放入操作数栈顶,增是将局部变量表中a的值加一。

08-13
<think>我们正在讨论C语言中连续运算符的解析问题。用户之前询问了`a---b`的运算次序,现在询问`a+++++b`的解析规则。 首,我们需要理解C语言的词法分析规则:编译器会尽可能将多个连续的字符组合成最长的有效运算符。因此,在`a+++++b`中,编译器会尝试将连续的加号组合成`++`运算符(自增运算符)和`+`运算符(加法运算符)。 现在,我们分解表达式`a+++++b`: - 第一个`++`:由于紧跟在变量`a`后面,可能被解析为后缀自增运算符(`a++`)。 - 然后,剩下的部分是`+++b`。这里,前两个加号可以组合成`++`,后面剩下一个`+b`?但这样解析是不合法的,因为`++`运算符需要操作数(左操作数或右操作数)。 实际上,表达式`a+++++b`会被词法分析器拆分为:`a ++ ++ + b`。但是,这样拆分后,第一个`++`是后缀自增(作用于`a`),然后紧接着又一个`++`运算符,这个`++`运算符前面没有操作数(因为`a++`是一个表达式,但表达式的结果是一个值,这个值不能直接作为`++`的左操作数,因为`++`要求操作数是可修改的左值)。 具体解析过程如下: 1. 词法分析:从左到右,首遇到`a`(标识符),然后遇到两个连续的加号,所以第一个运算符是`++`(后缀自增),即`a++`。 2. 然后剩下的部分是`+++b`。现在,同样,前两个加号组合成`++`(前缀自增?但是前缀自增要求后面跟一个左值),然后剩下一个`+b`?实际上,这里会尝试将前两个加号组合成`++`,然后后面是`+b`,即`++ + b`。但是这样,`++`运算符后面是`+`,而`+`是一个二元运算符,需要两个操作数,所以这里语法错误。 实际上,在`a++`之后,我们得到的是一个表达式(`a++`的结果是一个右值),然后紧接着两个加号,这两个加号会被解析为`++`(前缀自增运算符),但是前缀自增运算符要求一个可修改的左值,而`a++`的结果是一个右值(不是左值),因此不能进行自增操作。 所以,表达式`a+++++b`会被解析为: `(a++) ++ + b` 但是,`(a++)`的结果是一个值(不是变量),所以不能作为`++`运算符的操作数(因为`++`需要操作数是一个左值)。因此,这个表达式在C语言中是无效的,会导致编译错误。 我们也可以尝试另一种分组:`a++ + ++b`。但是,由于词法分析规则,连续的加号会被拆分成最长的有效运算符,所以实际上不会这样拆分(因为`+++`会被拆成`++`和`+`,而不是`+`和`++`)。因此,编译器不会将其解析为`a++ + ++b`。 结论:表达式`a+++++b`在C语言中是非法的,因为词法分析后得到`a ++ ++ + b`,而第一个`a++`的结果不能作为第二个`++`的操作数(需要左值)。 为了验证,我们可以编写一个简单的程序: ```c #include <stdio.h> int main() { int a = 1; int b = 2; int c = a+++++b; // 这一行会编译错误 printf("%d\n", c); return 0; } ``` 在GCC中编译,会得到类似这样的错误: ``` error: lvalue required as increment operand ``` 错误信息指出,在需要左值的地方使用了右值。 因此,用户需要避免这样的写法,而应该使用括号明确运算顺序,或者分开写。例如,如果意图是`(a++) + (++b)`,那么应该显式地加上括号:`(a++) + (++b)`。 总结: - `a---b`被解析为`(a--) - b`(合法,但可能有歧义)。 - `a+++++b`被解析为`(a++) ++ + b`,这是非法的,因为`a++`的结果不是左值。 引用说明: - 引用[1]中提到,赋值运算符的左右操作数的运算顺序是不确定的,但赋值操作本身是右操作数的值赋给左操作数。但这里不涉及赋值,所以不直接相关。 - 引用[5]中讨论了多个自增运算符在同一表达式中的行为,但要注意多个自增运算符在同一个表达式中的副作用可能导致未定义行为。然而,在`a+++++b`中,问题更早发生:语法错误。 因此,在C语言中,连续多个运算符的表达式需要谨慎,避免出现需要左值的地方使用右值,以及避免歧义。 相关问题: 1. 在C语言中,什么是左值(lvalue)和右值(rvalue)? 2. 如何正确使用自增(++)和自减(--)运算符? 3. C语言中哪些运算符要求操作数是左值? 4. 如何避免C语言表达式中的词法分析歧义?</think>在C语言中,表达式 `a+++++b` 的解析涉及词法分析规则和运算符优级,其运算次序有明确的解析规则,但该表达式**在标准C语言中是非法且无法编译通过的**。以下是详细解析: --- ### 一、解析规则和运算次序 1. **词法分析规则(最长匹配原则)** C编译器会尝试将**最长的有效运算符序列**识别为一个整体。表达式 `a+++++b` 会被拆分为: - `a++`:后缀自增运算符(优级2级) - `++`:前缀自增运算符(优级2级) - `+b`:加法运算符(优级6级) 因此,表达式被解析为: **`(a++) ++ + b`** 2. **运算次序和错误原因** - 第一步:计算 `a++` 返回 `a` 的旧值(右值),然后 `a` 自增1(副作用稍后生效)。 - 第二步:尝试计算 `(a++) ++` **错误原因**:后缀/前缀 `++` 要求操作数必须是**左值**(可修改的内存位置)。但 `a++` 的结果是一个**右值**(临时值),无法进行自增操作[^5]。 - 结果:编译器报错 `lvalue required as increment operand`(自增操作需要左值)。 --- ### 二、对比合法表达式 若意图实现类似逻辑,需显式使用括号或空格明确意图: | 表达式 | 解析结果 | 是否合法 | 行为说明 | |--------------|----------------|----------|------------------------------| | `a++ + ++b` | `(a++) + (++b)` | 合法 ✅ | 计算 `a` 旧值 + `b` 自增后的值 | | `(a++) + b` | `(a++) + b` | 合法 ✅ | `a` 旧值 + `b` 的值 | | `a+++++b` | `(a++)++ + b` | 非法 ❌ | 右值不能自增 | --- ### 三、关键概念解释 1. **左值(lvalue) vs 右值(rvalue)** - **左值**:可寻址的变量(如 `a`),可出现在赋值左侧。 - **右值**:临时计算结果(如 `a++` 的返回值),不可修改。 - **自增运算符 `++` 要求操作数为左值**,因此 `(a++)++` 非法[^5]。 2. **序列点(Sequence Point)** 复杂表达式中的副作用(如 `a++` 的自增)需在序列点(如分号 `;`)前完成。未定义顺序的副作用会导致未定义行为,例如: ```c int i = 0; int j = i++ + i++; // 未定义行为[^1] ``` --- ### 四、正确写法建议 避免依赖隐式解析规则,优使用括号明确意图: ```c // 合法且明确的写法 int result = (a++) + (++b); // 或拆分步骤 int temp = a++; a = temp; // 显式处理副作用 int result = temp + (++b); ``` > **关键结论**:`a+++++b` 因违反"自增操作需要左值"的规则而非法。C语言的词法分析虽遵循最长匹配原则,但语法规则优级更高。 --- ### 引用说明 - 词法分析的最长匹配原则解析表达式[^1]。 - 自增运算符要求左值操作数[^5]。 - 序列点对副作用顺序的约束[^1][^5]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值