参数传递
在咱们日常编写代码的过程中,调用函数可能是最常见的操作了。那么,在调用函数时,参数是怎么样传递的呢?
值传递
置信有很多人都是学 C 语言入门的,刚开始写代码时,用的最多的就是值传递了。
void plus_one(int a){
a++;
printf("a: %d", a);
}
int main(){
int n = 10;
plus_one(n);
printf("n:%d", n);
return 0;
}
复制代码
这是一个简略的值传递的例子,无需多言,plus_one
函数的作用就是将传进来的数加一,而后输入。所谓值传递,就是间接将实参 n
的值赋给形参a
,赋值实现之后,两者再无瓜葛。
因而,下面的代码能够等效为:
int main(){
int n = 10;
// plus_one start
int a;
a = n;
a++;
printf("a: %d", a);
// plus_one end
printf("n:%d", n);
return 0;
}
复制代码
能够看到,值传递简略直观,然而,调用函数并不能扭转实参 n
的值。
指针传递
那么,当咱们须要扭转实参的值的时候,咱们就会想到应用指针传递,也就是所谓的地址传递。
void plus_one(int* p){*p = *p + 1;}
int main(){
int n = 10;
plus_one(&n);
printf("The result is %d", n);
return 0;
}
复制代码
这里,咱们将实参 n
的地址传入 plus_one
函数,在函数中,间接对指针 p
所指向的值,也就是 n
做操作,天然就能够扭转实参 n
的值了。
实际上,指针传递也是值传递。咱们将下面的代码改写:
int main(){
int n = 10;
// plus_one start
int* p;
p = &n;
*p = *p + 1;
printf("The result is %d", n);
// plus_one end
return 0;
}
复制代码
能够看到,所谓的指针传递,也只不过是将变量 n
的地址值赋给指针变量p
,实际上也是值传递。
所以,能够不负责任的概括为,C 语言中只有值传递;
援用传递
指针诚然弱小,然而因为代码不易读,难以了解等问题,也是广为诟病。C++ 作为 C 语言的超大杯,引入了援用传递来简化指针传递的写法。
void plus_one(int& a){a++;}
int main(){
int n;
plus_one(n);
printf("The result is %d", n);
return 0;
}
复制代码
C++ 中,对 & 运算符进行了重载,实现了援用传递。具体实现为,在调用 plus_one
函数时,在函数调用栈中存变量 n
的地址,而不是 n 的值。因而,plus_one
中的变量 a
就相当于是 n
的 ” 别名 ”,对 a
操作时,天然会扭转 n
的值。
可见,援用传递的底层也是赋值操作。
Java 中的参数传递
那么,在 Java 中,到底是援用传递,还是值传递呢?
Java 中变量分为根本变量和对象,咱们无妨别离探讨。
根本变量类型
首先,对于 int
、char
等根本类型,Java 是应用值传递的,很容易验证。
static void plusOne(int a){
a++;
System.out.println("a:" + a);
}
public static void main(String[] args){
int n = 10;
plusOne(n);
System.out.println("n:" + n);
}
复制代码
显然,与 C 语言中一样,这里 n
的值是不会扭转的。
对象
public class PassObject {public static void main(String[] args) {Dog myDog = new Dog("Test");
foo(myDog);
System.out.println(myDog.getName());// TestPlus
}
public static void foo(Dog dog) {dog.setName("TestPlus");
}
}
class Dog{
private String name;
public Dog(String name) {this.name = name;}
public void setName(String name) {this.name = name;}
public String getName() {return name;}
}
复制代码
通过下面的例子能够看到,传入对象的援用时,是能够扭转对象的属性变量的。那么 Java 在传递对象作为参数时,是援用传递吗?
实际上并非如此,Java 中,对象的援用,实际上相当于对象的指针。在 Java 中操作对象,只有通过援用操作这一种路径。某种意义上,Java 中是不能间接操作对象的。
也就是说,在上例中传参时,没有对 myDog
对象实例做任何操作,只是把 myDog
援用值赋给了 foo
函数中的本地变量dog
。并没有像援用传递一样,传入对象实体,然而只在栈中保留对象援用的操作。所以,Java 中传递对象时,也是值传递。
所以,Java 中只有值传递。
值得一提
然而,还是会有一些非凡状况,会让人狐疑上述论断。
数组
下面只剖析了根本变量类型和对象,数组呢?
实际上,Java 中的数组也是一种对象,数组类也是继承自 Object 类。在将数组作为参数时,也是传递的数组的援用,并没有传递数组的实体。
public static void changeContent(int[] arr) {arr[0] = 10;
}
public static void changeRef(int[] arr) {arr = new int[2];
arr[0] = 15;
}
public static void main(String[] args) {int [] arr = new int[2];
arr[0] = 4;
arr[1] = 5;
changeContent(arr);
System.out.println(arr[0]); // 10
changeRef(arr);
System.out.println(arr[0]); // 10
}
复制代码
在上例中能够看到,将传入的数组援用赋给一个新的数组后,这个援用就不能操作之前的数组了。
对于 援用 ,英文是reference
,实际上,我自认为,翻译为 句柄 是更为贴切的,援用就像是一个 柄,一个Handler
,你能够用它操作实体,但他并不是实体自身。就像手柄能够操控游戏机,但不是游戏机自身,当你将这个手柄连贯到另一个游戏机的时候,它就不能操控之前的游戏机了。
包装类和 String
public static void main(String[] args) {
Integer n = 1;
plusOne(n);
System.out.println(n); // 1
}
private static void plusOne(Integer n) {
n = n + 1;
System.out.println(n);// 2
}
复制代码
在这段代码中,n
作为 Integer
类型实例的句柄,却并没有胜利扭转对象的值,这是为什么呢?
在 Integer
类中,存对应值的属性是value
,其申明如下:
private final int value;
复制代码
可见,value 值是不能改的,那加的操作是怎么实现的呢?
在上述加一的过程中,会从新 new
一个 Integer
对象,让后将这个对象赋给援用n
。这样以来,之前的对象天然是不会扭转的。
实际上,包装类以及 String
类的值,都是 final
的,所以在执行 +
的过程中,都会从新生成一个对象,而后对它赋值。
参考:《2020 最新 Java 根底精讲视频教程和学习路线!》
链接:https://juejin.cn/post/691670…