“通过引用传递”究竟意味着什么?
谁有权决定?
编辑:显然我没有成功地制定我的问题。
我不是在问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通过引用传递对象是完全错误的。
所以,
当然,不同的人目前对“通过引用”的含义有不同的定义。 这就是为什么他们不同意是否通过引用。
但是,无论您使用何种定义,都必须在不同的语言中一致使用它。 你不能说一种语言具有传递价值,并且在另一种语言中具有完全相同的语义,并且说它是通过引用传递的。 指出语言之间的类比是解决这一争议的最好方法,因为虽然人们可能对特定语言中的传递模式有强烈的意见,但是当你将相同的语义与其他语言进行对比时,它有时会带来反直觉的结果,重新思考他们的定义。
如果有人同意这种观点,那么我们也必须考虑大多数语言,包括Python,Ruby,OCaml,Scheme,Smalltalk,SML,Go,JavaScript,Objective-C等多种语言, 只能作为传递值 。 如果其中任何一个都让你感到奇怪或者违反直觉,那么我向你指出为什么你认为这些语言中的对象与Java中的对象之间的语义不同。 (我知道其中一些语言可能会明确声明它们是通过引用传递的;但它们与他们所说的无关;必须根据实际行为对所有语言应用一致的定义。)
以您的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);
我声称上述语义上是相同的; 只有语法不同。 唯一的语法差异是:
*
来表示一个指针类型; Java的引用(指向对象的指针)类型不需要明确的*
。 ->
通过指针访问字段; Java只是使用.
new
为堆上的新对象动态分配内存; C使用malloc
来分配它,然后我们需要初始化内存。 请注意,重要的是,
func(something)
,而不需要做任何事情,比如获取地址或任何东西。 object = null;
函数内部不影响调用范围。 所以两种情况下的语义都是一样的,所以如果你调用Java pass-by-reference,你也必须调用C pass-by-reference。
谁有权决定? 没人,每个人。 你自己决定; 一位作家决定他或她的书; 读者决定是否同意作者。
要理解这个术语,需要在语言的底层(并且用C代码来解释它们而不是忽略)。 参数传递样式是指编译器通常用来创建特定行为的机制。 通常定义如下:
(关于术语的说明:参数是在子例程中定义的变量,参数是在调用中使用的表达式。)
教科书通常也定义了名字传递,但在这里很难解释并不容易。 通过需要也存在。
参数传递风格的重要性在于:通过值传递,对参数所做的任何更改都不会传递给参数; 在传递结果时,对参数所做的任何更改都会在最后传递给参数; 在通过引用传递时,对参数所做的任何更改都会在创建时传递给参数。
某些语言定义了多个传递样式,允许程序员分别为每个参数选择其首选样式。 例如,在Pascal中,默认样式是按值传递的,但程序员可以使用var
关键字来指定按引用传递。 一些其他语言指定一种传球风格。 还有一些语言为不同类型指定不同的样式(例如,在C中,按值传递是默认值,但数组是通过引用传递的)。
现在,在Java中,从技术上讲,我们有一种通过值传递的语言,其中对象变量的值是对象的引用。 无论是在对象变量所涉及的地方使Java传递引用都是一个趣味问题。
你的两个C例子实际上都证明了按值传递,因为C没有传递引用。 只是你传递的价值是一个指针。 传递引用以Perl等语言进行:
sub set_to_one($)
{
$_[0] = 1; # set argument = 1
}
my $a = 0;
set_to_one($a); # equivalent to: $a = 1
这里,变量$a
实际上是通过引用传递的,所以子例程可以修改它。 它不会修改$a
通过间接指向的某个对象; 相反,它修改了$a
本身。
除了Java对象是“引用类型”之外,Java就像C一样,所以你拥有的所有东西(以及你所能传递的所有东西)都是指向它们的指针。 像这样的东西:
void setToOne(Integer i)
{
i = 1; // set argument = 1
}
void foo()
{
Integer a = 0;
setToOne(a); // has no effect
}
实际上并不会改变a
; 它只重新分配i
。