当前位置:首页 > 新闻中心 > java的引用明明和指针没什么本质区别,java为什么还宣称没有指针并把这个当作语言的优点?

java的引用明明和指针没什么本质区别,java为什么还宣称没有指针并把这个当作语言的优点?

来源:云智库平台     时间:2021-05-12 15:37:57



指针是抽象意义上的引用的一种,指针看起来和一般的引用那么不一样,主要是因为他的语法不一样,解引用的时候要用一个运算符,其实就是一种引用。new malloc 出来的非数组对象返回的指针,还有目的是让函数能够修改指向对象而传递给函数的指针(容易引起误会的原文:作为函数参数让函数修改指向对象的指针),他们的作用就像一般的引用。只不过C/C++里还给了它指针运算的能力,让他提供访问数组元素(还有动态分配的数组)、传递数组作为函数参数的效果。

用指针的这些特性可以达到上述的许多目的,一次性省去了实现好几个特性的必要(或者说就像汇编里地址带上类型的简单包装)。数组访问可以用指针算术(汇编里就变成整数加减法或者某些指令里寻址直接可以完成)完成。甚至某些奇人会用指针算术和强制转换配合在一起做更多的事情,比如修改整数的前几个字节,用指针算术做到像结构体一样的效果(这里涉及到一些 UB),甚至还有C语言提供的特性(struct 中的0长度的数组)来搞。特别黑一下C语言,这些做法让我感觉自己在使用汇编语言。

那么指针有什么问题?C语言的设计为了运行效率考虑不引入边界检查,不去检查指针会不会超过数组的边界,然后你在这里又遇上了未定义行为(Undefined Behavior)。传入数组作为函数参数的时候,复制一份是不现实的(大部分实现里这样的参数要用栈传递),C语言也没有 Copy on write 这样的 lazy 策略,于是就有按引用传递数组的需求,然后C语言就鼓励传递数组首位元素的指针来代表这个数组(甚至用了函数参数中 T [N] 等同于 T* 的语法糖)。指针作为引用传来传去,传入函数的时候你也没有给编译器足够的信息让他知道你是想传递一个比较大的对象给函数,同时不让他修改,还是要给他一个地方让他返回结果,还是想传递一个数组,你传递的到底是一个动态分配的对象,还是自动(auto)对象(有人喜欢管他们叫堆变量、栈变量)。因此编译器不能在编译的时候搞清楚这个指针应不应该允许他做指针算术(加加减减),或者它能不能被 free/delete。你传递两个指针让他们相减的时候,你也不知道他们是不是在同一个数组里。你不恰当使用指针就会出现一些很麻烦的后果,首先程序本身就不正确,而且指针造成的错误可以不局限在使用指针的地方

更糟糕的是,即使在运行时你也不是很能搞得清楚状况,为了效率,C/C++ 把那些不太正确的指针用法,比如解引用空指针、野指针,free/delete 野指针,把 delete[]delete 搞错,计算指向不同数组元素指针的差之类的,算作未定义行为。原因是为了允许编译器假定这些糟心事情不会发生,让编译器可以放宽心进行一些不考虑这种问题会不会存在的优化。也就是说每次你做了“未定义行为”,程序的表现不需要保持一致——这也让你很难 debug。到时候可能出现段错误,吐核,彩虹小马从天而降请你吃冰淇淋等等表现,也不排除你的电脑当场爆炸(用烂的梗

总之,指针同时承载太多功能,这些功能间的区别也没有反映在编译时候给出的任何信息上(编译器看不懂你的注释也不会去理解你代码的意图),你自己也容易搞糊涂,还有个 UB 的问题在盯着你。

java 有了一些机制可以代替掉 C/C++ 中指针去承载那些功能,也不那么“过分”注重性能,不需要有额外功能的引用,可以避免指针带来的那些麻烦事,所以 java 没有指针也算优点。你要说有什么本质区别的话,就是java的引用更像引用。

PS:指针还有一个问题,所指向的对象的所有权。指针指向对象的所有权不能反映在程序里,只记在程序员脑中、注释里、文档上,编译器不能自己分析出来有没有正确处理这些问题——到底该不该 free/delete 掉?谁来 free/delete ?这涉及到的其实是资源管理问题,java 的解决方案是 GC。
函数传递数组的情况,其实C++的引用和C的指针在这里并没有多大区别,只是语法稍微不同了,开玩笑的话,对应过来我喜欢这样写

int foo(int (*bar)[N]);

要是 N 也能在运行时决定就好了。

我的答案被一个答主点名了,然后答案的排名次序一下掉了——看来被 downvote 了。对于那位答主的答复(不喜欢看的请无视)——

你连c语言指针灵活性有什么好处都没深入了解过就布拉布拉扯指针灵活性不叫优点

我承认我不像大V们水平那么高,然而指针的几种使用动机我还是在回答里列举出来了的。我会仔细参考你的答案考虑C语言的指针有其他什么样的优越性的。

还有那段关于传参时候按值还是按引用,我负责任地告诉你,Java和c都是按值传递,C++是按值传递和按引用传递。这是编译原理里学的。

C是按值传递。传递的是指针的值,而指针是“抽象意义上引用的一种”。我也并没有说C语言可以按引用传递。

你知道Java多态是怎么实现的吗,子类为什么能当作父类类型去操作吗?

实现是实现,语言特性是语言特性,这个问题和这个回答旨在讨论“指针”这个语言特性在语言中的优劣,java 的多态特性的实现要用到指针,和我讨论的问题并没有什么关系。

少年多学习学习,我刚毕业时候都是不敢说话,先学习大神作品的。

我个人认同的观点是,只要觉得有道理,发表观点是不需要讲求资格的。

你倒好,C语言灵魂就被你抛弃了,我就问你,c语言要是为了你所谓的指针的灵活性这个“缺点”去掉指针,他还有啥?

指针有缺点也不意味着一定要去掉——我在谈他的缺点的时候并没有表明态度说指针百害无一利。指针这个特性可以改造,也可以去掉换用其它东西(前提是如果指针真的不好)。即使C语言真的除了指针一无所有,那么淘汰C语言也是一种选择。

那位答主针对我这段的质疑

指针的这种特性引入的目的据说大概也许可能是这样可以减少开销
那么指针有什么问题?最初引入这些特性就是为了效率

大概是他误会了。也有我没仔细考虑的原因。我在讲这段话的时候脑子里想的是别的东西。确实,这也不是减少开销。更准确的说,C语言用指针这样一个特性实现了其他语言里的许多特性的功能。我也对应修改了答案。

考研408专业课之一计算机体系与结构清清楚楚告诉我们,冯诺依曼体系=输入输出,运算器,控制器,存储器。存储器是按地址访问的。指针指向地址。你告诉我指针仅仅为了传参时候减少开销吗

并没有说只有这个作用。以及重申一遍,我们在讨论的是语言特性对使用这门语言的时候带来的优势和劣势,和实现无关(也不考虑实现难度的问题)。真正以 java 编译的二进制码为 native code 的机器也是存在的,你提出实现是想说明什么我不是很明白。你说的这些主要反映的是指针与实现对应,更主要还是这样的体系上指针更容易实现。

难道你是培训机构培训出来的Java农

我对 C++/C 比 java 熟,因为这个我才对 C++/C 的缺点印象更深刻(请脑补缓和气氛的表情)。我是以C++厨的立场说话的(虽然我还不够资格w)。


最后的 java 代码例子,和我的答案虽然无关,但是吐个槽。还是和C语言指针一样的说法,你在这里看到了这种做法(immutable 对象)的弊端,并不能代表这个做法就百害无一利。(虽然个人觉得这个也挺蠢的)
而且C语言里这种奇妙的事情大概只会更多而不会更少。