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


theme: cyanosis

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

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


level1: 为什么数字没改变

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

```dart 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 会发生变化

```dart 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 对象作为函数内的临时变量,在函数出栈时被释放:

image.png


level2: 请关注内存地址

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

```dart 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 对象的内存地址指向新的对象。

image.png

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

image.png


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

image.png

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

dart 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 家里的数据。

image.png


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 回收。


小结:

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

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

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

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值