在类似Dart的现代语言中,按引用传递的真正含义是什么?

问题描述 投票:2回答:3

[在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

获得cookEggsList<Eggs>函数工作正常,但是访问stylesingle属性失败,并抛出NoSuchMethodError

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

有什么想法吗?

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

omelette
over easy
Uncaught Error: The null object does not have a getter 'style'.

NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
Stack Trace: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
#1      main.<anonymous closure> (file:///path/to/eggs.dart:20:51)
#2      _rootRun (dart:async/zone.dart:719)
#3      _RootZone.run (dart:async/zone.dart:862)
#4      _Future._propagateToListeners.handleWhenCompleteCallback (dart:async/future_impl.dart:540)
#5      _Future._propagateToListeners (dart:async/future_impl.dart:577)
#6      _Future._complete (dart:async/future_impl.dart:317)
#7      Future.Future.<anonymous closure> (dart:async/future.dart:118)
#8      _createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:11)
#9      _handleTimeout (dart:io/timer_impl.dart:292)
#10     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:124)


Unhandled exception:
The null object does not have a getter 'style'.

NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
#0      _rootHandleUncaughtError.<anonymous closure>.<anonymous closure> (dart:async/zone.dart:713)
#1      _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
#2      _asyncRunCallback (dart:async/schedule_microtask.dart:32)
#3      _asyncRunCallback (dart:async/schedule_microtask.dart:36)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:128)
dart scope variable-assignment pass-by-reference future
3个回答
3
投票

快速解答:传递给函数cookEggscookOne的是对objects的引用,而不是对变量的引用(这是真正的按引用传递)。

术语pass-by-reference通常被误认为是pass-references-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 ...)中很常见。传递对象时,实际上是按值传递(因此要复制)变量的内容,该变量是指向该对象的指针。您永远无法控制对象真正存在的位置。

那些指针被命名为引用,而不是指针,因为它们被限制为抽象出内存布局:您不知道对象的地址,您无法窥视对象周围的字节,甚至不能确保对象存储在内存中的固定位置,或者完全存储在内存中(可以像在Gemstone中那样将对象引用实现为UUID或持久数据库中的键)。

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


1
投票

这是因为,与Java一样,Dart是always pass-by-value,并且从不传递引用。 Dart中传递和分配的语义与Java中相同。在StackOverflow上有关Java的任何地方查找并按值传递,以了解为什么Java被描述为总是按值传递。诸如pass-by-valuepass-by-reference之类的术语应在所有语言中一致使用。因此Dart应该被描述为始终按值传递。

在实际的“通过引用”中,例如在C ++或PHP中用&标记参数时,或在C#中用refout标记参数时,在函数内部对该参数变量的简单赋值(即=)将具有与简单分配(即=)给函数外部原始传递变量的效果相同。这在Dart中是不可能的。


0
投票

[许多Python程序员使用术语pass-by-assignment。这适用于许多其他语言,例如Java或Dart,其中的一切都是对象,没有单独的指针类型。在这样的语言中,按值传递和按引用传递没有特别的意义。这些语言从技术上讲是按值传递的,其中对象的“值”是对其的引用。

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

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

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

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

那么行为将等同于:

...

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

void main() {
  foo();
}

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

© www.soinside.com 2019 - 2024. All rights reserved.