“值语义”和“指针语义”是什么意思?

24
什么是“值语义”,什么是“隐式指针语义”?
4个回答

20

Java在对象类型上使用隐式指针语义,在原始数据类型上使用值语义。

值语义意味着您直接处理值并传递副本。重点在于,当您拥有一个值时,可以相信它不会在背后发生变化。

使用指针语义时,您没有一个值,而是一个“地址”。其他人可能会更改其中的内容,而您无法知道。

C ++中的指针语义:

void foo(Bar * b) ...
... b->bar() ...

您需要使用*来请求指针语义,并使用->在指针上调用方法。

Java中的隐式指针语义:

void foo(Bar b) ...
... b.bar() ...

由于您没有使用值语义的选择,因此不需要 *,也不需要 -> 和 . 之间的区别,因此采用了隐式方式。


15

基本上,值语义意味着将一个值分配给另一个值会创建一个副本:

int x = 1;
int y = x;
x = 2; // y remains the same!

特殊情况是函数调用传递了一个参数:

void f(int x) {
    x = 5;
}

int a = 1;
f(a);
// a is still 1

对于Java和C++而言,它们实际上是相同的。然而,Java只知道一些原始类型,其中包括intdoublebooleanchar,以及行为类似的枚举。所有其他类型都使用引用语义,这意味着一个值赋给另一个值时,实际上是重定向指针而不是复制底层的值:

class Foo {
    int x;

    public Foo(int x) { this.x = x; }
}

Foo a = new Foo(42);
Foo b = a; // b and a share the same instance!
a.x = 32;
//b.x is now also changed.

然而,需要注意的是有一些限制条件。例如,许多引用类型(如 StringInteger 等)实际上是不可变的。它们的值无法更改,并且对它们的任何赋值都会覆盖旧值。

此外,参数仍然按值传递。这意味着传递给函数的对象的值可以更改,但其引用不能更改:

void f(Foo foo) {
    foo.x = 42;
}

void g(Foo foo) {
    foo = new Foo(42);
}

Foo a = new Foo(23);
f(a);
// a.x is now 42!

Foo b = new Foo(1);
g(b);
// b remains unchanged!

为什么是特殊情况? - lightning_missile
@morbidCode 是什么特殊情况?传递参数吗?实际上它并不是一个特殊情况,它的行为与赋值完全相同(只是语法上不是赋值)。 - Konrad Rudolph
我不确定我理解了。你的意思是当你将a传递给f(x)时,会创建a的一个副本,然后该副本被赋值为5,因此当方法返回时,该副本消失了,原始a的值保持不变? - lightning_missile
只是为了明确,因为您和其他答案说引用是按值传递的,而在执行g(Foo)时,b实际上是否有两个指针指向它,一个已经指向原始b,另一个在g(foo)内创建?ffoo(和a)也是如此。 - lightning_missile
@morbidCode 我的回答并不与其他回答矛盾。b 没有两个指针指向它,因为它本身就是一个指针(实际上是一个引用)。因此,b 和(在 g 内部)foo 都指向/指向同一个对象。 - Konrad Rudolph
1
我终于搞定了!谢谢。 - lightning_missile

1

Java在变量访问时使用隐式指针语义(您无法直接编辑引用,它会在访问时自动(隐式)解析为对象),并且在方法参数传递上也使用按值传递语义

阅读Java应用程序中的按值传递语义

在Java应用程序中,当一个对象引用是方法的参数时,您传递的是引用的副本(按值传递),而不是引用本身。请注意,调用方法的对象引用和副本都指向同一个对象。这是一个重要的区别。Java应用程序在传递各种类型的参数时不会像C++那样有所不同。Java应用程序通过值传递所有参数,因此无论类型如何,都会复制所有参数。

简而言之:Java中的所有参数都是按值传递的。但这并不意味着对象被复制了(就像PHP4中的默认设置一样),而是引用该对象的引用被复制了。

您将在Java应用程序中的传值语义中看到所有解释和深入示例。


1

4
由于Java的隐藏指针(即使指针本身是按值传递的),所以那个描述是误导性的。 - finnw
2
@finnw:我同意,但是Java开发人员仍然应该以这种方式理解它,因为如果他们希望更改函数内部指向的数据,则会惊讶地发现原始的“引用”外部没有被触及。因此,对于他们来说,理解他们的“引用”被复制是很重要的。 - paercebal
1
Bartosz:很高兴看到有人在传播我的文章 ;)finnw:“隐藏”是关键词...我们正在讨论有关某人如何使用Java进行编程的问题。幕后的实现机制并不改变Java严格按值传递的事实--请阅读Java语言规范以了解其详细说明。 - Scott Stanchfield
@ScottStanchfield:你是否认为这样说可以满足你,即Java虽然在技术上是“按值传递”,但并没有给程序员提供他们真正关心的值?在Java中,唯一的值是原始类型和引用。 - isekaijin
@EduardoLeón - 仅仅因为一个给定的程序员(在这种情况下是你)不关心某些事情并不意味着它并不重要……传引用意味着你可以改变对象标识符(以我的术语,指针)- 换句话说,你可以编写像我在文章中提到的swap()方法一样的东西。在C++和Pascal(等等)中,编译器提供了一个C和Java没有的_额外_参数模式——传引用,它允许人们编写像swap()这样的代码,这对于某些设计非常有用。 - Scott Stanchfield
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接