首页 > 试题广场 >

检查程序,是否存在问题,如果存在指出问题所在,如果不存在,说

[单选题]
检查程序,是否存在问题,如果存在指出问题所在,如果不存在,说明输出结果。
package algorithms.com.guan.javajicu;  
public class Inc {  
    public static void main(String[] args) {  
       Inc inc = new Inc();  
       int i = 0;  
       inc.fermin(i);  
       i= i ++;  
       System.out.println(i); 
   
    }  
    void fermin(int i){  
       i++;  
    }  
}  
  • 0
  • 1
  • 2
  • 3
推荐
如果你理解JVM的内存模型,就不难理解为什么答案返回的是0,而不是1。
我们单独看问题中的这两句代码。
int i = 0; i = i++; 
Java虚拟机栈(JVM Stack)描述的是Java方法执行的内存模型,而JVM内存模型是基于“栈帧”的,每个栈帧中都有 局部变量表 操作数栈 (还有动态链接、return address等),那么JVM是如何执行这个语句的呢?通过javap大致可以将上面的两行代码翻译成如下的JVM指令执行代码。
0: iconst_0
1: istore_1
2: iload_1
3: iinc          1, 1
6: istore_1
7: iload_1

接下来分析一下JVM是如何执行的:
第0:将int类型的0入栈,就是放到操作数栈的栈顶
第1:将操作数栈栈顶的值0弹出,保存到局部变量表 index (索引)值为1的位置。(局部变量表也是从0开始的,0位置一般保存当前实例的this引用,当然静态方法例外,因为静态方法是类方法而不是实例方法)
第2:将局部变量表index 1位置的值的副本入栈。(这时局部变量表index为1的值是0,操作数栈顶的值也是0)
第3:iinc是对int类型的值进行自增操作,后面第一个数值1表示,局部变量表的index值,说明要对此值执行iinc操作,第二个数值1表示要增加的数值。(这时局部变量表index为1的值因为执行了自增操作变为1了,但是操作数栈中栈顶的值仍然是0)
第6:将操作数栈顶的值弹出(值0),放到局部变量表index为1的位置(旧值:1,新值:0),覆盖了上一步局部变量表的计算结果。
第7:将局部变量表index 1位置的值的副本入栈。(这时局部变量表index为1的值是0,操作数栈顶的值也是0)

总结:从执行顺序可以看到,这里第1和第6执行了2次将0赋值给变量i的操作(=号赋值),i++操作是在这两次操作之间执行的,自增操作是对局部变量表中的值进行自增,而栈顶的值没有发生变化,这里需要注意的是保存这个初始值的地方是操作数栈而不是局部变量表,最后再将栈顶的值覆盖到局部变量表i所在的索引位置中去。

有兴趣的同学可以去了解一下JVM的栈帧(Stack Frame)

关于第二个陷阱(为什么 fermin方法没有影响到i的值 )的解答看下面。
inc.fermin(i);
1. java方法之间的参数传递是 值传递 而不是 引用传递
2. 每个方法都会有一个栈帧,栈帧是方法运行时的数据结构。这就是说每个方法都有自己独享的局部变量表。(更严谨的说法其实是每个线程在执行每个方法时都有自己的栈帧,或者叫当前栈帧 current stack frame)
3. 被调用方法fermin()的形式参数int i 实际上是调用方法main()的实际参数 i 的一个副本。
4. 方法之间的参数传递是通过局部变量表实现的,main()方法调用fermin()方法时,传递了2个参数:
第0个隐式参数是当前实例(Inc inc = new Inc(); 就是inc引用的副本,引用/reference 是指向对象的一个地址,32位系统这个地址占用4个字节,也就是用一个Slot来保存对象reference,这里传递的实际上是reference的一个副本而不是 reference本身 );
第1个显示参数是 i 的一个副本。所以 fermin()方法对 i 执行的操作只限定在其方法独享或可见的局部变量表这个范围内,main()方法中局部变量表中的i不受它的影响;

如果main()方法和fermin()方法共享局部变量表的话,那答案的结果就会有所不同。 其实你自己思考一下,就会发现, JVM虚拟机团队这么设计是有道理的。

编辑于 2017-06-26 10:07:31 回复(60)
上面的回答都没说明白为什么会这样,首先运行这个程序,在c/c++和java会发现不一样,在c/c++中答案是1,在java中答案是0。为什么呢?
原因:jvm里面有两个存储区,一个是暂存区(是一个堆栈,以下称为堆栈),另一个是变量区。jvm会这样运行这条语句, JVM把count值(其值是0)拷贝到临时变量区。 步骤2 count值加1,这时候count的值是1。 步骤3 返回临时变量区的值,注意这个值是0,没修改过。 步骤4 返回值赋值给count,此时count值被重置成0。 c/c++中没有另外设置一个临时变量或是临时空间来保存i,所有操作都是在一个内存空间中完成的,所以在c/c++中是1。
编辑于 2016-01-26 10:34:08 回复(42)
Java使用了中间缓存变量机制:
i=i++;等同于:
temp=i; (等号右边的i)
i=i+1;      (等号右边的i)
i=temp;   (等号左边的i)
而i=++i;则等同于:
i=i+1;
temp=i;
i=temp;
但是那些说将i++表达式的结果赋给i的答案,本人表示不理解。
发表于 2015-08-10 17:02:16 回复(34)
本人的讲解比较简单明了(初学者也可以看懂),如有不对的地方请各路大神指点。
i++      先赋值在计算结果;
++i      先计算结果再赋值。
int i = 0;  
       i = i ++;  // 左边这个i其实是障眼法,就是一个中间变量,可以和下行的i合并;
       System.out.println(i);   这里等价于:
int i = 0;
       System.out.println(i++);   这下再看,先赋值(先将i传给println函数打印出来,在计算表达式结果)
所以打印出来的是0,实际上整个表达式的结果已经是1了,只是没有打印出整个表达式的结果。
所以我们知道如下结论:
1、无论怎么变,i++和++i的整个表达式的结果都是1.
2、有时我们打印的是表达式的结果(System.out.println(++i)),
      有时我们打印的只是一个中间变量(System.out.println(i++))。
Ps:
int i = 0;  
i++;
System.out.println(i);  //值为1    打印的是表达式的结果

int i = 0;  
++i;
System.out.println(i);  //值为1     打印的是表达式的结果 

int i = 0;  
i = i++;
System.out.println(i);  //值为0      打印的是中间变量(JVM中间缓存变量机制)

int i = 0;  
i = ++i;
System.out.println(i);  //值为1    打印的是表达式的结果







发表于 2018-01-15 17:12:49 回复(25)
先讲i=10这个值放到操作数栈此时操作数栈的值为10中然后i变量在++,++之后再把操作数栈的值赋给i变量所以输出10
发表于 2022-08-17 15:18:58 回复(0)
  1. public   static   void  main(java.lang.String[]);
  2.   Code:
  3.     0 :   iconst_0     //0放到栈顶
  4.     1 :   istore_1    //把栈顶的值保存到局部变量1,也就是i中
  5.     2 :   iload_1     //把i的值放到栈顶,也就是说此时栈顶的值是0
  6.     3 :   iinc     1 1  //注意这个指令,把局部变量1,也就是i,增加1,这个指令不会导致栈的变化,也就是说局部变量1,即i此时为1了。
  7.     6 :   istore_1     //把栈顶的值(0)保存到局部变量1,也就是让i为0了,所以最后i为0
  8.     7 :   getstatic   # 2 //Field java/lang/System.out:Ljava/io/PrintStream;
  9.     10 :  iload_1
  10.     11 :  invokevirtual   # 3 //Method java/io/PrintStream.println:(I)V
  11.     14 :   return
发表于 2016-09-03 21:21:35 回复(0)
15: iload_2       //将局部变量区2号索引的变量i值0压入操作数栈 
16: iinc     2, 1 //将局部变量区2号索引的值加1,操作数栈不发生变化 
19: istore_2      //将栈顶的0弹出,赋给局部变量区2号索引的i 

发表于 2015-07-30 18:57:37 回复(0)
i++也是一个表达式,是有返回值的,返回值就是i自增前的值。因此执行i = i++后,虽然i自增为1,但是又被i++的返回值给重新赋值了
发表于 2017-06-14 21:48:15 回复(14)
调用前 i的地址值是0,调用fermin()方法时,因为i是基本数据类型,参数是按值传递的,此时会创建一个i的副本,该副本与i有相同的值,把这个副本作为参数进行i++。 但是修改后的参数i与i没有关系的。所以i++,i=0.。在此说明一下值传递的含义就是形参只是用实参的值初始化一个临时的存储单元,与实参有着相同的值,但是却有着不同的存储单元,因此对形参的改变不会影响实参的值。
编辑于 2016-04-25 20:20:40 回复(3)
答案:A
 inc.fermin(i);
基本数据类型是值传递,因此这里不改变i的值
 i= i ++; 
i++这个表达式的值为0,然后将i+1得到i=1
然后将上面i++这个值为0的表达式赋值给i
所以结果i=0
编辑于 2017-03-24 20:52:37 回复(25)
以下是i= i ++;字节码指令:
15: iload_2       //将i从 局部变量表 加载到 栈顶, i = 0
16: iinc     2, 1  //将 局部变量表 中的i 加1, 局部变量表中变为1
19: istore_2      //将栈顶的0 存回到 局部变量表, i变回0

编辑于 2015-08-03 16:52:34 回复(3)
顺序应该是:先计算表达式的值,得0,然后i加1,此时i=1,最后赋值,赋值是把表达式的值赋值,所以,i又变成0了
发表于 2015-05-29 16:33:22 回复(0)

这个问题可以简化为:

int i=0;
i=i++;
System.out.println(i);

问题的关键在于i=i++;是怎么运算的。首先++运算符的优先级高于=。因此首先执行i++,表达式返回值为0,但注意此时i++还未执行完,i自增为1。然后将表达式i++的返回值赋值给i,此时i又变成了0。 查看字节码就能明白:

 0: iconst_0            //字面常量0入栈
 1: istore_1            //将栈顶的0值取出保存在局部变量表位置1中
 2: iload_1             //将局部变量表位置1的int值0入栈
 3: iinc          1, 1  //将局部变量表位置1的int值增加1
 6: istore_1            //将栈顶的0值取出保存在局部变量表位置1中
 7: getstatic     #2    // Field java/lang/System.out
10: iload_1
11: invokevirtual #3    // Method java/io/PrintStream.println
14: return
编辑于 2017-02-16 02:01:07 回复(6)
 
其实本题考的是 表达式的执行顺序
编辑于 2019-02-26 14:28:39 回复(0)
强哥说,i++实现原理:
int getI(int &i){
    int temp = i;
    i++;
    return temp;
}
注意这里写成 &i 是为了说明不是传值

发表于 2015-09-10 12:34:46 回复(5)
i=i++;在整个式子结束之后才增加1。
int i = 0;
int j = i++;
System.out.println(j);0
System.out.println(i);
看了一些文章,我给一点我自己的理解:
在执行i++和i--时,java会重新分配一块内存来存放原值的副本,这个副本暂叫为零时变量。在执行i++时,原值++变成了1,如果是j=i++,这时候这个副本就要赋值给j,j=0,而i也在执行完语句之后成了1。而i=i++,是这个副本直接赋值给了i,致使i变成了0,而那个i++得的1,被覆盖,成了0。在完成这一系列之后,副本所占的内存也就被释放了。这只是i++和i--的特性,对于++i之类的我还不知道,希望大家补充。
发表于 2016-09-16 22:05:29 回复(1)
因为Java采用了 中间缓存变量机制, 所以 i = i++可以换成如下写法:
temp = i;
i = i + 1;
i = temp;
发表于 2015-07-22 12:33:45 回复(1)
别被概念绕晕了,什么中间缓存变量机制,其实就是个运算返回值的问题。
抛开循环的干扰来理解你的第一段代码:
j = 0; j = j++; System.out.println(j); 
官方文档上的对于 j++ 的解释是:
  1. 先将1与变量 j 的值相加,将新值 (1) 存回变量。
  2. 表达式返回旧值 (0).

然而,返回旧值后,你的代码又做了一次多余的赋值运算,把刚加了1的 j 又重新赋值为 0; 即便加个100的循环加上去,不过是做了100次的j=0操作而已,跟你的加法没有任何关系。



编辑于 2017-11-22 14:13:47 回复(2)
以下是个人观点,感觉大家被带跑偏,远没有这样难吧,JVM都出来了。

大家请注意这个方法 void fermin(int i),是个void,也就是说没有返回值,在本题中不用考虑。
剩下的就只有一句i=i++;先使用i的值(尽管i已加1了),即i=0.输出当然就是0.

另外写了一个小程序,在方法中直接对i赋值100,输出仍是0,大家可参考。



发表于 2017-07-21 14:02:33 回复(5)
   void fermin(int i){ 
       i++; 
    } 
此代码改变的只是形参i的值,而实参i的值并没有改变
i= i ++; 此代码是先赋值  后加加
发表于 2017-08-18 20:08:14 回复(1)
main 和 fremin 的 i 在各自 (运行时的当前栈帧<StackFrame>)的局部变量表<Local Variable Table>中,两个变量不一样。main调用fremin结束之后退栈,然而并没有改变main的栈帧的局部变量表里 i 的值。 i=i++;i++的汇编指令是(iinc 1,1),他只改变本地变量表的值,但是  i= 赋值的时候,操作数栈的栈顶还是0,存到本地变量表,又把slot1覆盖成0,所以得0.
iconst_0// 0
istore_1//slot 1 : i=0
iload_1 //取出0
iinc 1,1 //slot 1: i=1
istore_1// slot 1 = 0覆盖
iload_1 //println的还0
输出。
发表于 2015-07-31 17:02:42 回复(3)