【问题标题】:Pass by reference in Pascal在 Pascal 中通过引用传递
【发布时间】:2019-07-03 10:31:43
【问题描述】:

我不确定我是否正确理解 Pascal 中的引用传递是如何工作的。它是像在 C++ 中那样创建别名 (https://isocpp.org/wiki/faq/references),还是像在 C 中那样工作,并且过程获取指向变量的指针并使用该指针。

我想我可以将我的问题表述为:Pascal 是否支持真正的引用传递,还是通过调用共享来完成。

例如 FreePascal 引用状态,该过程获取一个指针 (https://www.freepascal.org/docs-html/current/ref/refsu65.html),但根据 https://swinbrain.ict.swin.edu.au/wiki/Pass_by_Value_vs._Pass_by_Reference#Conclusion 和例如 https://cgi.csc.liv.ac.uk/~frans/OldLectures/2CS45/paramPassing/paramPassing.html#callByReference 在 Pascal 中的引用传递与在 C 中的工作方式不同(其中传递指针)。

如果有人能解释一下差异或按引用传递的含义如何变化(在现代语言中,我们说按引用传递,但实际上它们是按值传递,例如 Java)。那么 pass by reference 的本义是什么,它是如何工作的呢?那么在 Pascal 中是怎样的呢?

非常感谢。

【问题讨论】:

  • 实际上并没有太大区别,在pascal中你可以使用“var”关键字来传递地址。如果它说不支持,您必须自己构建指针,您也可以在 pascal 中执行此操作。在所有情况下,过程都会获得指向变量的指针。
  • 按引用传递的工作方式与在 C 中的工作方式不同,但它具有不同的语法。它在各个地方都有解释,但请尝试我关于指针和引用的文章,Addressing pointers
  • FWIW,Java 总是按值传递。但它也将references(看起来不像指针的指针)传递给按值的类。在那里,字符串或数组之类的东西也是类,所以可以解决。
  • @Mike:假设你在对我说:我的意思是对类实例的引用,是的。在 Java 中没有任何东西通过引用传递。引用类型也按值传递。引用类型是对非原始对象的引用。所以非基元不通过引用传递;对非基元的引用按值传递。大不同。
  • @Mike:支持我所说的许多参考资料中的两个:journaldev.com/3884/…stackoverflow.com/a/40523/95954。再说一遍:Java 总是按值传递。这显然让初学者感到困惑。

标签: parameter-passing pass-by-reference pascal


【解决方案1】:

比(非常好的)其他答案更简单。

它的工作方式是否与 C 中的类似,并且该过程获取一个指向变量的指针并使用该指针。

是的,这就是幕后发生的事情。该过程实际上得到了一个指向变量的指针,一个地址。但是编译器知道这一点,使它变得透明。因此,在声明参数“a”的过程中,声明:

a := a div 2;

可以用两种不同的方式编译。如果a参数被正常声明,即传值,语句编译如下:

1 - 加载地址“a”处的值
2 - 整数除以二
3 - 将结果存储在地址“a”

如果参数被声明为var,意味着通过引用传递,编译器会这样做:

1 - 在地址“a”处加载值
2 - 在刚刚加载的地址处加载值(这是一个指针解引用)
3 - 划分
4 - 存储回来

如果源码是的话,上面四个语句正是C编译出来的:

*a = *a / 2;

我想我可以将我的问题表述为:Pascal 是否支持真正的通过引用传递...?

答案是肯定的,真正的通过引用传递,没有多少语言能做到这一点如此出色和干净。调用过程的源代码不会改变,无论过程是通过“引用”还是“按值”调用的。与 C 不同,编译器知道如何传递形式参数,再次隐藏细节。 例如,调用一个需要按值传递参数的过程:

myproc(a);  // pascal and C are identical

如果过程期望通过引用传递,情况会有所不同:

myproc(a);  // pascal: identical to before

myproc(&a); // C: the language does not hide anything

关于最后一点,有人认为 C 是最好的,因为它迫使程序员知道传递的变量可以被过程(函数)修改。相反,我认为 pascal 更优雅,程序员应该知道该过程会做什么。

所有这些都是针对“简单”类型的。如果我们谈论字符串(现代帕斯卡有两种风格),那是一样的——在帕斯卡里。编译器复制,增加引用计数,做任何需要完全支持按值或按引用传递的事情。 C 语言没有类似的东西。

如果我们谈论类,事情是不同的,但它们必须因为类的语义而有所不同。

我希望对另一个完整的答案有所补充。

【讨论】:

  • 或多或少; “如果源代码为:*a = *a / 2;,则上述四个语句正是 C 编译的内容。”。我认为它会变成shr [a],1sar [a],1
  • @RudyVelthuis 是的,但这更难理解。无论如何,如果我们降级到硬件,shr [a],1 又是 load-load-divide-store。
  • @linuxfan 非常感谢您的回答,它也很有帮助 :) 希望您不介意我选择了 Rudy 的答案,因为我觉得它更完整。
  • @pjSonic 没问题,我是第一个说我只是在一个非常好的答案中添加一些东西的人。
【解决方案2】:

通过引用传递

在 Delphi 中,通过引用传递(使用 varout)意味着传递一个指针。但请注意,语义“按值传递”或“按引用传递”与实际传递是有区别的。

var 或 out

与 C 的区别不在于实际的传递(通过指针),而在于这些关键字的含义。 Var 只是通过引用传递。 Out 以不同的方式处理某些托管类型,因为使用 out,您告诉编译器输入值不能保证被初始化。否则,它们在技术上是相同的(但在语义上不同):传递(IOW,指向)原始值的地址。

语义 实际传递

语义

  • 按值传递的语义是例程获取原始值的本地副本,并且例程可以(如果允许)更改它而不影响原版的。这些更改适用于本地副本,并在例程结束时消失。
  • 通过引用传递的语义是您正在访问(并且可能修改)原始文件

实际低级通过可能不同

但出于优化的原因,实际生成的代码可能会有所不同。尤其是较大的结构(大小取决于版本和平台——这在某处有文档并随着时间的推移而变化)通常通过引用(作为指针)传递,即使语义说按值传递。在例程的序言(在第一个begin 之前运行的隐藏代码)中,这些结构随后被复制 到例程的本地框架中。 请注意,并非所有调用约定都这样做。有时会在调用之前对参数堆栈进行完整复制,并且不传递指针

由于您仍然有一个本地副本,并且不修改原始副本,因此这仍然被视为按值传递,就像您声明的那样。所有技术内容对用户来说都是透明的,因此差异仅在较低级别。这仅在您编写汇编程序或必须在 CPU 窗口中读取生成的代码时才重要。

常量

如果一个值参数被声明为const,那么原件无论如何都不会被修改,因此可以省略将一个大结构复制到本地框架,并且可以通过引用(指针)访问(只读)值,即使语义按值传递。在这种情况下,小值总是按值传递(在寄存器或堆栈中)。

虽然将const var 用作参数修饰符没有意义(某些东西要么是常量,要么是变量,但不是两者兼有),你仍然可以通过使用@ 来强制const 参数的引用传递987654326@修饰符属性:const [ref] X: Integer[ref] const X: Integer。整数通常按值传递(在寄存器或堆栈中,取决于调用约定和平台)。

引用类型

请注意,如果您按值传递引用类型(例如对象、动态数组等),按值传递语义仅适用于引用本身,即您将获得一份副本,您可以修改参考而不影响原件。

但是:这些引用所指向的项目(对象、数组等)可以修改。只有 references 本身是本地副本,而不是它们指向的内容。因此,如果您在按值传递的对象引用上调用方法,那么该对象(在堆上)可能会被修改!这不是因为按值传递或按常量传递不能正常工作,而是因为引用是什么。

结论

  • 按值传递意味着制作了原件的副本,并且可以在本地修改该副本。
  • 通过引用传递意味着传递了对原始文件的引用(指针),并且任何修改都会修改原始文件
  • 在底层,技术上,在按值传递语义中,指针毕竟可以传递。如有必要,会制作原件的副本,因此原件根本不会被修改。
  • 按值传递引用类型,甚至像 const 一样表示它们指向的原始(堆)项不会被修改。没有类似 C++ 的 const 概念可以防止这种情况发生。

关于引用和指针的更多信息可以在我的(非常受欢迎的)文章Addressing pointers中找到。

【讨论】:

  • Object pascal 可以传递对象或托管类型。如果没有var 声明,这意味着即使参数是按值传递的,实例也会受到方法的影响。如果类型是字符串或对象,效果可能会有所不同。
  • @LURD:传递了一个引用。如果按值传递,则 reference 不会被修改,或者可以在不附加原始引用的情况下进行修改。引用的项(对象、接口)仍然可以修改。我会将其添加到答案中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-05
  • 2011-01-14
  • 2015-01-15
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多