Flutter-Daily-Interview icon indicating copy to clipboard operation
Flutter-Daily-Interview copied to clipboard

第001期 dart是值传递还是引用传递?

Open wyufeng02 opened this issue 5 years ago • 8 comments

wyufeng02 avatar Aug 03 '19 15:08 wyufeng02

dart是值传递。我们每次调用函数,传递过去的都是对象的内存地址,而不是这个对象的复制。 先来看段代码

main(){
  Test a = new Test(5);
  print("a的初始值为:${a.value}");
  setValue(a);
  print("修改后a的值为: ${a.value}");
}

class Test{
  int value = 1;
  Test(int newValue){
    this.value = newValue;
  }
}

setValue(Test s){
  print("修改value为100");
  s.value = 100;
}

输出结果为:

a的初始值为:5
修改value为100
修改后a的值为:100

从这里可以看出是值传递,如果只是复制了一个对象,然后把这个新建的对象地址传递到函数里面的话,setValue()函数中的修改是不会影响到main函数中的a的,因为二者所引用的内存地址是不一样。 有些人可能会以以下代码反驳我:

main(){
  int s = 6;
  setValue(s);
  print(s); //输出6,而不是7
}

class Test{
  int value = 1;
  Test(int newValue){
    this.value = newValue;
  }
}

setValue(int s){
  s += 1;
}

你看,这输出的不是6吗,在dart中一切皆为对象,如果是引用传递,那为什么是6啊。 答案是这样的,在setValue()方法中,参数s实际上和我们初始化int s = 6的s不是一个对象,只是他们现在指的是同一块内存区域,然后在setValue()中调用s += 1的时候,这块内存区域的对象执行+1操作,然后在堆(类比java)中产生了一个新的对象,s再指向这个对象。所以s参数只是把main函数中的s的内存地址复制过去了,就比如java中的:

public class Test {
    public static void main(String[] args) {
        Test a = new Test();
        Test b = a;    
        b = new Test();
    }
}

我们只要记住一点,参数是把内存地址传过去了,如果对这个内存地址上的对象修改,那么其他位置的引用该内存地址的变量值也会修改。千万要记住dart中一切都是对象。

Natoto avatar Aug 06 '19 07:08 Natoto

你麻痹我被误导了

nEdAy avatar Aug 27 '20 06:08 nEdAy

我刚学flutter被你弄懵了。 如果我理解没有错的话应该是: 值传递是深拷贝,也就是基本类型,不是对象的类型。引用传递是浅拷贝,传递的如果是对象,那就是引用传递。

codewpf avatar Sep 05 '20 01:09 codewpf

这里应该叫引用传递

Jaykong avatar Dec 09 '20 08:12 Jaykong

哈哈,看不下去了。确实误人子弟,简书上也是这个。

wuyanghu avatar May 18 '21 00:05 wuyanghu

对值传递和引用传递的定义是不是存在什么误解? 其它编程语言都是传递地址是引用传递,将对象复制后赋值是值传递。 另外不讲明场景,说一门语言是值传递还是引用传递,这不是耍流氓么?

xx-li avatar Oct 21 '21 00:10 xx-li

https://stackoverflow.com/questions/54545977/dart-variable-store-reference-to-the-value

zinc357 avatar Apr 27 '22 03:04 zinc357

不要误人子弟。

函数里的参数是值传递还是引用传递

Dart内,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

关于这个问题,引发过很多广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Dart中的参数传递是值传递,但是说不出来为什么。

在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。

  1. 值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
  2. Dart是引用传递。
  3. 传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

实参与形参 我们都知道,在Dart中定义方法的时候是可以定义参数的,如上的可选参数,参数在程序语言中分为形式参数和实际参数。

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。 实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

简单举个例子:

void main() {
 // 实际参数为 Hollis
 sout("Hollis");
}

void sout(String name) {
 // 形式参数为 name
 print(name);
}

实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。 上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。 有了上面的概念,然后大家就可以写代码实践了,来看看 Dart 中到底是值传递还是引用传递 ,于是,最简单的一段代码出来了:

 int i = 10;
 pass(10);
 print("print in main , i is $i");
}

void pass(int j) {
 j = 20;
 print("print in pass , j is $j");
}

// 输出结果
print in pass , j is 20
print in main , i is 10

可见,pass方法内部对 j 的值的修改并没有改变实际参数 i 的值。那么,按照上面的定义,有人得到结论:Dart 的方法传递是值传递。 但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会搬出以下代码:

 User user = User();
 user.setName("jame");
 pass(user);
 print("print in main , user name is ${user.name}");
}

class User {
 String name;

 void setName(String name) {
 this.name = name;
 }
}

void pass(User user) {
 user.setName("hollis");
 print("print in pass , user name is ${user.name}");
}

// 输出结果
print in pass , user name is hollis
print in main , user name is hollis

经过 pass 方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Dart的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。 但是,这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递:

 String name = "jame";
 pass(name);
 print("print in main , name is $name");
}

void pass(String name) {
 name = "Hollis";
 print("print in pass , name is $name");
}

// 输出结果
print in pass , name is Hollis
print in main , name is jame

这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了? 上面,我们举了三个例子,表现的结果却不一样,这也是导致很多人对于Dart的传递类型有困惑的原因。 其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画一下概念中的重点,然后再举几个真正恰当的例子。

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。 [图片上传失败...(image-31f994-1710928031690)]

我们上面看过的几个pass的例子中,都只关注了实际参数内容是否有改变。如传递的是User对象,我们试着改变他的name属性的值,然后检查是否有改变。其实,在实验方法上就错了,当然得到的结论也就有问题了。

为什么说实验方法错了呢?这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递,然后你就知道为啥错了。

你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。

你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。

但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。

还拿上面的一个例子来举例,我们真正的改变参数,看看会发生什么?

void main() {
 User user = User();
 user.setName("jame");
 pass(user);
 print("print in main , user name is ${user.name}");
}

class User {
 String name;

 void setName(String name) {
 this.name = name;
 }
}

void pass(User user) {
 user = User();
 user.setName("hollis");
 print("print in pass , user name is ${user.name}");
}

当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name等数据。然后hollis持有该内存的地址0x123456。当尝试调用pass方法,并且user作为实际参数传递给形式参数user的时候,会把这个地址0x123456交给user,这时,形参user也指向了这个地址。然后在pass方法内对参数进行修改的时候,即user = new User();,会重新开辟一块0X456789的内存,赋值给user。后面对user的任何修改都不会改变内存0X123456的内容。

上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有。

通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数

我们再来回顾下之前的那个“砸电视”的例子,看那个例子中的传递过程发生了什么。

同样的,在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。

所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。

那么,既然这样,为啥上面同样是传递对象,传递的String对象和User对象的表现结果不一样呢?我们在pass方法中使用name = "hollis";试着去更改name的值,阴差阳错的直接改变了name的引用的地址。因为这段代码,会new一个String,在把引用交给name,即等价于name = new String("hollischuang");。而原来的那个”Hollis”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。

所以说,Dart 中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用,简单点说,Dart 中的传递,是值传递,而这个值,实际上是对象的引用。

作者:ghroosk 链接:https://juejin.cn/post/6931340267324702733

snailflying avatar Mar 20 '24 09:03 snailflying