java的传值调用什么_值传递和引用传递传的到底是啥?

原标题:值传递和引用传递传的到底是啥?

作者 | 编程指北 责编 | 张文

来源 | 编程指北(ID:cs_dev)

在网上看到过很多讨论 Java、C++、Python 是值传递还是引用传递这类文章,所以这一篇呢就是想从原理讲明白关于函数参数传递的几种形式。

参数传递无外乎就是传值(pass by value),传引用(pass by reference)或者说是传指针。

传值还是传引用可能在 Java、Python 这种语言中常常会困扰一些初学者,但是如果你有 C/C++背景的话,那这个理解起来就是 so easy。

今天我就从 C 语言出发,一次性把 Java、Python 这些都给大家讲明白。

不过呀,要想彻底搞懂这个,需要了解两个背景知识:

堆、栈

函数调用栈

堆、栈

要注意,这“堆”和“栈”并不是数据结构意义上的堆(Heap,一个可看成完全二叉树的数组对象)和 栈(Stack,先进后出的线性结构)。

这里说的堆栈是指内存的两种组织形式,堆是指动态分配内存的一块区域,一般由程序员手动分配,比如 Java 中的 new、C/C++ 中的 malloc 等,都是将创建的对象或者内存块放置在堆区。

而 栈是则是由编译器自动分配释放(大概就是你申明一个变量就分配一块相应大小的内存),用于存放函数的参数值,局部变量等。

就拿Java来说吧,基本类型(int、double、long 这种)是直接将存储在栈上的,而引用类型(类)则是值存储在堆上,栈上只存储一个对对象的引用。

举个栗子:

int age = 22;

Stringname = newString( "shuaibei");

这两个变量存储图如下:

66870df4e35d6b61fb441b2765dd1ca6.png

如果,我们分别对 age、name 变量赋值,会发生什么呢?

age= 18;

name= new String( "xiaobei");

如下图:

38a6fca6ac593bdddf6e8d2cedf4f24c.png

age 仅仅是将栈上的值修改为 18,而 name 由于是 String 引用类型,所以会重新创建一个 String 对象,并且修改 name,让其指向新的堆对象。(细心的话,你会发现,图中 name 执行的地址我做了修改)

然后,之前那个对象如果没有其它变量引用的话,就会被垃圾回收器回收掉。

这里也要注意一点,我创建 String 的时候,使用的是 new,如果 直接采用字符串赋值,比如:

Stringname = "shuaibei"

那么是 会放到 JVM 的常量池去,不会被回收掉,这是字符串两种创建对象的区别,不过这里我们不关注。

Java 中引用这东西,和 C/C++ 的指针就是一模一样的嘛。只不过 Java 做了语义层包装和一些限制,让你觉得这是一个引用,实际上就是指针。

好,让我继续了解下函数调用栈。

函数调用栈

一个函数需要在内存上存储哪些信息呢?

参数、局部变量,理论上这两个就够了,但是当多个函数相互调用的时候,就还需要机制来保证它们顺利的返回和恢复主调函数的栈结构信息。

那这部分就包括 返回地址、ebp 寄存器(基址指针寄存器,指向当前堆栈底部) 以及其它需要保存的寄存器。

所以一个完整的函数调用栈大概长得像下面这个样子:

5ba40f3604a8fb26a214666008a71d5d.png

那,多个函数调用的时候呢?

简单来说就是叠罗汉,这是两个函数栈:

7155ff3dac3f597a104073ce3cc40477.png

今天,我们不会去详细了解函数调用过程 ebp、ebp 如何变化,返回地址又是如何起作用的。

今天的任务就是搞明白参数传递,所以其它的都是非主线的知识,忽略即可。

顺便插点题外话:

学习新知识有时候需要刨根问底,有时候却需要及时回头,尤其是计算机,你要是一直刨根问题,我能给你整到硅的提纯去,这就是失去了学习的意义。

最好的方式是,在一个恰到好处的地方建立一个抽象层,并且认可这个抽象层提供的功能/接口,不去探究这一层下面是什么,怎么实现的。

比如,学习 HTTP,我就只需要认 TCP 提供稳定、可靠传输就够了,暂时就不需要去看 TCP 如何做到的。

好了,继续说回函数传参,举个例子,下面这段代码在main函数内调用了 func_a函数

intfunc_a( inta, int*b){

a = 5;

*b = 5;

};

intmain( void){inta = 10;intb = 10;func_a(a, &b);printf( "a=%d, b=%dn", a, b);return0;}

// 输出a= 10, b= 5

那么func_a(a, &b)这个过程,在函数调用栈上究竟是怎么样的呢?

7c3b24d558ccc8b17b5bf3226981f673.png

就像上图所示,编译器会生成一段函数调用代码。

将 main 函数内变量 a 的值拷贝到 func_a 函数参数 a 位置。

将变量 b 的地址,拷贝到 func_a 函数参数 b 的位置。

记住这张图,这是函数参数传递的本质,没有其它方式,just copy!

copy 意味着是副本,也就是 在子函数的参数永远是主调函数内的副本。

决定是值传递还是所谓的引用传递,在于你 copy 的到底是一个值,还是一个引用(的值)。

其实引用也是值......不要觉得引用就是那种玄乎的东西。

所以会有一种声音说,是不存在所谓的引用传递的,一切传引用的本质还是传值。

也就是pass pointer by value或者pass reference by value,哈哈哈有点意思。

今天,我们不讨论到底有没有传引用这个东西,这是一个个仁者见仁智者见智的问题。我的目的呢,就是把参数传递这个过程给大家剖析下,至于到底是传值还是传引用,那就看大家怎么思考了。

pass by value in java

举个最简单的例子来说明下:

publicclassHelloWorld{publicstaticvoidChangeRef( String name){name = newString( "xiaobei");}publicstaticvoidmain( String[] args){String name = newString( "shuaibei");ChangeRef(name);System. out.println(name. equals( "shuaibei"));

}}

上面,ChangeRef函数实际上并没有改变到 main 函数内的 name 对象,看图就明白了:

13b2f5f654ac1cf34347ac65fe237d46.png

根据我们前面所讲,参数传递实际就是复制栈上的值本身,这里 name 的值就一串地址,所以 ChangeRef 接收到的也是这串地址,但是 在 ChangeRef 函数内将 name 的指向改成了一个新的 String 对象,但是这里不会对 main 函数中的 name 对象产生任何的影响。

咦,不是说引用类型都是引用传递吗?为什么还不会对主调函数产生影响呢?

我们都把引用的指向改变了,还能影响个啥, 如果想通过引用传递修改外部传进来的值,一般是采用成员方法。

假设 String 类有一个方法叫做 changeStr(String value),那么我们就可以在 ChangeRef 内调用这个方法,修改 name 的值,并且会同步修改到main函数里的值。

Python

其实和Java 挺像的,但是 Python 有个特点就是 所有变量本身只是一个引用,真正的类型信息都是和对象存储在一起的。这里就不再过多展开。

以上,希望能帮到大家。返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Swing 中,可以使用以下方法在窗体之间: 1. 使用构造函数:在一个窗体中创建一个新的窗体时,可以通过构造函数。例如: ``` public class FirstFrame extends JFrame { private String value; public FirstFrame(String value) { this.value = value; } // ... } ``` 在另一个窗体中创建 FirstFrame 时,可以递一个: ``` FirstFrame frame = new FirstFrame("Hello"); ``` 2. 使用方法:可以在一个窗体中定义一个方法来设置,然后在另一个窗体中调用该方法。例如: ``` public class FirstFrame extends JFrame { private String value; public void setValue(String value) { this.value = value; } // ... } ``` 在另一个窗体中创建 FirstFrame 时,可以先创建一个实例,然后调用 setValue 方法: ``` FirstFrame frame = new FirstFrame(); frame.setValue("Hello"); ``` 3. 使用静态变量:可以在一个类中定义一个静态变量来存储,然后在另一个窗体中访问该静态变量。例如: ``` public class Data { public static String value; } public class FirstFrame extends JFrame { public void setValue(String value) { Data.value = value; } // ... } public class SecondFrame extends JFrame { public void getValue() { String value = Data.value; // ... } // ... } ``` 在 FirstFrame 中调用 setValue 方法来设置,在 SecondFrame 中调用 getValue 方法来获取。 以上是三种常用的在 Java Swing 中的方法,具体使用哪种方法取决于你的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值