在 Pascal 中通过引用传递
Pass by reference in Pascal
我不确定我是否正确理解了 Pascal 中引用传递的工作原理。它是像在 C++ 中那样创建一个别名 (https://isocpp.org/wiki/faq/references),还是像在 C 中那样工作并且过程获取指向变量的指针并使用该指针。
我想我可以将我的问题表述为:Pascal 是否支持真正的引用传递,还是通过共享调用来完成。
例如 FreePascal 参考声明,该过程获取一个指针(https://www.freepascal.org/docs-html/current/ref/refsu65.html), but according to https://swinbrain.ict.swin.edu.au/wiki/Pass_by_Value_vs._Pass_by_Reference#Conclusion and for example https://cgi.csc.liv.ac.uk/~frans/OldLectures/2CS45/paramPassing/paramPassing.html#callByReference 在 Pascal 中通过引用传递的方式与在 C 中不同(在 C 中传递指针)。
如果有人能解释一下差异或引用传递的含义如何变化(在现代语言中我们说引用传递,但实际上它们是按值传递,例如 Java).那么按引用传递的原始含义是什么,它是如何工作的呢? Pascal 中的情况如何?
非常感谢。
按引用传递
in Delphi,按引用传递(使用var或out)表示传递一个指针。但是要注意语义"pass by value"或"pass by reference"和实际传递是有区别的
var 或 out
与 C 的区别不在于实际传递(通过指针),而在于这些关键字的含义。 Var 只是通过引用传递。 Out 以不同方式处理某些托管类型,因为使用 out,您告诉编译器输入值不能保证被初始化。否则,这些在技术上是相同的(但在语义上不同):传递(IOW,指向)原始值的地址。
语义<>实际传递
语义
- 按值传递的语义是例程获取原始值的本地副本,并且例程可以(如果允许)在不影响原件的情况下更改它。更改应用于本地副本并在例程结束时消失。
- 通过引用传递的语义是您正在访问(并且可能修改)原始。
实际低水平传球可能不同
但出于优化原因,实际生成的代码可能有所不同。特别是较大的结构(多大取决于版本和平台——这在某处有记录并且随着时间的推移而改变)通常通过引用通过(如指针),即使语义说 按值传递 。在例程的序言中(在第一个 begin
之前运行的隐藏代码),这些结构然后被 复制 到例程的本地框架。 请注意,并非所有调用约定都这样做。有时在调用之前对参数堆栈进行了完整复制,并且没有传递指针。
由于你还有一个本地副本,并且不修改原件,这仍然被认为是按值传递,只是就像你宣布的那样。所有技术上的东西对用户来说都是透明的,所以区别只是在较低的层次上。这仅在您编写汇编程序或必须读取 CPU window.
中的生成代码时才重要
常量
如果声明了值参数const
,原始值无论如何都不会被修改,因此可以省略将大型结构复制到本地框架并且可以通过引用访问(只读)值(指针),即使 语义 是 按值传递 。在这种情况下,小值总是按值传递(在寄存器中或堆栈中)。
虽然将 const var
作为参数修饰符没有意义(某些东西要么是常量要么是变量,但不能同时是两者),您仍然可以强制通过引用传递 const
参数,通过使用 [ref]
修饰符属性:const [ref] X: Integer
或 [ref] const X: Integer
。整数通常按值传递(在寄存器中或堆栈中,具体取决于调用约定和平台)。
引用类型
请注意,如果您按值传递引用类型(例如对象、动态数组等),按值传递语义仅适用于引用 自己,即你得到了它们的副本,你可以修改 reference 而不会影响原件。
但是:这些引用指向的项(对象、数组等)可以被修改。只有 references 本身是本地副本,而不是它们指向的内容。因此,如果您在按值传递的对象引用上调用方法,那么该对象(在堆上)可能会被修改!这不是因为按值传递或按常量传递不能正常工作,而是因为引用是什么。
结论
- 按值传递意味着对原件进行复制,并且可以在本地修改该副本。
- 按引用传递意味着传递对原始文件的引用(指针),任何修改都会修改原始文件。
- 在幕后,技术上,在按值传递语义中,毕竟可以传递指针。如果有必要,会制作一份原件的副本,所以毕竟没有修改原件。
- 按值传递引用类型,甚至像 const 一样传递引用类型不是意味着它们指向的原始(堆)项目防止被修改.没有类似 C++ 的 const 概念来防止这种情况。
关于引用和指针的更多信息可以在我的(非常受欢迎的)文章 Addressing pointers 中找到。
比其他答案(非常好)更简单。
does it work similarly as in C and the procedure gets a pointer to the variable and uses this pointer.
是的,这就是引擎盖下发生的事情。该过程实际上获得了一个指向变量的指针,一个地址。但是编译器知道这一点,使其变得透明。因此,在声明参数 "a" 的过程中,语句:
a := a div 2;
可以用两种不同的方式编译。如果 a
参数是正常声明的,即按值传递,则语句编译为:
1 - 加载地址 "a"
处的值
2 - 整数除以二
3 - 将结果存储在地址 "a"
如果参数声明为 var
,意思是通过引用传递,编译器会:
1 - 加载地址 "a"
处的值
2 - 在刚刚加载的地址加载值(这是一个指针解引用)
3 - 除法
4 - 存储回来
如果源码为:
,以上4条语句正是C编译出来的
*a = *a / 2;
I guess I could formulate my question as: Does Pascal support true passing by reference ...?
答案是肯定的,通过引用传递是正确的,而且没有多少语言能如此干净利落地做到这一点。调用过程的源代码不会改变,无论该过程是用 "by reference" 还是 "by value" 调用的。同样,编译器知道如何传递形式参数,因此隐藏了细节,这与 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 更优雅,程序员应该知道程序将做什么。
所有这些都是针对 "simple" 类型的。如果我们谈论字符串(现代 Pascal 有两种风格),在 Pascal 中是一样的。编译器复制,增加引用计数,做任何需要的事情来完全支持按值或引用传递。 C语言没有类似的东西。
如果我们谈论 类,事情就不同了,但是由于 类.
的语义,它们 肯定 不同
我希望对另一个完整的答案有所补充。
术语“引用”是在编程语言的早期发明的,在 Algol、Fortran 或 Pascal 中。
引用是共享别名。 reference 的基本技术 属性 是共享实例的地址。在 C++ 中,引用等同于 de-referenced 指针。
// C++
int z = 29; // instance
int& alias = z; // reference
int* p = &alias; // pointer, address of z
int& r = *p; // reference
在 Pascal 中,引用仅作为过程或函数的 var-parameters 或 de-referenced 指针存在
// PASCAL
z: INTEGER = 29; // instance
p: ^INTEGER = @z; // pointer, address of z
// p^ is a reference
PROCEDURE RefDemo(var x: integer)
VAR q: ^INTEGER;
BEGIN
q =@x;
RefDemo(q^);
END;
此 var x: INTEGER
在 C++ 中存在等效项
void RefDemo(short int& x)
{
short int* q = &x;
RefDemo(*q);
}
回想起来,这样的引用 p^
也存在于 C 中,如 *p
。虽然发明者没有使用这个命名。他们甚至决定将指针称为值,而不是引用实例 (-value)。
现在对对象做同样的事情
// Pascal
type
Books = OBJECT
title: packed array [1..50] of char;
author: packed array [1..50] of char;
subject: packed array [1..100] of char;
book_id: integer;
end;
...
PROCEDURE RefDemo(var book: Books)
VAR q: ^Books;
otherBook: Books;
BEGIN
book.author[1] := '-';
book := otherBook; // assigns all attributes
RefDemo(q^);
END;
// C++
class Books
{
public:
char title[50];
char author[50];
char subject[50];
short int book_id;
}
...
void RefDemo(Books& book)
Books *q;
Books otherBook;
{
q = &book;
book.author[0] = '-';
book = otherBook; // assigns all attributes [1]
RefDemo(*q);
}
// C emulation
typedef struct _Books
{
char title[50];
char author[50];
char subject[50];
short int book_id;
} Books;
...
void RefDemo(Books* book)
Books *q;
Books otherBook;
{
q = book;
book->author[0] = '-';
*book = otherBook; // assigns all attributes [1]
RefDemo(*q);
}
离题,但为了完成评论:
在 Java 中,对象参数的行为与 C 中的“By-Reference”模拟非常相似,只是我们没有 de-referentiation 运算符和 built-in 赋值运算符来完成[1].
中的对象实例
Java 的设计者遵循 C 的想法并打破了 [=] 的范式58=]是面向对象的语言,他们将对象引用视为值而不是对象实例,这就是无尽的起源关于 calling-conventions.
的讨论
我们永远不会知道,对于像“Java is pass by value?”这样的甚至语法薄弱的语句,Niklaus Wirth 会怎么说。
我不确定我是否正确理解了 Pascal 中引用传递的工作原理。它是像在 C++ 中那样创建一个别名 (https://isocpp.org/wiki/faq/references),还是像在 C 中那样工作并且过程获取指向变量的指针并使用该指针。
我想我可以将我的问题表述为:Pascal 是否支持真正的引用传递,还是通过共享调用来完成。
例如 FreePascal 参考声明,该过程获取一个指针(https://www.freepascal.org/docs-html/current/ref/refsu65.html), but according to https://swinbrain.ict.swin.edu.au/wiki/Pass_by_Value_vs._Pass_by_Reference#Conclusion and for example https://cgi.csc.liv.ac.uk/~frans/OldLectures/2CS45/paramPassing/paramPassing.html#callByReference 在 Pascal 中通过引用传递的方式与在 C 中不同(在 C 中传递指针)。
如果有人能解释一下差异或引用传递的含义如何变化(在现代语言中我们说引用传递,但实际上它们是按值传递,例如 Java).那么按引用传递的原始含义是什么,它是如何工作的呢? Pascal 中的情况如何?
非常感谢。
按引用传递
in Delphi,按引用传递(使用var或out)表示传递一个指针。但是要注意语义"pass by value"或"pass by reference"和实际传递是有区别的
var 或 out
与 C 的区别不在于实际传递(通过指针),而在于这些关键字的含义。 Var 只是通过引用传递。 Out 以不同方式处理某些托管类型,因为使用 out,您告诉编译器输入值不能保证被初始化。否则,这些在技术上是相同的(但在语义上不同):传递(IOW,指向)原始值的地址。
语义<>实际传递
语义
- 按值传递的语义是例程获取原始值的本地副本,并且例程可以(如果允许)在不影响原件的情况下更改它。更改应用于本地副本并在例程结束时消失。
- 通过引用传递的语义是您正在访问(并且可能修改)原始。
实际低水平传球可能不同
但出于优化原因,实际生成的代码可能有所不同。特别是较大的结构(多大取决于版本和平台——这在某处有记录并且随着时间的推移而改变)通常通过引用通过(如指针),即使语义说 按值传递 。在例程的序言中(在第一个 begin
之前运行的隐藏代码),这些结构然后被 复制 到例程的本地框架。 请注意,并非所有调用约定都这样做。有时在调用之前对参数堆栈进行了完整复制,并且没有传递指针。
由于你还有一个本地副本,并且不修改原件,这仍然被认为是按值传递,只是就像你宣布的那样。所有技术上的东西对用户来说都是透明的,所以区别只是在较低的层次上。这仅在您编写汇编程序或必须读取 CPU window.
中的生成代码时才重要常量
如果声明了值参数const
,原始值无论如何都不会被修改,因此可以省略将大型结构复制到本地框架并且可以通过引用访问(只读)值(指针),即使 语义 是 按值传递 。在这种情况下,小值总是按值传递(在寄存器中或堆栈中)。
虽然将 const var
作为参数修饰符没有意义(某些东西要么是常量要么是变量,但不能同时是两者),您仍然可以强制通过引用传递 const
参数,通过使用 [ref]
修饰符属性:const [ref] X: Integer
或 [ref] const X: Integer
。整数通常按值传递(在寄存器中或堆栈中,具体取决于调用约定和平台)。
引用类型
请注意,如果您按值传递引用类型(例如对象、动态数组等),按值传递语义仅适用于引用 自己,即你得到了它们的副本,你可以修改 reference 而不会影响原件。
但是:这些引用指向的项(对象、数组等)可以被修改。只有 references 本身是本地副本,而不是它们指向的内容。因此,如果您在按值传递的对象引用上调用方法,那么该对象(在堆上)可能会被修改!这不是因为按值传递或按常量传递不能正常工作,而是因为引用是什么。
结论
- 按值传递意味着对原件进行复制,并且可以在本地修改该副本。
- 按引用传递意味着传递对原始文件的引用(指针),任何修改都会修改原始文件。
- 在幕后,技术上,在按值传递语义中,毕竟可以传递指针。如果有必要,会制作一份原件的副本,所以毕竟没有修改原件。
- 按值传递引用类型,甚至像 const 一样传递引用类型不是意味着它们指向的原始(堆)项目防止被修改.没有类似 C++ 的 const 概念来防止这种情况。
关于引用和指针的更多信息可以在我的(非常受欢迎的)文章 Addressing pointers 中找到。
比其他答案(非常好)更简单。
does it work similarly as in C and the procedure gets a pointer to the variable and uses this pointer.
是的,这就是引擎盖下发生的事情。该过程实际上获得了一个指向变量的指针,一个地址。但是编译器知道这一点,使其变得透明。因此,在声明参数 "a" 的过程中,语句:
a := a div 2;
可以用两种不同的方式编译。如果 a
参数是正常声明的,即按值传递,则语句编译为:
1 - 加载地址 "a"
处的值
2 - 整数除以二
3 - 将结果存储在地址 "a"
如果参数声明为 var
,意思是通过引用传递,编译器会:
1 - 加载地址 "a"
处的值
2 - 在刚刚加载的地址加载值(这是一个指针解引用)
3 - 除法
4 - 存储回来
如果源码为:
,以上4条语句正是C编译出来的*a = *a / 2;
I guess I could formulate my question as: Does Pascal support true passing by reference ...?
答案是肯定的,通过引用传递是正确的,而且没有多少语言能如此干净利落地做到这一点。调用过程的源代码不会改变,无论该过程是用 "by reference" 还是 "by value" 调用的。同样,编译器知道如何传递形式参数,因此隐藏了细节,这与 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 更优雅,程序员应该知道程序将做什么。
所有这些都是针对 "simple" 类型的。如果我们谈论字符串(现代 Pascal 有两种风格),在 Pascal 中是一样的。编译器复制,增加引用计数,做任何需要的事情来完全支持按值或引用传递。 C语言没有类似的东西。
如果我们谈论 类,事情就不同了,但是由于 类.
的语义,它们 肯定 不同我希望对另一个完整的答案有所补充。
术语“引用”是在编程语言的早期发明的,在 Algol、Fortran 或 Pascal 中。
引用是共享别名。 reference 的基本技术 属性 是共享实例的地址。在 C++ 中,引用等同于 de-referenced 指针。
// C++
int z = 29; // instance
int& alias = z; // reference
int* p = &alias; // pointer, address of z
int& r = *p; // reference
在 Pascal 中,引用仅作为过程或函数的 var-parameters 或 de-referenced 指针存在
// PASCAL
z: INTEGER = 29; // instance
p: ^INTEGER = @z; // pointer, address of z
// p^ is a reference
PROCEDURE RefDemo(var x: integer)
VAR q: ^INTEGER;
BEGIN
q =@x;
RefDemo(q^);
END;
此 var x: INTEGER
在 C++ 中存在等效项
void RefDemo(short int& x)
{
short int* q = &x;
RefDemo(*q);
}
回想起来,这样的引用 p^
也存在于 C 中,如 *p
。虽然发明者没有使用这个命名。他们甚至决定将指针称为值,而不是引用实例 (-value)。
现在对对象做同样的事情
// Pascal
type
Books = OBJECT
title: packed array [1..50] of char;
author: packed array [1..50] of char;
subject: packed array [1..100] of char;
book_id: integer;
end;
...
PROCEDURE RefDemo(var book: Books)
VAR q: ^Books;
otherBook: Books;
BEGIN
book.author[1] := '-';
book := otherBook; // assigns all attributes
RefDemo(q^);
END;
// C++
class Books
{
public:
char title[50];
char author[50];
char subject[50];
short int book_id;
}
...
void RefDemo(Books& book)
Books *q;
Books otherBook;
{
q = &book;
book.author[0] = '-';
book = otherBook; // assigns all attributes [1]
RefDemo(*q);
}
// C emulation
typedef struct _Books
{
char title[50];
char author[50];
char subject[50];
short int book_id;
} Books;
...
void RefDemo(Books* book)
Books *q;
Books otherBook;
{
q = book;
book->author[0] = '-';
*book = otherBook; // assigns all attributes [1]
RefDemo(*q);
}
离题,但为了完成评论:
在 Java 中,对象参数的行为与 C 中的“By-Reference”模拟非常相似,只是我们没有 de-referentiation 运算符和 built-in 赋值运算符来完成[1].
中的对象实例Java 的设计者遵循 C 的想法并打破了 [=] 的范式58=]是面向对象的语言,他们将对象引用视为值而不是对象实例,这就是无尽的起源关于 calling-conventions.
的讨论我们永远不会知道,对于像“Java is pass by value?”这样的甚至语法薄弱的语句,Niklaus Wirth 会怎么说。