Loading... ### 问题提出 上周上C++程序设计课的时候有同学提出了一个问题,下面这个程序 ```C++ #include <iostream> using namespace std; int main(int argc, char** argv) { int i = 0; i = i++; cout << i << endl; return 0; } ``` 输出值是0而不是1。输出值应该为0的理由是 i++ 是先赋值后__自增__,也就是相等于下面这段代码 ```C++ i = i; // i=0 i++; // i=1 ``` 但我认为,i++应该相等于一个函数,代码如下: ```C++ int plus(int i) { int var = i; i = i + 1; return var; } ``` 也就是说,尽管变量i(0)自增变成i(1)了,但是随后又把赋值把i++的返回值0赋给i,所以i的最终的值还是0。 课后我希望能了解i++的具体实现,考虑到目前并没有汇编语言的知识,且Java实现上述代码和C++结果相同,所以我用Java进行了以下实验。 ### 实验环境 ``` 系统:Windows 10 x64 JDK版本:Oracle JDK 8u241 编译命令:javac ./Main.java ``` ### 实验代码 下面我尝试比较 i = i++ 和 i = ++i 的字节码指令。 ```Java public class Main { public static void main(String[] args) { System.out.println(i1()); System.out.println(i2()); } public static int i1() { int i = 0; i = i++; return i; } public static int i2() { int i = 0; i = ++i; return i; } } ``` 输出结果 >0 1 #### 下面用到的指令简单解释 字节码指令 | 栈的变化 | 局部变量表的变化 |指令描述 :-: | :-: | :-: |:-: iconst_0 | -> 0 | 无变化 |将int类型0压入栈 istore_0 | value -> | -> value | 将int类型弹出栈,存入第0[^1]个局部变量 iinc 0 1 | 无变化 | value -> value + 1 | 给第0个局部变量加上1 iload_0 | -> value | 无变化 | 将int类型的第0个局部变量压入栈 ireturn | -> value | 无变化 | 返回结果 然后后javap -c /.Main.class [^2]查看字节码 #### i++ ```Java public static int i1(); <localVar:index=0 , name=i , desc=I, sig=null, start=L1, end=L2> Code: 0: iconst_0 1: istore_0 2: iload_0 3: iinc 0, 1 6: istore_0 7: iload_0 8: ireturn } ``` #### ++i ```Java public static int i2(); <localVar:index=0 , name=i , desc=I, sig=null, start=L1, end=L2> Code: 0: iconst_0 1: istore_0 2: iinc 0, 1 5: iload_0 6: istore_0 7: iload_0 8: ireturn } ``` 经过比较我们可以发现,i++ 和 ++i 仅是 iload 和 iinc 的顺序不同。 | index | i++ | 栈 | 局部变量 | ++i | 栈 | 局部变量 | :-: | :-: | :-: | :-: | :-: | :-: | :-: | 1 | iconst_0 | 0 | null | iconst_0 | 0 | null | 2 | istore_0 | null | 0 | istore_0 | null | 0 | 3 | **iload_0** | 0 | 0 | **iinc 0 1** | null | 1 | 4 | **iinc 0 1** | 0 | 1 | **iload_0** | 1 | 1 | 5 | istore_0 | null | 0 | istore_0 | null | 1 | 6 | iload_0 | 0 | 0 | iload_0 | 1 | 1 | 8 | ireturn | null | 0 | ireturn | null | 1 相信能够发现区别,于是我们可以做出推断 - Java自增操作不需要入栈,可以直接修改局部变量表。 - 当写下赋值号右边的 i 的时,相当于要求局部变量 i 入栈,赋值号左边的 i,要求弹出栈中变量,赋值。(所以写下i = i++ 时编译器有一个warning: *The assignment to variable i has no effect*) ### 拓展 解释执行完代码后,j的值。 | 代码 | 结果 | | :----------------------------------: | :--: | | int i = 0; int j = i++; | 0 | | int i = 0: int j = ++i; | 1 | | int i = 0; int j = i++ + ++i; | 2 | | int i = 0; int j = ++i + i++; | 2 | | int j = 0; j = j++ + ++j; | 2 | | int j = 0; j = ++j + j++; | 2 | | int i = 0; int j = i++ + i++ + i++; | 3 | | int i = 0; int j = ++i + ++i + i++; | 5 | ### 结语 尽管实际生产中我们不大可能写出这种“难以理解”,但这样研究,使我深入理解了 i++ 和 ++i 的区别和联系 ,了解这种运算是如何进行的。 另外,由于本人水平有限,难免存在错误。欢迎评论反馈。 *注释* [^1]: 局部变量表的编号从0开始。 [^2]: 局部变量表可以javap的-v参数也可以生成。 这里采用Bytecode Viewer 生成的结果。 最后修改:2020 年 04 月 22 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏