指针和引用之间的真正区别是什么?

29

AKA - 指针为什么如此受关注?

我只用过像ActionScript,Java和C#这样的现代面向对象语言,我不太理解指针的重要性以及你如何使用它们。我在这方面错过了什么?


我不确定有人会“痴迷”于指针。问题标题是否可以重新措辞,因为问题本身很有价值,但标题只是愚蠢的。也许可以改成“现代‘引用’和传统‘指针’之间的区别是什么?” - Shaun Austin
是的,但我的标题更有趣。我从这些回答中学到了很多,所以我可能会用新的知识来重新表达问题。我应该改变它吗? - Iain
是的,这是一个有趣的标题。但如果是我,我会重新措辞,原因有两个:1)如果问题反映了实际问题的文本,更有可能在谷歌中找到它;2)由于你的措辞方式,类似问题的提问者可能看不到你的问题。仅供参考。 - Shaun Austin
如果你把“指针”当做朋友,那么“引用”也会跟随其后!! - Jay D
22个回答

81

这一切只是间接的:能够不直接处理数据,而是说“我将指引你去那里的某些数据”。在Java和C#中有相同的概念,但只以引用格式存在。

关键区别在于引用实际上是不可变的路标——它们总是指向某些东西。这很有用,也很容易理解,但比C指针模型的灵活性要低。C指针是可以愉快地重写的路标。你知道你要查找的字符串就在被指向的字符串旁边吗?好吧,稍微改变一下路标。

这与C的“贴近底层、需要低级知识”的方法很搭配。我们知道char* foo由从foo标志点开始的一组字符组成。如果我们还知道该字符串至少有10个字符长,我们可以将标志点更改为(foo+5),以指向同一字符串,但从一半处开始。

当你知道自己在做什么时,这种灵活性非常有用,但如果你不知道则会有致命后果(“知道”不仅仅是“知道语言”,还包括“确切了解程序的状态”)。弄错了,你的路标就会引领你走向悬崖边缘。引用不让你乱动,所以你更有信心在没有风险的情况下跟随它们(尤其是与规则相结合时,比如“被引用的对象永远不会消失”,这在大多数垃圾回收语言中都是这样的)。


3
Java和C#中的引用不像C ++中的引用那样是不可变的...它们可以更改为引用其他对象,并且还可以采用null值,这两者在C ++中都无法实现...有点像C ++引用和指针的混合(但又具有垃圾回收功能)。 - workmad3
3
言辞精妙的解释! - Robert Jeppesen
8
喜欢悬崖路牌的例子 :) - SharePoint Newbie
我认为指针直接引用计算机的内存,而引用则是其他变量的别名。如果您有一个int,然后给它两个名称,那基本上就是一个引用。如果你有一个int并记录了它在RAM中的位置-那就是一个指针。 - Max
2
跌下悬崖了 - 说得好! - Jay D
这个解释就像吃了蓝色药丸一样,谢谢。 - LumbusterTick

28

你错过了很多!了解计算机的底层工作原理在许多情况下非常有用。C和汇编语言可以帮助你实现这一点。

基本上,指针让你将数据写入计算机内存中的任何位置。在更原始的硬件/操作系统或嵌入式系统中,这可能会做一些有用的事情。例如打开和关闭blinkenlichts(指灯)。

当然,在现代系统上这并不起作用。操作系统是主内存的主人和大师。如果尝试访问错误的内存位置,则进程将因其傲慢而付出生命的代价。

在C中,指针是传递数据引用的方式。当调用函数时,你不想将一百万个比特位复制到堆栈中。相反,你只需告诉在主内存中数据所在的位置即可。换句话说,你提供了一个指向该数据的指针

在某种程度上,这也是Java中发生的事情。你传递对象的引用,而不是对象本身。请记住,最终每个对象都是计算机主内存中的一组比特位。


我完全同意你的评论:“你错过了很多!” - Jay D

14

指针是用于直接操纵内存内容的。

你可以自行决定这是否是一件好事,但这是C或汇编中完成任何操作的基础。

高级语言将指针隐藏在幕后:例如,在几乎所有JVM中,Java中的引用都被实现为指针,这就是为什么它被称为NullPointerException而不是NullReferenceException的原因。但是,它不允许程序员直接访问它所指向的内存地址,并且不能被修改以取代正确类型对象的地址。因此,它没有提供与低级语言中指针相同的功能(和责任)。

[编辑:这是对问题“这种关注指针的痴迷是什么?”的回答。我所比较的只是汇编/C风格的指针和Java引用。由于问题标题已更改:如果我想回答新问题,我可能会提到除Java以外的其他语言中的引用]


很棒的回答:平等尊重C等低级语言和高级语言! - Jay D

12

这就像是问:“为什么对CPU指令如此痴迷?如果我不在程序中到处使用x86 MOV指令,会错过什么吗?”

只有在进行低级编程时才需要用到指针。在大多数高级编程语言的实现中,指针与在C语言中一样经常被使用,但却被编译器从用户中隐藏。

所以... 不要担心。你已经在使用指针了——而且没有出现使用不当的危险。 :)


10
我将指针视为汽车中的手动变速器。如果您学习的汽车是自动变速器,那并不会让您成为一个糟糕的司机。您依然可以做大多数需要手动变速器技能的事情。只是您在驾驶方面的知识可能存在漏洞。如果你必须开一辆手动挡汽车,可能会有麻烦。当然,理解其基本概念很容易,但一旦你需要做一个坡道起步时,你就会心生畏惧。但是,手动变速器仍然有其用处。例如,赛车手需要切换档位以使赛车在当前比赛条件下以最优方式响应,因此手动变速器对他们的成功非常重要。
这与编程非常相似。现在需要在某些软件上进行C/C ++开发,例如高端3D游戏、低级嵌入式软件等。在这些软件中,速度是软件目的的关键部分,使用较低级别的语言可以更接近需要处理的实际数据,这对性能至关重要。然而,对大多数程序员来说,这不是必需的,不了解指针也不会导致失败。然而,我认为每个人都可以从学习C和指针中受益,就像手动变速器一样。

-1,它除了说明指针有用之外,什么也没有解释。 - Johan

5
自从你开始使用面向对象的语言编程,让我这样说吧。你让对象A实例化对象B,并将其作为方法参数传递给对象C。对象C修改了对象B中的一些值。当你回到对象A的代码时,你可以看到对象B中的更改值。为什么会这样呢?因为你传递了对象B的引用给对象C,而不是复制了另一个对象B。因此,对象A和对象C都持有对内存中同一对象B的引用。从一个地方进行的更改可以在另一个地方看到。这被称为按引用传递。
现在,如果你使用基本类型,比如int或float,并将它们作为方法参数传递,那么对象C中的更改就无法被对象A看到,因为对象A仅仅传递了自己变量的副本,而不是引用。这被称为按值传递。
你可能已经知道这个。
回到C语言,函数A将一些变量传递给函数B。这些函数参数本质上是副本,按值传递。为了使函数B操作属于函数A的副本,函数A必须传递一个指针给变量,这样它就成为按引用传递。
“嘿,这是我的整数变量的内存地址。把新值放在那个地址位置,我等会再来取。”
请注意,这个概念类似但不完全相同。指针可以做比仅仅传递“按引用”更多的事情。指针允许函数操作任意位置的内存到所需的值。指针也用于指向新的执行代码地址,以动态执行任意逻辑,而不仅仅是数据变量。指针甚至可以指向其他指针(双重指针)。这很强大,但也很容易引入难以检测的错误和安全漏洞。

4
我在我的日常工作中经常使用指针和引用...在托管代码(C#,Java)和非托管代码(C ++,C)中。我向大师[Binky!!][1]学习了如何处理指针以及它们是什么。不需要再说什么;)
指针和引用的区别在于:指针是某个内存块的地址。它可以被重写或者换句话说,重新分配到其他内存块。引用只是某个对象的重命名。它只能被分配一次!一旦它被分配给一个对象,就不能再分配给另一个对象。引用不是地址,它是变量的另一个名称。请查看C++ FAQ获取更多信息。 链接1 链接2

Binky 很有趣 - 据我所知,Java 和 C# 不允许直接访问内存,因此您正在使用引用而不是指针? - Iain
在Java和C#中,它通常不是直接的,在大多数情况下都是抽象化的(它们是指针)。虽然在C#中有编写非托管代码块(允许使用指针)以及使用关键字ref(类似于指针)的方法。 - Chap

4

如果您之前没有接触过指针,那么您一定会错过这个小宝石:

void strcpy(char *dest, char *src)
{    
        while(*dest++ = *src++);
}

4
历史上,使编程成为可能的原因是意识到内存位置不仅可以保存数据,还可以保存计算机指令。指针的出现源于意识到内存位置也可以保存其他内存位置的地址,从而给我们带来了间接性。如果没有指针(在低级别),大多数复杂的数据结构都将不可能存在。没有链表、二叉树或哈希表。只有按值传递,没有按引用传递。由于指针可以指向代码,如果没有它们,我们也将没有虚函数或函数查找表。

2

字符串是C语言(以及其他相关语言)的基础。在使用C语言编程时,必须管理自己的内存。你不能只说“好吧,我需要一堆字符串”;你需要考虑数据结构。你需要多少内存?什么时候分配它?什么时候释放它?假设你想要10个字符串,每个字符串不超过80个字符。

好的,每个字符串都是一个字符数组(81个字符 - 你不能忘记空值,否则你会后悔!),然后每个字符串本身又是一个数组。最终结果将是一个类似于多维数组的东西。

char dict[10][81];

请注意,dict不是“字符串”、“数组”或“字符”。它是一个指针。当您尝试打印其中的一个字符串时,您所做的只是传递单个字符的地址;C会假设如果它只是开始打印字符,最终会遇到一个空字符。并且它假设如果您在一个字符串的开头,并向前跳过81个字节,您将到达下一个字符串的开头。实际上,将指针加上81个字节是跳转到下一个字符串的唯一可能方式。
那么,为什么指针很重要?因为没有它们,您无法做任何事情。您甚至不能执行像打印一堆字符串这样简单的操作;您肯定无法执行任何有趣的操作,例如实现链接列表、哈希、队列、树、文件系统、某些内存管理代码、内核等等。您需要理解它们,因为C只会将一块内存交给您进行操作,而对于使用原始内存块进行任何操作都需要使用指针。
此外,许多人认为,理解指针的能力与编程技能高度相关。乔尔(Joel)等人提出了这个论点。例如:
现在,我自由承认,在当今编写的90%的代码中,不需要使用指针,实际上,在生产代码中使用指针是非常危险的。好的,这很好。而且,实际上,函数式编程并没有被广泛使用。同意这一点。但是它仍然对一些最令人兴奋的编程工作非常重要。例如,如果没有指针,你就永远无法在Linux内核上工作。你不能理解Linux或任何操作系统中的一行代码,而不真正了解指针。文章来源:这里。非常好的文章。

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