likes
comments
collection
share

Dart 点将台 | 你真的明白参数传递吗?

作者站长头像
站长
· 阅读数 30

参数传递,是编程开发中最最最常见的一种行为。我们将一个 对象 传入到函数中作为输入,参与函数逻辑运算,得到输出值。可能很多人被值传递、引用传递、指针传递这些弯弯绕绕的跟困住了。其实面向对象的高级语言中,没有所谓的指针,对象本身 是值,也是一块 内存区域的地址引用

在高级语言中,参数传递的是对象,也只能是对象,别无其他。


level1: 为什么数字没改变

如下所示,在 chang 方法前后,x 的值 未发生变化

void main() {
  int x = 0;
  print("::before:: $x"); // 0
  chang(x);
  print("::after:: $x"); // 0
}

void chang(int a){
  a = 3;
}

但如果在 moveX 中传入 Point 对象,修改其中的 x 属性,可以发现传入的 p0 会发生变化

void main() {
  Point p0 = Point();
  print("::before:: $p0"); // Point{x: 0, y: 0}
  moveX(p0);
  print("::after:: $p0"); // Point{x: 1, y: 0}
}

void moveX(Point p){
  p.x =1;
}

class Point {
  int x = 0;
  int y = 0;

  @override
  String toString() {
    return 'Point{x: $x, y: $y}';
  }
}

对于这个现象,可能有人会解释为:int 是基本数据类型,所以会拷贝一份;Point 是自定义的类型,传入的是引用。其实从内存地址的角度来看:

左图: p0 对象在 0x0001 地址,存储着两个值: x,y 中图: moveX 函数中,入参 p 对指向 p0 的内存地址 右图: moveX 执行 p.x =1 ,就是访问内存地址,将其中的 x 改为 1.

期间 p0 始终指向 0x0001 ,所以该地址中的值的变化,会影响 p0 对象的值。而 p 对象作为函数内的临时变量,在函数出栈时被释放:

Dart 点将台 | 你真的明白参数传递吗?


level2: 请关注内存地址

现在来使个坏,在 moveX 中,先将 p 赋值为 Point() ,然后再改变 p.x 的值。你觉得 p0 在 moveX 后会不会改变呢?

void main() {
  Point p0 = Point();
  print("::before:: $p0");
  moveX(p0);
  print("::after:: $p0");
}

void moveX(Point p){
  p = Point();
  p.x =1;
}

答案是 p0 不会变,其实从内存的角度来看,是非常好理解的。

左图: p0 对象在 0x0001 地址,存储着两个值: x,y 中图: moveX 函数中,入参 p 对指向 p0 的内存地址 右图: p = Point(); 这个动作,会让 p 对象的内存地址指向新的对象。

Dart 点将台 | 你真的明白参数传递吗?

所以接下来对 p 对象的修改,就不管 0x0001 的事了。就像你在 0x0002 家搞装修,0x0001 家肯定不会发生变化。由于 p 是局部变量,在 moveX 方法出栈时,将被销毁。这就是 p0 为什么没有变的原因:

Dart 点将台 | 你真的明白参数传递吗?


现在我们再从内存的角度来看待,为什么上面的 change 方法没有改变 x 。对于 Dart 而言一切皆为对象,占据内存空间,int 类型对象也一样。 当 a=3 时,就像 p = Point(); 一样,指向了另一个内存空间

Dart 点将台 | 你真的明白参数传递吗?

大家可以思考一下,将 moveX 改为如下形式,会得到什么结果,为什么?

void moveX(Point p){
  p.x =1;
  p = Point();
  p.x =7;
}

level3: 赋值为空能成功吗?

如下所示,moveX 中将 p 赋值为 null,后续的输出打印是空吗?

void main() {
  Point p0 = Point();
  print("::before:: $p0");
  moveX(p0);
  print("::after:: $p0");

}

void moveX(Point? p){
  p = null;
}

如果明白了内存的分析方式,很容易理解:局部变量 p 指向 null 并不会影响到 p0 家里的数据。

Dart 点将台 | 你真的明白参数传递吗?


level4: 回调函数

现在再变态一点,如果 moveX 中有一个回调,可以将函数内的局部变量回调出去,此时在回调在 p0 赋值为回调值 p ,在内存中发生了什么呢?

void main() {
  Point p0 = Point();
  print("::before:: $p0");
  moveX(p0, (p) => p0 = p);
  print("::after:: $p0");
}

void moveX(Point a, Function(Point p) callback) {
  a = Point();
  a.x = 4;
  callback(a);
}

moveX 中的 a 对象会作为 callback 函数的参数,也就是说 a 和 回调处理中的 p 指向同一个内存地址,当 p0 = p ,就相当于将 p0 的搬到了 a 的家里,p0 原先的家就没有任何对象指向他,也就是没有引用,将会被 gc 回收。


小结:

高级语言的对象并没有能力直接访问指针来修改内存地址中的数据。对象表面是一个值,背后指向一块内存地址。就像大古既是光也是人类,对象既是值,也是地址引用。参数传递过程中:

只是通过 函数局部变量 ,记录入参对象。 局部变量修改入参对象指向的内存地址数据,相当于你在我家装修,我家的表现肯定会变。 局部变量的内存地址的指向改变,相当于你到别家装修,关我屁事。

高级语言中函数入参的传递,是 对象传递,对象的正反两面兼具 地址 的特征。所以分析参数传递,最重要的是把握对象地址的指向,对象指向地址的数据就是该对象的

转载自:https://juejin.cn/post/7371779128478269479
评论
请登录