“通过引用传递”到底是什么意思?

谁有权决定?

编辑:显然,我没有成功地很好地表述我的问题。
我不是在问Java的参数传递是如何工作的。我知道看起来像一个保存对象的变量实际上是一个保存对象引用的变量,并且该引用是通过值传递的。这里(在链接线程和其他线程中)和其他地方有很多关于该机制的精细解释。

问题是关于术语“通过引用”的技术含义。(结束编辑)

我不确定这是否是SO的正确问题,如果不是,请道歉,但我不知道更好的地方。这里的其他问题已经说了很多,例如Java是“按引用传递”还是“按值传递”?通过引用传递还是按值传递?,但我还没有找到这个术语含义的权威答案。

我认为“通过引用传递”意味着“将引用(通常是指针)传递给对象”,因此被调用方可以修改调用方看到的对象,而“通过值传递”意味着复制对象,并让被调用方对副本感兴趣(明显的问题:如果对象包含引用,深复制或浅副本怎么办)。
唱FW出现了很多 地方“通过引用”意味着,这里有一些论点认为它意味着更多,但定义仍然读作

一种参数传递模式,其中对实际参数的引用(或者,如果想要在政治上不正确,则使用指针)传递到正式参数中;当被调用方需要正式参数时,它会取消引用指针以获取它。

我没有找到很多地方为这个术语提供更强的定义,在这个页面上,我发现“形式参数的左值设置为实际参数的左值”,如果我理解正确,这里使用相同的定义(“形式参数只是作为实际参数的别名。

事实上,我发现使用更强定义的唯一地方是反对在Java中,对象通过引用传递的概念的地方(这可能是由于我缺乏google-fu)。

所以,如果我把事情弄清楚了,就通过引用

class Thing { ... }
void byReference(Thing object){ ... }
Thing something;
byReference(something);

根据第一个定义将大致对应于(在C中)

struct RawThing { ... };
typedef RawThing *Thing;
void byReference(Thing object){
    // do something
}
// ...
struct RawThing whatever = blah();
Thing something = &whatever;
byReference(something); // pass whatever by reference
// we can change the value of what something (the reference to whatever) points to, but not
// where something points to

从这个意义上说,说Java通过引用传递对象就足够了。但根据第二个定义,通过引用或多或少意味着

struct RawThing { ... };
typedef RawThing *RawThingPtr;
typedef RawThingPtr *Thing;
void byReference(Thing object){
    // do something
}
// ...
RawThing whatever = blah();
RawThingPtr thing_pointer = &whatever;
byReference(&thing_pointer); // pass whatever by reference
// now we can not only change the pointed-to (referred) value,
// but also where thing_pointer points to

由于Java只允许你有指向对象的指针(限制了你可以用它们做什么),但没有指针到指针,从这个意义上说,说Java通过引用传递对象是完全错误的。

所以

  1. 我是否充分理解了上述引用传递的定义?
  2. 周围还有其他定义吗?
  3. 是否有共识哪个定义是“正确的”,如果是,哪个定义?

答案 1

当然,目前不同的人对“通过引用”的含义有不同的定义。这就是为什么他们对某些东西是否是通过引用存在分歧的原因。

但是,无论使用哪种定义,都必须跨语言一致地使用它。你不能说一种语言具有按值传递,而在另一种语言中具有完全相同的语义,并说它是按引用传递的。指出语言之间的类比是解决这一争议的最好方法,因为尽管人们可能对特定语言中的传递模式有强烈的意见,但当你将相同的语义与其他语言进行比较时,它有时会带来反直觉的结果,迫使他们重新思考他们的定义。

  • 一种主要观点认为Java只是按值传递的。(在互联网上到处搜索,你会发现这个观点。这种观点认为对象不是值,但始终通过引用进行操作,因此按值分配或传递的是引用。这种观点认为,逐引用传递的检验是是否可以在调用范围内分配给变量。

如果一个人同意这个观点,那么人们还必须考虑大多数语言,包括Python,Ruby,OCaml,Scheme,Smalltalk,SML,Go,JavaScript,Objective-C等各种语言。如果其中任何一个让你觉得奇怪或违反直觉,我挑战你指出为什么你认为任何这些语言中的对象语义与Java中的对象之间的语义是不同的。(我知道其中一些语言可能明确声称它们是按引用传递的;但它们所说的无关紧要;必须根据实际行为对所有语言应用一致的定义。

  • 如果您认为 Java 中的对象是逐个引用传递的相反观点,那么还必须将 C 视为逐个引用传递。

以你的 Java 为例:

class Thing { int x; }
void func(Thing object){ object.x = 42; object = null; }
Thing something = null;
something = new Thing();
func(something);

在C中,它等效于:

typedef struct { int x; } Thing;
void func(Thing *object){ object->x = 42; object = NULL; }
Thing *something = NULL;
something = malloc(sizeof Thing);
memset(something, 0, sizeof(something));
func(something);
// later:
free(something);

我声称上述内容在语义上是等价的;只是语法不同。唯一的语法差异是:

  1. C需要显式来表示指针类型;Java 的引用(指向对象的指针)类型不需要显式 。**
  2. C用于通过指针访问字段;Java只是使用->.
  3. Java用于为堆上的新对象动态分配内存;C用于分配它,然后我们需要初始化内存。newmalloc
  4. Java有垃圾回收

请注意,重要的是,

  1. 在这两种情况下,使用对象调用函数的语法是相同的:,而无需执行任何操作,例如获取地址或任何内容。func(something)
  2. 在这两种情况下,对象都是动态分配的(它可能超出函数的范围)。和
  3. 在这两种情况下,函数内部都不会影响调用范围。object = null;

因此,这两种情况下的语义是相同的,因此,如果您调用Java pass-by-reference,则也必须调用C pass-by-reference。


答案 2

谁有权决定?没有人,还有每个人。你自己决定;作家决定他或她的书;读者决定是否同意作者的观点。

要理解这个术语,人们需要进入语言的引擎盖(用C代码解释它们而不是错过了重点)。参数传递样式是指编译器通常用于创建某些行为的机制。通常定义以下内容:

  • pass by value:输入子例程时,参数被复制到参数中
  • pass by result:输入子例程时参数未定义,子例程返回时,参数复制到参数
  • pass by value-result:参数在输入时复制到参数中,在返回时将参数复制到参数中
  • 通过引用传递:对参数变量的引用被复制到参数;参数变量的任何访问都透明地转换为参数变量的访问

(术语说明:参数是在子例程中定义的变量,参数是在调用中使用的表达式。

教科书通常也通过名称来定义传递,但这很少见,在这里不容易解释。通过需求也存在。

参数传递样式的重要性在于其效果:在传递值时,对参数所做的任何更改都不会传达给参数;在传递结果中,对参数所做的任何更改都将传达到末尾的参数;在通过引用传递时,对参数所做的任何更改都会在进行时传达给参数。

一些语言定义了多个传递样式,允许程序员为每个参数单独选择他们的首选样式。例如,在 Pascal 中,默认样式是按值传递,但程序员可以使用关键字指定按引用传递。其他一些语言指定一种传递样式。还有一些语言为不同类型的类型指定不同的样式(例如,在C中,传递值是默认值,但数组是通过引用传递的)。var

现在,在Java中,从技术上讲,我们有一种具有按值传递的语言,对象变量的值是对对象的引用。这是否使得Java在涉及对象变量的情况下逐个引用是一个品味问题。