Dart等现代语言中传递引用的真正含义是什么?

时间:2014-08-06 21:00:47

标签: dart scope variable-assignment pass-by-reference future

在Dart中与Futures合作,我遇到了一个有趣的问题。

import 'dart:async';

class Egg {
  String style;
  Egg(this.style);
}

Future cookEggs(List<Egg> list) =>
  new Future(() =>
    ['omelette','over easy'].forEach((_) => list.add(new Egg(_)))
  );

Future cookOne(Egg egg) => new Future(() => egg = new Egg('scrambled'));

void main() {
  List<Egg> eggList = new List();
  Egg single;

  cookEggs(eggList).whenComplete(() => eggList.forEach((_) => print(_.style));
  cookOne(single).whenComplete(() => print(single.style));
}

预期输出为:

omelette
over easy
scrambled

获取cookEggs的{​​{1}}函数运行正常,但访问List<Eggs>的{​​{1}}属性失败并抛出style

我首先想到这可能与传递引用有关,但我不明白为什么Dart会通过引用而不是single传递NoSuchMethodError。现在我认为差异可能与赋值(List)运算符和Egg方法有关。我陷入了那种思路,我不知道如何检验我的假设。

有什么想法吗?

程序的输出和堆栈跟踪如下所示:

=

3 个答案:

答案 0 :(得分:2)

快速回答:传递给您的函数cookEggscookOne的内容是对对象的引用,而不是对变量的引用(这将是真正的传递引用) )。

术语传递引用经常被误用为传递 - 引用 -by-value :许多语言只传递 - by-value语义,其中传递的值是引用(即指针,没有危险的特征)。见Is Java "pass-by-reference" or "pass-by-value"?

cookEggs(eggList) ...

的情况下

...变量eggList包含对鸡蛋列表的引用。该引用最初由表达式new List()提供给您,并且在您同时存储到变量cookEggs之后将其传递给eggList。在cookEggs内,添加到列表的工作原理,因为您传递了对实际的现有列表对象的引用。

cookOne(single) ...

的情况下

...变量single仅被声明,因此它被语言运行时隐式初始化为特殊引用null。在cookOne内,您正在替换egg中包含的引用;由于single是一个不同的变量,它仍然包含null,因此当您尝试使用该代码时代码会失败。

澄清

许多现代语言(Smalltalk,Java,Ruby,Python ......)都会使用pass-references-by-value行为。当您传递一个对象时,您实际上是在传递值(因此复制)变量的内容,这是一个指向该对象的指针。你永远无法控制对象真正存在的位置。

这些指针是命名引用而不是指针,因为它们被限制为抽象出内存布局:你不能知道对象的地址,你可以查看对象周围的字节,你甚至不能确定一个对象存储在内存中的固定位置,或者它根本存储在内存中(可以将对象引用实现为持久数据库中的UUID或键,如宝石)。

相反,通过引用传递,您在概念上传递变量本身,而不是其内容。要使用按值传递语言实现传递引用,您需要将变量重新声明为可以传递的ValueHolder对象,并且可以更改其内容。

答案 1 :(得分:1)

这是因为,像Java一样,Dart 始终按值传递,并且永远不会通过引用传递。 Dart中传递和赋值的语义与Java中的相同。查看StackOverflow上有关Java的任何位置并按值传递,以查看为什么将Java描述为始终按值传递。应该跨语言一致地使用按值传递传递引用等术语。所以Dart应该被描述为始终按值传递。

在实际的&#34;传递参考&#34;,例如当一个参数在C ++或PHP中用&标记,或者参数在C#中用refout标记时,简单赋值(即使用=)函数内部的参数变量与函数外部原始传递变量的简单赋值(即=)具有相同的效果。这在Dart中是不可能的。

答案 2 :(得分:1)

一个流行的误解是Dart是通过引用传递的。但是,在真正的按引用传递系统(受C ++等语言支持)中,函数可以在调用方中设置(而不仅仅是突变)本地变量。

与许多其他语言(例如Java,Python)一样,Dart在技术上是按值传递的,其中对象的“值”是对其的引用。这就是函数可以对调用方传递的参数进行变异的原因。

但是,在这些一切都是对象且没有单独的指针类型的语言中,“按值传递”和“按引用传递”可能会使术语混淆,并且没有特别的意义。相反,许多Python程序员使用术语pass-by-assignment

按分配传递意味着以与普通变量分配等效的方式传递参数。例如,如果我有:

final int x = 42;
final String s = "hello world!";

void foo(int intArgument, String stringArgument) {
  ...
}

void main() {
  foo(x, s);
}

那么行为将等同于:

final int x = 42;
final String s = "hello world!";

void foo() {
  int intArgument = x;
  String stringArgument = s;
  ...
}

void main() {
  foo();
}

当您执行String stringArgument = s;时,stringArguments是两个指向同一对象的独立变量。将stringArgument重新分配给其他内容不会更改s所指的内容。但是,如果您在适当的位置变异该对象,则stringArguments都将引用现在已修改的对象。