首页 > 其他分享 >美团一面问我i++跟++i的区别是什么

美团一面问我i++跟++i的区别是什么

时间:2024-05-18 15:42:29浏览次数:18  
标签:操作数 压入 区别 ++ 美团 局部变量 栈顶 表中

美团一面问我i++跟++i的区别是什么

面试官:“i++跟++i的区别是什么?”

我:“i++是先使用然后再执行+1的操作,++i是先执行+1的操作然后再去使用i”

面试官:“那你看看下面这段代码,运行结果是什么?”

public static void main(String[] args) {
    int j = 0;
    for (int i = 0; i < 10; i++) {
        j = (j++);
    }
    System.out.println(j);
}

我:“我猜他肯定不是10”

面试官:

我:“哈哈.....,开个玩笑,结果为0啦”

面试官:“为什么呢?”

我:“简单来说的话,j++这个表达式每次返回的都是0,所以最终结果就是0”

对应前文提到过的:i++这种写法是先使用,再执行+1操作,如果不理解请暂停多思考思考

面试官:“小伙子不错,那你能从更底层的角度讲一讲为什么嘛?”

首先我们知道,JVM的运行时数据区域是分为好几块的,具体分布如下图所示:

现在我们主要关注其中的虚拟机栈,关于虚拟机栈,我们需要了解的是:

  1. Java虚拟机栈是由一个个栈帧组成,线程在执行一个方法时,便会向栈中放入一个栈帧。
  2. 每一个方法所对应的栈帧又包含了以下几个部分
    • 局部变量表
    • 操作数栈
    • .........

其中的局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用。

局部变量表的最小存储单元为Slot(槽),其中64位长度的long和double类型的数据会占用2个Slot,其余的数据类型只占用1个。因此可以直接通过下标来进行数据访问

操作数栈对于数据的存储跟局部变量表是一样的,但是跟局部变量表不同的是,操作数栈对于数据的访问不是通过下标而是通过标准的栈操作来进行的(压入与弹出)

数据的计算是由CPU完成的,弹栈的目的就是将数据压入到CPU中

接下来我们分析下面这段代码在字节码层面的执行过程:

// 为方便阅读将对应代码也放到这里
public static void main(String[] args) {
 int j = 0;
 for (int i = 0; i < 10; i++) {
     j = (j++);
 }
 System.out.println(j);
}

我们进入到这段代码编译好的.class文件目录下执行:javap -c xxx.class,得到其字节码如下:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0    // 将常数0压入到操作数栈顶
       1: istore_1    // 将操作数栈顶元素弹出并压入到局部变量表中1号槽位,也就是j=0
       2: iconst_0    // 将常数0压入到操作数栈顶
       3: istore_2	  // 将操作数栈顶元素弹出并压入到局部变量表中2号槽位,也就是i=0
       4: iload_2     // 将2号槽位的元素压入操作数栈顶
       5: bipush        10   // 将常数10压入到操作数栈顶,此时操作数栈中有两个数(常数10,以及i)
       7: if_icmpge     21	 // 比较操作数栈中的两个数,如果i>=10,跳转到第21行
      10: iload_1			 // 将局部变量表中的1号槽位的元素压入到操作数栈顶,就是将j=0压入操作数栈顶
      11: iinc          1, 1 // 将局部变量表中的1号元素自增1,此时局部变量表中的j=1

      14: istore_1			 // 将操作数栈顶的元素(此时栈顶元素为0)弹出并赋值给局部变量表中的1号							      槽位(一号槽位本来已经完成自增了,但是又被赋值成了0)
      
      15: iinc          2, 1 // 将局部变量表中的2号槽位的元素自增1,此时局部变量表中的2号元素值为1,也就是i=1
      
      18: goto          4	 // 第一次循环结束,跳转到第四行继续循环
      21: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      24: iload_1
      25: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      28: return

我们着重关注第10,11,14行字节码指令,用图表示如下:

可以看到本来局部变量表中的j已经完成了自增(iinc指令是直接对局部变量进行自增),但是在进行赋值时是将操作数栈中的数据弹出,但是操作数栈的数据并没有经过计算,所以每次自增的结果都被覆盖了,最终结果就是0。

我们平常说的i++是先使用,然后再自增,而++i是先自增再使用。这个到底怎么理解呢?如果站在JVM的层次来讲的话,应该这样说:

  1. i++是先被操作数栈拿去用了(先执行的load指令),然后再在局部变量表中完成了自增,但是操作数栈中还是自增前的值
  2. 而++1是先在局部变量表中完成了自增(先执行innc指令),然后再被load进了操作数栈,所以操作数栈中保存的是自增后的值

这就是它们的根本区别。

关于i++的执行过程,我这里也给出一个程序及编译后的结果

public static void main(String[] args) {
    int i = 0;
    i = ++i;
    System.out.println(i);
}
>  0 iconst_0
>  1 istore_1
>  2 iinc 1 by 1
>  5 iload_1
>  6 istore_1
>  7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
> 10 iload_1
> 11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
> 14 return

大家可以自行分析


作者简介

大三退学,创业、求职、自考,一路升级

7年it从业经验,多个开源社区contributor

半自由职业,新时代数字游民

自媒体创业,专注分享成长路上的所悟所得

长期探索 个人成长职业发展副业探索

标签:操作数,压入,区别,++,美团,局部变量,栈顶,表中
From: https://www.cnblogs.com/daimzh/p/18199375

相关文章

  • C/C++技巧
    1.三目运算符语法:表达式1?表达式2:表达式3。表达式1为真则执行表达式2,否则执行表达式3。相比if语句,三目运算符短小简洁,适当使用可以提高代码可读性。另外,如果三目运算符返回左值,可以继续赋值。举例#include<iostream>usingnamespacestd;intmain(){system("......
  • C++学习----make
    基本规则:touchmain.cadd.csub.cadd.hsub.h#新建以上文件main函数:intmain(void){return0;}Makefile文件:main:main.oadd.osub.ogcc-Wall-gmain.oadd.osub.o-omainmain.o:main.cgcc-Wall-g-cmain.c-omain.oadd.o:add.cadd.h......
  • C++学习----gcc
    gcc编译步骤 静态库使用步骤hello_fn.h#ifndef_HELLO_FN_H#define_HELLO_FN_Hvoidhello(constchar*name);#endifhello_fn.c#include<stdio.h>#include"hello_fn.h"voidhello(constchar*name){printf("hello%S!!!\n",name);......
  • std::thread和std::jthread的区别
    `jthread`和`std::thread`是C++标准库中用于创建和管理线程的两个类。它们的主要区别在于线程的管理方式和对异常的处理:1.**RAII(ResourceAcquisitionIsInitialization)语义:**-`std::thread`在销毁时,如果线程仍在运行,需要显式调用`join()`或`detach()`方法来等待......
  • Qt/C++音视频开发74-合并标签图形/生成yolo运算结果图形/文字和图形合并成一个/水印滤
    一、前言在使用yolo做人工智能运算后,运算结果除了一个方框,还可能需要增加文字显示在对应方框上,以便标记是何种物体,比如显示是人还是动物,或者还有可能追踪人员,显示该人员的姓名。这种应用场景非常普遍,而且非常有必要,可以非常直观的直接看到对应移动的物体是什么。当然也有个缺点,就......
  • localdatetime和date的区别
    类型和线程安全性:localdatetime是Java 8引入的,属于Java8日期时间API(java.time包),而date是旧版Java日期时间API(java.util包)中的类。localdatetime是不可变类型,一旦创建后其值不可变,是线程安全的。而date是可变类型,可以通过方法修改其值,非线程安全。时间精度和时区处理:......
  • ORACLE 物理读 逻辑读 一致性读 当前模式读区别
    转自:https://www.cnblogs.com/kerrycode/p/5940626.html在ORACLE数据库中有物理读(PhysicalReads)、逻辑读(LogicalReads)、一致性读(ConsistantGet)、当前模式读(DBBlockGets)等诸多概念,如果不理解或混淆这些概念的话,对你深入理解一些知识无疑是一个障碍,但是这些概念确实挺让让人犯......
  • algo c++ 常用接口
    接口网站cppreferencesetunorder_set//unorder_setunorder_set<T>u_set;//insertu_set.insert(Tt);//findandjudgeiteratorit=u_set.find(Tt);if(u_set.find(t)!=it.end()){}//删除u_set.erase(t);技巧如果想要通过一种数据类型种的值构建另一种......
  • Uri.EscapeDataString 和 Server.UrlEncoding 的区别
    今天在iis中访问一个即含有空格又含有#的文件名时,通过iis直接访问都无法到达,显示404,即便是urlencode文件名后依然无济于事,最后通过gpt问到了答案。Uri.EscapeDataString和Server.UrlEncode是.NETFramework中用于URL编码的两个方法,它们有以下区别:命名空间和所属类:Uri.Es......
  • sql:left join和join区别
    join,即innerjoin,表示两个表都必须存在的联结。它返回的是两个表有交集的部分,其余没有关联的部分则不显示。这种联结方式是排他性的,即如果某行在其中一个表中存在,但在另一个表中没有匹配的行,那么这行就不会出现在结果集中。leftjoin,即左联结,表示以左边的表为主,不管右边的表有......