指针和参考文献之间的真正区别是什么?

时间:2008-09-18 12:19:54

标签: c pointers reference

AKA - 对指针的这种迷恋是什么?

只使用了ActionScript,Java和C#这样的现代面向对象语言,我并不真正理解指针的重要性以及它们的用途。我在这里错过了什么?

22 个答案:

答案 0 :(得分:79)

这一切都只是间接:不处理数据的能力,但说“我会指引你到那里的一些数据”。您在Java和C#中具有相同的概念,但仅以参考格式。

关键的区别在于引用是有效的不可变路标 - 它们总是指向某些东西。这很有用,也很容易理解,但不如C指针模型灵活。 C指针是你可以愉快地重写的路标。你知道你要找的字符串是指向字符串的隔壁吗?好吧,只是略微改变路标。

这与C的“接近骨头,需要低水平的知识”方法很吻合。我们知道 char* foo由一组字符组成,这些字符从foo路标指向的位置开始。如果我们也知道字符串长度至少为10个字符,我们可以将路标更改为(foo + 5)以指向相同的字符串,但是开始一半的长度。

当您知道自己在做什么时,这种灵活性非常有用,如果您不知道,那么这种灵活性很有用(“知道”不仅仅是“了解语言”,而是“知道程序的确切状态”)。弄错了,你的路标指引你离开悬崖的边缘。参考文献不会让你小提琴,所以你更有信心可以毫无风险地跟随它们(特别是在与“引用的对象永远不会消失”等规则相结合时,就像在大多数垃圾收集的语言中一样)。

答案 1 :(得分:28)

你错过了很多!了解计算机如何在较低级别上工作在某些情况下非常有用。 C和汇编程序会为你做这件事。

基本上,一个指针可以让你把东西写到计算机内存中的任何一点。在更原始的硬件/操作系统或嵌入式系统中,这实际上可能会有用。说再次打开和关闭blinkenlichts。

当然,这不适用于现代系统。操作系统是主存和主存。如果你试图访问错误的内存位置,那么你的过程将为它的生命付出代价。

在C中,指针是传递对数据的引用的方式。调用函数时,您不希望将百万位复制到堆栈。相反,您只需知道数据驻留在主存储器中的位置。换句话说,您为数据提供指针

在某种程度上,即使使用Java也会发生这种情况。您将引用传递给对象,而不是对象本身。请记住,最终每个对象都是计算机主存储器中的一组位。

答案 2 :(得分:14)

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

您是否认为这是一件好事,这取决于您,但它是在C或汇编程序中完成任何事情的基础。

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

[编辑:这是对'这个对指针的迷恋是什么的问题的答案'。我所比较的只是带有Java引用的汇编程序/ C风格的指针。问题标题后来发生了变化:如果我开始回答我可能提到的除Java之外的其他语言的引用的新问题]

答案 3 :(得分:12)

这就像问,“对CPU指令的这种痴迷是什么?我是否因为没有在各地洒x86 MOV指令而错过了什么?“

在低级编程时,你只需要需要指针。在大多数高级编程语言实现中,指针的使用方式与C中的一样广泛,但编译器对用户隐藏。

所以......别担心。你已经在使用指针了 - 而且没有做错的危险。 :)

答案 4 :(得分:10)

我认为指针是汽车中的手动变速器。如果你学会驾驶带有自动变速器的汽车,那就不会让驾驶员感觉不好。而且你仍然可以完成手动变速器上学到的驾驶员可以做的大部分工作。你的驾驶知识会有一个漏洞。如果你不得不开一本手册,你可能会遇到麻烦。当然,很容易理解它的基本概念,但是一旦你必须做一个小山开始,你就搞砸了。但是,仍然存在手动变速器的地方。例如,赛车驾驶员需要能够转移以使车辆以最佳方式响应当前比赛条件。手动变速器对他们的成功非常重要。

这与现在的编程非常相似。某些软件需要进行C / C ++开发。一些例子是高端3D游戏,低级嵌入式软件,速度是软件目的的关键部分,低级语言允许您更密切地访问需要处理的实际数据,这是该性能的关键。然而,对于大多数程序员而言,情况并非如此,并且不知道指针不会瘫痪。但是,我相信每个人都可以从学习C和指针以及手动传输中受益。

答案 5 :(得分:5)

由于您使用面向对象语言进行编程,所以让我这样说吧。

您获得对象A实例化对象B,并将其作为方法参数传递给对象C.对象C修改对象B中的某些值。当您返回到对象A的代码时,您可以看到更改后的值对象B.为什么会这样?

因为您将对象B的引用传递给了对象C,所以没有创建对象B的另一个副本。因此对象A和对象C都保存对内存中相同对象B的引用。从一个地方改变而在另一个地方看到。这称为参考。

现在,如果您使用原始类型(如int或float)并将它们作为方法参数传递,则对象A无法看到对象C中的更改,因为对象A仅传递副本而是它自己的变量副本的引用。这称为按值。

你可能已经知道了。

回到C语言,函数A向函数B传递一些变量。这些函数参数是本机副本,按值。为了使函数B能够操作属于函数A的副本,函数A必须将指针传递给变量,以使其成为传递参考。

“嘿,这是我的整数变量的内存地址。把新值放在那个地址位置,我会稍后再拿。”

注意这个概念是相似的,但不是100%类似的。指针可以做的不仅仅是通过“引用”。指针允许函数将任意位置的内存操作到所需的任何值。指针还用于指向执行代码的新地址,以动态执行任意逻辑,而不仅仅是数据变量。指针甚至可以指向其他指针(双指针)。这很强大,但也很容易引入难以检测的错误和安全漏洞。

答案 6 :(得分:4)

从历史上看,使编程成为可能的是实现内存位置可以保存计算机指令,而不仅仅是数据。

指针源于认识到内存位置也可以保存其他内存位置的地址,从而为我们提供了间接性。没有指针(在低级别),大多数复杂的数据结构是不可能的。没有链表,二进制树或哈希表。没有通过引用传递,只能通过值传递。由于指针可以指向代码,没有它们,我们也没有虚函数或函数查找表。

答案 7 :(得分:4)

如果你之前没有看过指针,你肯定错过了这个迷你宝石:

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

答案 8 :(得分:4)

我在日常工作中大量使用指针和引用...在托管代码(C#,Java)和非托管(C ++,C)中。我学会了如何处理指针以及主人自己的指示...... [Binky !!] [1]没有其他需要说的;)

指针和引用之间的区别就是这个。指针是某个内存块的地址。它可以被重写,换句话说,重新分配给其他一些内存块。引用只是重命名某个对象。它只能分配一次!一旦将其分配给对象,就无法将其分配给另一个对象。引用不是地址,它是变量的另一个名称。有关详细信息,请查看C++ FAQ

Link1

LInk2

答案 9 :(得分:2)

我目前正在设计一些高级企业软件,其中一个或多个其他实体引用了大量数据(在这种情况下存储在SQL数据库中)。如果没有更多实体引用它时仍然存在大量数据,我们就会浪费存储空间。如果参考指出了那些不存在的数据,那也是一个大问题。

我们的问题和使用指针的语言中的内存管理之间存在着强烈的类比。能够与这些类比的同事交谈是非常有用的。不删除未引用的数据是“内存泄漏”。一个无处可去的引用是一个“悬空指针”。我们可以选择显式的“frees”,或者我们可以使用“引用计数”来实现“垃圾收集”。

所以在这里,理解低级内存管理有助于设计高级应用程序。

在Java中,你一直在使用指针。大多数变量都是指向对象的指针 - 这就是原因:

StringBuffer x = new StringBuffer("Hello");
StringBuffer y = x;
x.append(" boys");
System.out.println(y);

...打印“Hello boys”而非“Hello”。

C的唯一区别在于添加和减去指针是很常见的 - 如果你弄错了逻辑,你最终可能会搞乱你不应该触及的数据。

答案 10 :(得分:2)

C ++中的引用与Java或.NET语言中的引用根本不同; .NET语言具有称为“byrefs”的特殊类型,其行为与C ++“引用”非常相似。

C ++引用或.NET byref(我将使用后一术语来区分.NET引用)是一种特殊类型,它不包含变量,而是保存足以识别变量的信息(或者某些东西)在其他地方可以表现为一个,例如数组插槽。 Byref通常仅用作函数参数/参数,并且旨在是短暂的。将byref传递给函数的代码保证了由此识别的变量至少在该函数返回之前存在,并且函数通常保证在返回之后不保留byref的任何副本(注意在C ++中后者的限制不是执行)。因此,byrefs不能超过由此识别的变量。

在Java和.NET语言中,引用是一种标识堆对象的类型;每个堆对象都有一个关联的类,堆对象的类中的代码可以访问存储在对象中的数据。堆对象可以授予外部代码对存储在其中的数据的有限或完全访问权限,和/或允许外部代码调用其类中的某些方法。使用对类的方法的引用将导致该引用可用于该方法,然后该方法可以使用它来访问堆对象中的数据(甚至是私有数据)。

在Java和.NET语言中引用特殊的是,它们作为绝对不变量维护,只要该引用存在,每个非空引用将继续标识相同的堆对象。一旦在Universe中的任何地方都没有对堆对象的引用,堆对象就会停止存在,但是当对象的任何引用存在时,堆对象就不会停止存在,也没有任何方法可以“正常” “引用堆对象以自发地成为除对该对象的引用之外的任何东西。 Java和.NET都有特殊的“弱引用”类型,但即使它们支持不变量。如果Universe中的任何位置都不存在对对象的非弱引用,则任何现有的弱引用都将失效;一旦发生这种情况,就不会有任何对该对象的引用,因此它可能会失效。

指针,如C ++引用和Java / .NET引用,标识对象,但与上述类型的引用不同,它们可以比它们识别的对象更长。如果指针识别的对象不再存在但指针本身不存在,则任何使用指针的尝试都将导致未定义的行为。如果一个指针不知道是null还是识别一个当前存在的对象,那么没有标准定义的方法来用该指针做任何而不是用其它东西覆盖它。如果没有任何东西使用指针,那么指针在被识别的对象停止之后继续存在是完全合法的,但指针外的东西必须指示它是否可以安全使用,因为没有办法问指针本身。

指针和引用(两种类型)之间的关键区别在于,始终可以询问引用是否有效(它们是有效的还是可识别为null),如果被认为是有效的,它们将保持为有效只要他们存在。指针不能被问及它们是否有效,并且系统不会做任何事情来确保指针不会变得无效,也不会允许无效的指针被识别出来。

答案 11 :(得分:2)

老实说,如果你不知道指针,大多数经验丰富的开发人员都会笑(希望友好)。 在我之前的工作中,我们去年有两位新员工(刚刚毕业),他们不知道指针,仅此一周就是与他们交谈的主题。没有人能够相信如果有人能在不知道指针的情况下毕业......

答案 12 :(得分:2)

字符串是C(和其他相关语言)的基础。用C语言编程时,必须管理你的记忆。你不只是说“好吧,我需要一堆字符串”;你需要考虑数据结构。你需要多少记忆?你什么时候分配它?你什么时候开始免费?假设你想要10个字符串,每个字符串不超过80个字符。

好的,每个字符串都是一个字符数组(81个字符 - 你一定不要忘记null或者你会后悔!)然后每个字符串本身就是一个数组。最终结果将是一个多维数组,如

char dict[10][81];

请注意,顺便提一下,dict不是“字符串”或“数组”或“字符”。这是一个指针。当您尝试打印其中一个字符串时,您所做的只是传递单个字符的地址; C假设如果它只是开始打印字符,它最终会达到空值。并且它假设如果你在一个字符串的开头,并且你向前跳转81个字节,那么你将在下一个字符串的开头。而且,实际上取指针并向其添加81个字节是唯一可能的方式跳转到下一个字符串。

那么,为什么指针很重要?因为没有它们你就无能为力。你甚至不能做一些简单的事情,比如打印出一堆字符串;你当然不能做任何有趣的事情,如实现链接列表,哈希,队列,树,文件系统,或某些内存管理代码,或内核或......等等。你需要理解它们,因为C只是给你一块内存,让你做其余的事情,用一块原始内存做任何事都需要指针。

此外,许多人认为理解指针的能力与编程技能高度相关。乔尔提出了这个论点,其中包括。例如

  

现在,我承认在今天编写的90%的代码中不需要使用指针进行编程,事实上,它在生产代码中是非常危险的。好。没关系。函数式编程在实践中并没有太多用处。同意。

     

但对于一些最激动人心的编程工作来说,它仍然很重要。例如,如果没有指针,您将永远无法使用Linux内核。在没有真正理解指针的情况下,您无法理解Linux或任何操作系统中的一行代码。

来自here。优秀的文章。

答案 13 :(得分:1)

很长一段时间我都不懂指针,但我理解了数组寻址。所以我通常会为数组中的对象组合一些存储区域,然后使用该数组的索引作为“指针”概念。

SomeObject store[100];
int a_ptr = 20;
SomeObject A = store[a_ptr];

这种方法的一个问题是,在我修改'A'之后,我必须将它重新分配给'store'数组,以便更改是永久性的:

store[a_ptr] = A;

在幕后,编程语言正在进行多次复制操作。大多数情况下,这并不影响性能。它主要使代码容易出错且重复。

在我学会理解指针后,我放弃了实现数组寻址方法。这个比喻仍然非常有效。只要考虑'store'数组是由编程语言的运行时管理的。

SomeObject A;
SomeObject* a_ptr = &A;
// Any changes to a_ptr's contents hereafter will affect
// the one-true-object that it addresses. No need to reassign.

如今,当我无法合法地复制对象时,我只使用指针。出现这种情况的原因有很多:

  1. 避免昂贵的对象复制 操作为了 性能
  2. 其他一些因素不允许 对象复制操作。
  3. 你想要一个函数调用 对象的副作用(不要 传递对象,传递指针 于此)。
  4. 在某些语言中 - 如果你愿意的话 从a返回多个值 功能(虽然一般 避免的)。

答案 14 :(得分:0)

使用C和C ++等语言进行编程时,您更接近“金属”。指针包含存储位置,您可以在其中存储变量,数据,函数等。您可以传递指针而不是传递值(复制变量和数据)。

指针有两件事情很困难:

  1. 指针,寻址等指针可能会变得非常神秘。它会导致错误,而且很难阅读。
  2. 指针指向的内存通常是从堆中分配的,这意味着您负责释放该内存。您的应用程序越大,就越难以满足此要求,并最终导致难以追踪的内存泄漏。
  3. 您可以将指针行为与Java对象的传递方式进行比较,但在Java中您不必担心释放内存,因为这是由垃圾收集处理的。通过这种方式,您可以获得有关指针的好处,但不必处理负面因素。如果你没有取消引用你的对象,你仍然可以在Java中获得内存泄漏,但这是另一回事。

答案 15 :(得分:0)

参数效率 - 传递指针(Int - 4个字节)而不是复制整个(任意大的)对象。

Java类通过引用(基本上是一个指针)也通过btw传递,它只是在程序员隐藏的java中。

答案 16 :(得分:0)

如果你想在运行时生成“对象”而不预先在堆栈上分配内存,那么你需要它们

答案 17 :(得分:0)

另外需要注意的是,您可以通过将代码块标记为不安全来使用C#中的指针(与普通引用相对)。然后你可以直接改变内存地址并做指针算术和所有有趣的东西。它非常适合非常快速的图像处理(我个人使用它的唯一地方)。

据我所知,Java和ActionScript不支持不安全的代码和指针。

答案 18 :(得分:0)

指针很重要!它们“指向”内存地址,许多内部结构表示为指针,IE,字符串数组实际上是一个指针列表!指针也可用于更新传递给函数的变量。

答案 19 :(得分:0)

指针是在低级编程语言中表示间接的最务实的方式。

答案 20 :(得分:0)

我总是对高级语言中的指针或引用这样的东西感到苦恼。在对象(甚至只是函数)的行为方面考虑更高层次的抽象是非常有用的,而不是用“让我看看,如果我将这个东西的地址发送到那里,然后那个东西”的思考。会给我一个指向其他东西的指针“

考虑一个简单的交换功能。如果你有

void swap(int& a,int& b)

程序交换(var a,b:整数)

然后解释这些意味着可以更改值。通过传递变量的地址来实现这一事实只会分散目的。

与对象相同---不要将对象标识符视为指针或对“stuff”的引用。相反,只需将它们视为OBJECTS即可向其发送消息。即使在像C ++这样的原始语言中,通过尽可能高的思考(和写作),你可以更快地进行更多的工作。

答案 21 :(得分:-3)

写出超过2行的c或c ++,你会发现。

它们是变量内存位置的“指针”。这就像通过引用传递变量一样。