在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
方法有关。我陷入了那种思路,我不知道如何检验我的假设。
有什么想法吗?
程序的输出和堆栈跟踪如下所示:
=
答案 0 :(得分:2)
快速回答:传递给您的函数cookEggs
和cookOne
的内容是对对象的引用,而不是对变量的引用(这将是真正的传递引用) )。
术语传递引用经常被误用为传递 - 引用 -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#中用ref
或out
标记时,简单赋值(即使用=
)函数内部的参数变量与函数外部原始传递变量的简单赋值(即=
)具有相同的效果。这在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;
时,stringArgument
和s
是两个指向同一对象的独立变量。将stringArgument
重新分配给其他内容不会更改s
所指的内容。但是,如果您在适当的位置变异该对象,则stringArgument
和s
都将引用现在已修改的对象。