谁有权力决定?
编辑:显然我没有成功地提出我的问题。
我不是问Java 的参数传递是如何工作的。我知道看起来像保存对象的变量实际上是保存对象引用的变量,并且该引用是按值传递的。
这里(在链接的线程和其他线程中)和其他地方对该机制有很多很好的解释。
问题是关于“按引用传递”一词的技术含义。 (编辑结束)
我不确定这是否是正确的问题,如果不是,抱歉,但我不知道更好的地方。这里的其他问题已经说了很多,例如 Java 是“pass-by-reference”还是“按值传递”? 和 通过引用传递或传递按值?,但我还没有找到该术语含义的权威答案。
我认为“按引用传递”意味着“将引用(通常是指针)传递给对象”,因此被调用者可以修改调用者看到的对象,而“按值传递”意味着复制对象,并让被调用者享受复制的乐趣(明显的问题:如果对象包含引用、深复制或浅复制怎么办)。
Sing the FW 出现 很多 地点 < a href="http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference" rel="nofollow noreferrer">说“通过引用传递”意味着只是,这里有一些争论认为它意味着更多,但定义仍然是这样的
参数传递模式,其中对实际参数的引用(或者如果你想在政治上不正确,则为指针)被传递到形式参数中;当被调用者需要形式参数时,它会取消引用指针来获取它。
在 this页面,我发现“形参的左值设置为实参的左值”。并且,如果我理解正确的话,这里使用相同的定义(“形式参数仅起作用作为实际参数的别名。”)
事实上,我发现唯一使用更强定义的地方是反对 Java 中对象通过引用传递这一概念的地方(这可能是由于我缺乏 google-fu )。
按引用传递
class Thing { ... }
void byReference(Thing object){ ... }
Thing something;
byReference(something);
因此,如果我搞清楚了,根据第一个定义的
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
将大致对应于(在 C 中) ,从这个意义上说,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 通过引用传递对象是完全错误的。
那么,
- 我是否充分理解了上述引用传递的定义?
- 还有其他定义吗?
- 是否就哪个定义是“正确的”达成共识?如果是,那么哪个定义是“正确的”?
And who has the authority to decide?
Edit: Apparently I haven't succeeded in formulating my question well.
I am not asking how Java's argument passing works. I know that what looks like a variable holding an object is actually a variable holding a reference to the object, and that reference is passed by value.
There are lots of fine explanations of that mechanism here (in the linked threads and others) and elsewhere.
The question is about the technical meaning of the term pass-by-reference. (End edit)
I am not sure if this is the right kind of question for SO, apologies if not, but I don't know a better place. Much has already been said in other questions here, for example Is Java "pass-by-reference" or "pass-by-value"? and pass by reference or pass by value?, but I haven't found an authoritative answer to the question what the term means.
I have thought that "pass by reference" means "pass a reference (usually a pointer) to the object", so the callee can modify the object the caller sees, while "pass by value" means copying the object, and letting the callee have fun with the copy (obvious problem: what if the object contains references, deep copy or shallow).
Sing the FW turns up lots of places saying "pass by reference" means just that, here there's some argument that it means more, but the definition still reads
A ParameterPassing mode where a reference (or if you want to be politically incorrect, a pointer) to the actual parameter is passed into the formal parameter; when the callee needs the formal parameter it dereferences the pointer to get it.
I haven't found many places giving a stronger definition for the term, on this page, I found "The lvalue of the formal parameter is set to the lvalue of the actual parameter." and, if I understand correctly, the same definition is used here ("The formal parameter merely acts as an alias for the actual parameter.")
In fact, the only places I found where the stronger definition is used are places arguing against the notion that in Java, objects are passed by reference (that may be due to my lacking google-fu).
So, if I got things straight, pass-by-reference
class Thing { ... }
void byReference(Thing object){ ... }
Thing something;
byReference(something);
according to the first definition would roughly correspond to (in 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
and in that sense, saying that Java passes objects by reference would be adequate. But according to the second definition, pass-by-reference means more or less
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
And since Java only lets you have pointers-to-objects (limiting what you can do with them) but doesn't have pointers-to-pointers, in that sense, saying that Java passes objects by reference is totally wrong.
So,
- Have I adequately understood the above definitions of pass-by-reference?
- Are there other definitions around?
- Is there consensus which definition is "the correct one", if so, which?
发布评论
评论(8)
当然,目前不同的人对“引用传递”的含义有不同的定义。这就是为什么他们对于某些东西是否是通过引用传递存在分歧的原因。
但是,无论您使用什么定义,都必须在不同语言中一致使用它。你不能说一种语言具有按值传递,并且在另一种语言中具有完全相同的语义并说它是按引用传递。指出语言之间的类比是解决这一争议的最佳方式,因为尽管人们可能对特定语言的传递模式有强烈的看法,但当你将相同的语义与其他语言进行对比时,有时会带来反直觉的结果,迫使他们重新思考他们的定义。
如果你同意这一观点,那么你还必须考虑大多数语言,包括 Python、Ruby、OCaml、Scheme、Smalltalk、SML、Go、JavaScript、Objective-C 等多种语言。 仅按值传递。如果您觉得这些内容很奇怪或违反直觉,我要求您指出为什么您认为这些语言中的对象的语义与 Java 中的对象的语义不同。 (我知道其中一些语言可能明确声称它们是按引用传递的;但它们所说的无关紧要;必须根据实际行为将一致的定义应用于所有语言。)
以 Java 为例:
在 C 中,它相当于:
我声称上面的内容在语义上是等效的;只是语法不同。唯一的语法差异是:
*
来表示指针类型; Java 的引用(指向对象的指针)类型不需要显式的*
。->
通过指针访问字段; Java 只是使用.
new
为堆上的新对象动态分配内存; C使用malloc
来分配它,然后我们需要初始化内存。请注意,重要的是,
func(something)
,无需执行任何诸如获取地址之类的操作。object = null;
不会影响调用范围。因此,两种情况下的语义是相同的,因此如果您调用 Java 传递引用,则也必须调用 C 传递引用。
Sure, different people currently have different definitions of what "pass-by-reference" means. And that is why they disagree on whether something is pass-by-reference or not.
However, whatever definition you use, you must use it consistently across languages. You can't say that one language has pass-by-value, and have the exact same semantics in another language and say that it is pass-by-reference. Pointing out the analogies between languages is the best way to address this dispute, because although people might have strong opinions about the passing modes in particular languages, when you contrast the identical semantics with other languages, it sometimes brings counter-intuitive results that force them to re-think their definition.
If one agrees with this viewpoint, then one must also consider most languages, including as diverse ones as Python, Ruby, OCaml, Scheme, Smalltalk, SML, Go, JavaScript, Objective-C, etc. as pass-by-value only. If any of this strikes you as strange or counterintuitive, I challenge you to point out why you think it is different between the semantics of objects in any of those languages from objects in Java. (I know that the some of these languages may explicitly claim that they are pass-by-reference; but it is irrelevant what they say; a consistent definition must be applied to all languages based on the actual behavior.)
Take your Java example:
in C, it would be equivalent to this:
I claim that the above are semantically equivalent; only the syntax is different. The only syntax differences are:
*
to denote a pointer type; Java's reference (pointers to objects) types don't need an explicit*
.->
to access a field through a pointer; Java just uses.
new
to dynamically allocate memory for a new object on the heap; C usesmalloc
to allocate it, and then we need to initialize the memory.Note that, importantly,
func(something)
, without needing to do anything like taking address or anything.object = null;
inside the function does not affect the calling scope.So the semantics are the same in both cases, so if you call Java pass-by-reference you must call C pass-by-reference too.
谁有权力决定?没有人,也没有人。你自己决定;作家决定他或她的书;读者决定是否同意作者的观点。
要理解这个术语,需要深入了解该语言的底层(用 C 代码来解释它们却没有抓住重点)。参数传递样式是指编译器通常用来创建某些行为的机制。通常定义如下:
(术语说明:参数是子例程中定义的变量,参数是调用中使用的表达式。)
教科书通常也定义 pass按名称来说,但这种情况很少见,而且不容易在这里解释。路过的需求也存在。
参数传递风格的重要性在于它的效果:在按值传递时,对参数所做的任何更改都不会传达给实参;在按结果传递中,对参数所做的任何更改都会传达到最后的参数;在按引用传递中,对参数所做的任何更改都会在进行时传达给参数。
有些语言定义了不止一种传递样式,允许程序员分别为每个参数选择他们喜欢的样式。例如,在 Pascal 中,默认样式是按值传递,但程序员可以使用
var
关键字指定按引用传递。其他一些语言指定了一种传球风格。还有一些语言为不同的类型指定不同的样式(例如,在 C 中,默认情况下按值传递,但数组是按引用传递)。现在,在 Java 中,从技术上讲,我们有一种按值传递的语言,对象变量的值是对该对象的引用。这是否使 Java 在涉及对象变量时采用引用传递是一个品味问题。
Who has the authority to decide? Nobody, and everybody. You decide for yourself; a writer decides for his or her book; and a reader decides whether to agree with the writer.
To understand the term, one needs to go under the hood of the language (and explaining them in terms of C code rather misses the point). Parameter passing styles refer to mechanisms that compilers typically use to create certain behaviour. The following are usually defined:
(A note of terminology: a parameter is the variable defined in the subroutine, an argument is the expression that is used in a call.)
Textbooks usually also define pass by name, but it's rare and not easy to explain here. Pass by need also exists.
The importance of the parameter passing style is its effect: in pass by value, any changes made to the parameter is not communicated to the argument; in pass by result, any changes made to the parameter are communicated to the argument at the end; in pass by reference, any changes made to the parameter are communicated to the argument as they are made.
Some languages define more than one passing style, allowing the programmer to select their preferred style for each parameter separately. For example, in Pascal, the default style is pass by value, but a programmer can use the
var
keyword to specify pass by reference. Some other languages specify one passing style. There are also languages that specify different styles for different types (for example, in C, pass by value is the default but arrays are passed by reference).Now, in Java, technically we have a language with pass-by-value, with the value of an object variable being a reference to the object. Whether that makes Java pass-by-reference where object variables are concerned is a matter of taste.
您的两个 C 示例实际上都演示了按值传递,因为 C 没有按引用传递。只是您传递的值是一个指针。引用传递发生在 Perl 等语言中:
这里,变量
$a
实际上是通过引用传递的,因此子例程可以修改它。它不会修改$a
通过间接指向的某个对象;相反,它修改$a
本身。在这方面,Java 与 C 类似,只不过 Java 中的对象是“引用类型”,因此您所拥有的(以及您可以传递的)都是指向它们的指针。像这样的事情:
实际上不会改变
a
;它只重新分配i
。Both of your C examples actually demonstrate pass-by-value, because C doesn't have pass-by-reference. It's just that the value that you're passing is a pointer. Pass-by-reference occurs in languages such as Perl:
Here, the variable
$a
is actually passed by reference, so the subroutine can modify it. It's not modifying some object that$a
is pointing to via indirection; rather, it modifies$a
itself.Java is like C in this respect, except that in Java objects are "reference types", so all you ever have (and all you can ever pass) are pointers to them. Something like this:
won't actually change
a
; it only reassignsi
.实际上,通过引用传递是传递对值的引用(而不是它的副本)作为参数。
大多数语言,当您将变量作为参数传递时,默认情况下都会创建变量值的副本并传递该副本。被调用者将其参数名称绑定到该副本。这称为“按值传递”(或更清楚地说,“按副本传递”)。调用两侧的两个变量最终具有不同的存储位置,因此是完全不同的变量(仅相关,因为它们通常以相同的值开始)。
另一方面,通过引用传递并不进行复制。相反,它传递变量本身(减去名称)。也就是说,它传递对与变量别名完全相同的值的引用。 (这通常是通过隐式传递指向变量存储的指针来完成的,但这只是一个实现细节;调用者和被调用者不必知道或关心它是如何发生的。)被调用者将其参数的名称绑定到该位置。最终结果是双方使用相同的存储位置(只是名称可能不同)。因此,被调用者对其变量所做的任何更改也会对调用者的变量进行。例如,在面向对象语言的情况下,可以为变量分配完全不同的值。
大多数语言(包括 Java)本身并不支持此功能。哦,他们喜欢说他们这样做......但那是因为那些从未能够真正通过引用传递的人,通常不会理解做与做之间的微妙区别so 并按值传递引用。这些语言中令人困惑的地方在于引用类型变量。 Java 本身从不直接处理引用类型对象,而是处理对这些对象的引用。区别在于“包含”所述对象的变量。引用类型变量的值就是这样的引用(或者,有时,是一个表示“无”的特殊引用值)。当Java传递这样的引用时,虽然它不复制对象,但它仍然复制值(即:函数获取的引用是变量引用的值的副本)。也就是说,它传递引用,但按值传递。这允许通过引用传递允许的大多数事情,但不是全部。
我能想到的真正通过引用支持的最明显的测试是“交换测试”。原生支持按引用传递的语言必须提供足够的支持来编写交换其参数值的函数
swap
。与此等效的代码:(显然,没有可变变量的语言无法通过这种方式进行测试——但这没关系,因为它们并不重要。两者之间最大的语义差异在于调用者的可修改程度变量是由被调用者调用的。当变量的值在任何情况下都不可修改时,差异就只是实现细节或优化。)
注意,这个答案中的大部分讨论都是关于“变量”的。许多语言(例如 C++)也允许通过引用传递匿名值。机制是一样的;该值占用存储空间,而引用是它的别名。它只是不一定在调用者中具有名称。
Passing by reference is, in effect, passing a reference to a value -- rather than a copy of it -- as an argument.
Most languages, when you pass a variable as an argument, will by default create a copy of the variable's value and pass the copy. The callee binds its name for the parameter to that copy. This is called "passing by value" (or, more clearly, "passing by copy"). The two variables on either side of the call end up with different storage locations, and are thus completely different variables (only related in that they typically start out with equal values).
Passing by reference, on the other hand, doesn't do the copy. Instead, it passes the variable itself (minus the name). That is, it passes a reference to the very same value the variable aliases. (This is typically done by implicitly passing a pointer to the variable's storage, but that's just an implementation detail; the caller and callee don't have to know or care how it happens.) The callee binds its parameter's name to that location. The end result is that both sides use the same storage location (just by possibly different names). Any changes the callee makes to its variable are thus also made to the caller's variable. For example, in the case of object-oriented languages, the variable can be assigned a whole different value.
Most languages (including Java) do not support this natively. Oh, they like to say they do...but that's because people who have never been able to truly pass by reference, often don't grok the subtle difference between doing so and passing a reference by value. Where the confusion comes in with those languages, is with reference-type variables. Java itself never works directly with reference-type objects, but with references to those objects. The difference is in the variables "containing" said objects. The value of a reference-type variable is such a reference (or, sometimes, a special reference value that means "nothing"). When Java passes such a reference, while it doesn't copy the object, it still copies the value (ie: the reference the function gets is a copy of the value the variable refers to). That is, it is passing a reference, but is passing it by value. This allows most of the things that passing by reference allows, but not all.
The most obvious test i can think of for real pass-by-reference support, would be the "swap test". A language that natively supports passing by reference must provide enough support to write a function
swap
that swaps the values of its arguments. Code equivalent to this:(Obviously, languages that don't have mutable variables can't be tested this way -- but that's fine, because they don't matter. The big semantic difference between the two is how modifiable the caller's variable is by the callee. When the variable's value is not modifiable in any case, the difference becomes merely an implementation detail or optimization.)
Note, most of the talk in this answer is about "variables". A number of languages, like C++, also allow passing anonymous values by reference. The mechanism is the same; the value takes up storage, and the reference is an alias to it. It just doesn't necessarily have a name in the caller.
维基百科给出了引用调用的非常明确的定义,我无法改进:
请注意,您的两个示例都不是按引用调用,因为在 C 中分配形式参数永远不会修改调用者所看到的参数。
但这已经足够复制粘贴了,请阅读
http://en.wikipedia 上的全面讨论(带有示例) .org/wiki/Evaluation_strategy#Call_by_reference
Wikipedia gives a very clear definition of call-by-reference I can not improve upon:
Note that neither of your examples is call-by-reference, because assigning a formal parameter in C never modifies the argument as seen by the caller.
But that's enough copy-pasting, read the thorough discussion (with examples) at
http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference
Java 不通过引用传递。您总是传递副本/按值。但是,如果您传递一个对象,那么您将获得引用的副本。因此,您可以直接编辑对象,但是如果您覆盖本地引用,则原始对象引用将不会被覆盖。
Java doesn't pass by reference. You are always passing a copy/by value. However if you pass an object then you will get a copy of the reference. So you can directly edit the object, however if you overwrite your local reference then the original object reference won't be overriden.
通过引用传递参数意味着参数的指针嵌套比局部变量的指针嵌套更深。如果您有一个具有类类型的变量,则该变量是指向实际值的指针。原始类型的变量包含值本身。
现在,如果按值传递这些变量,则可以保持指针嵌套:对象引用保留指向对象的指针,而基元变量保留值本身。
将变量作为引用传递意味着指针嵌套会更深:将指针传递给对象引用,以便可以更改对象引用;或者传递一个指向原语的指针,以便可以更改其值。
这些定义在 C# 和 Object Pascal 中使用,它们都具有通过引用传递变量的关键字。
回答你的问题:因为最后一个变量 - 第一个示例中的
whatever
和第二个示例中的thing_pointer
- 通过指针传递给函数(& ;
),两者都是通过引用传递的。Passing parameters by reference means that the pointer nesting of parameters is deeper than the pointer nesting of local variables. If you have a variable with the type of a class, the variable is a pointer to the actual value. A variable of a primitive type is contains the value itself.
Now, if you pass these variables by value, you keep the pointer nesting: The object reference stays a pointer to the object, and the primitive variable stays the value itself.
Passing the variables as references means that the pointer nesting gets deeper: You pass a pointer to the object reference, so that you can change the object reference; or you pass a pointer to the primitive, so that you can change its value.
These definitions are used in C# and Object Pascal which both have keywords to pass a variable by reference.
To answer your question: Because the last variables -
whatever
in the first example andthing_pointer
in the second one - are passed to the function each through a pointer (&
), both are passed by reference.如果您熟悉 C,也许下面的类比可以解释 Java 的工作原理。这仅适用于类类型(而不是基本类型)的对象。
在 Java 中,我们可以拥有一个变量并将其传递给函数:
在 C 中,这将如下所示:
在 Java 中,类类型的变量始终是引用,这在 C 中是最忠实的映射到指针。但在函数调用中,指针本身是通过复制传递的。 访问如 #1j 和 #1c 中的指针会修改原始变量,因此从这个意义上说,您正在传递对该变量的引用。然而,变量本身只是一个指针,它本身是通过复制传递的。所以当你给它分配其他东西时。与 #2j 和 #2c 一样,您只是在
f
的本地范围内重新绑定引用/指针的副本。相应示例中的原始变量a
或w
保持不变。简而言之:一切都是引用,引用是按值传递的。
另一方面,在 C 中,我可以通过声明
void v(struct Foo ** r);
并调用f(&w)
来实现真正的“按引用传递”代码>;这将允许我从f
内部更改w
本身。注 1:对于像
int
这样的基本类型来说,情况并非如此,它们完全按值传递。注 2:C++ 示例会更整洁一些,因为我可以通过引用传递指针(而且我不必说
struct
):void f(Foo * & r) { r = 新 Foo; }
和f(w);
。If you are familiar with C, perhaps the following analogy explains how Java works. This will be true only for objects of class-type (and not fundamental type).
In Java, we can have a variable and pass it to a function:
In C, this would look as follows:
In Java, variables of class-type are always references, which in C would be most faithfully mapped to pointers. But in function calls, the pointer itself is passed by copy. Accessing the pointer as in #1j and #1c modifies the original variable, so in that sense you are passing around a reference to the variable. However, the variable itself is only a pointer, and it itself is passed by copy. So when you assign something else to it. as in #2j and #2c, you are only rebinding the copy of the reference/pointer in the local scope of
f
. The original variable,a
orw
in the respective examples, remains untouched.In short: Everything is a reference, and references are passed by value.
In C, on the other hand, I could implement a true "passing by reference" by declaring
void v(struct Foo ** r);
and callingf(&w)
; this would allow me to changew
itself from withinf
.Note 1: this is not true for fundamental types like
int
, which are wholly passed by value.Note 2: A C++ example would be a bit tidier since I could pass the pointer by reference (and I didn't have to say
struct
):void f(Foo * & r) { r = new Foo; }
andf(w);
.