参数传递是什么?
在C的函数或是JAVA的方法中,向一个函数或方法内部传递一个参数,比如:
void fun( int num ){
num+=2 ;
}
int a = 3 ;
fun( a ) ;
这个a就被作为参数传入函数fun()中,作为a,然后返回或者不返回值
回到最初,函数的作用是复用,那么我们希望这个参数传递是什么样的呢?就是假如我们去掉函数的外衣,就让函数变成代码放到之前是函数的地方,那么很自然这里最后b的值会被改变,这可以说是最朴实的参数传递了,自然的样子。
但是人们又发明了另一种方式
其实我认为这是人们重新定义函数,把函数只作为一段代码的复用上升到一个工具,函数不是作为调用函数代码的一部分,而是把函数作为一个工具,一个函数只能以返回值的方式影响调用代码(这里不考虑在函数体内能跟调用代码中除了参数以外的代码沟通,因为java中泛滥了,其实java中能取到的值也是类中的而不是调用代码的)的,这样更加安全。
这种方式采用的方法是复制一个新值放到函数中去运算,就像:在调用时初始化的局部变量。我们知道局部变量的生存空间只有函数内,这样函数内部的操作就不会影响到外面。
这种方式被称为值传递。 那它是怎么实现的呢?看下面。
上面说了复制一个新值,其实这个东西很有门道。
先从硬件说起。
所有的代码在跑的时候,里边数据都是放在内存中的,包括文件啊什么的都需要从硬盘啊SD卡中取出来,放到内存中,也就是说你所有的值啊,对象啊,都在内存中。
而内存中放这些东西的地方分为两块:堆、栈
很俗套的情节:其中栈的位置小,速度快,堆的位置大,速度慢
于是很自然会有一套放东西的规划,小的数据,用的多的数据,放栈中,大的数据,用得少的数据,放堆中
具体在JAVA中是这样的:
- JAVA中所有的数据类型有9种,8种基本类型和1种对象类型,对象类型又分为系统自建和用户自建
- int、long、double、float、byte、boolean、char 这七种基本类型的数据是直接放在栈中
- string基本类型和所有的对象类型的数据(这里的数据指的是实例化的对象,空的类不是数据)放在堆中,在栈中存放在堆中的指针
所以如果要说复制一个新值,对于只存在栈中和存放在在栈和堆中的数据来说,情况是不一样的。
说复制之前,要说一下创建,创建一个值的时候发生了什么?同样是分两种情况:
- 创建一个存放在栈中的数据
- 比如:int a = 3 ; 这一句其实是两步:int a ; a = 3 ;
- 第一步 int a 是在栈中找了并且开辟了一个放int类型的存储空间,然后把这个存储空间跟变量名a绑定起来了(我自定义绑定的机制发生在命名空间中);
- 第二步是把3这个值存入a绑定的存储空间中。
- 创建一个 指针存放在栈中,内容存放在堆中 的数据
- 比如:Apple myapple = new Apple() ; 这一句也是两步:Apple myapple ; myapple = new RedApple() ;
- 第一步是在栈中找了并且开辟了一个存储空间,因为声明不是基本类型,所以它开辟了一个指针类型的存储空间,然后把这个存储空间跟对象名myapple绑定起来了;
- 第二步是(这里特意用了多态的属性)先在内存的堆中开辟一个能够存放RedApple对象的空间(这个空间没有绑定命名空间),然后经过类型检查是否合法之后,把这个空间的地址存放在跟myapple绑定的指针空间中
数据在内存中的状态就像这样:
好,回到开始,那么复制一个新值到函数中运算的复制的真实情况是什么样子?
假如是这两个函数:
传参的过程其实是一个赋值的过程:
- fun(a); 这句执行的时候,传参其实是执行了这么两句:int num ; num = a ;
-
- 在栈中开辟了一个int类型int大小空间,并且绑定num,然后把a绑定的栈空间中的数据复制一份放到num绑定的栈空间中
- fun(myapple)这句执行的时候也是一样:Apple apple ; apple = myapple ;
-
- 在栈中开辟了一个Apple类型指针大小空间,并且绑定apple,然后把myapple绑定的栈空间的数据(是地址啊)复制衣服放到apple绑定的栈空间中
现在数据在内存中的状态就像这样:
这种在传参时,把a绑定的栈空间中的数据复制一份放到num绑定的栈空间中 的行为就叫做值传递(同样发生在=运算符赋值运算符的时候)。
不过你要注意,你也能很清晰看到,所谓值传递之后,在函数中对变量进行操作会不会影响到调用函数代码中的变量,是不一定的,需要看传的是值还是地址,也就看真实数据是放在哪里,真实数据放在堆中传递的是栈中的地址时,就能被改变。
不过其实如果传入的时地址,能不能改变也要看是什么操作。
以上面的apple.dosomesth() 为例:
- 假如这个方法能对apple的某些值进行操作,那么调用函数的代码中的myapple的内容也会受到改变
- 但如果是赋值,也就是JAVA中的赋值。那么,它进行的还是值传递,受改变的是在局部变量中new出新对象的栈空间的地址,可能换成了新的地址,但是原来那个地址链接的堆空间的数据不受影响,也就是调用函数的代码中的myapple的内容完全不受影响