指针和引用之间的真正区别是什么?
AKA对指针的痴迷是什么?
我只真正使用了现代的,面向对象的语言(如ActionScript,Java和C#),但我并不真正了解指针的重要性以及它们的用途。我在这里错过了什么?
解决方案
指针用于直接操作内存的内容。
是否认为这是一件好事取决于我们自己,但这是在C或者汇编程序中完成任何事情的基础。
高级语言将指针隐藏在幕后:例如,Java引用几乎在我们遇到的所有JVM中都作为指针实现,这就是为什么它被称为NullPointerException而不是NullReferenceException的原因。但是,它不允许程序员直接访问其指向的内存地址,并且不能对其进行修改以采用除正确类型的对象的地址以外的其他值。因此,它所提供的功能(和职责)不如底层语言的指针所提供的那样。
[编辑:这是对"指针的这种迷恋是什么?"这个问题的答案。我比较的只是带有Java引用的汇编程序/ C风格的指针。自那时以来,问题的标题已经改变:我是否打算回答这个新问题,我可能已经提到了Java以外的其他语言的参考文献]
写两行以上的c或者c ++,我们会发现。
它们是变量的内存位置的"指针"。就像通过引用传递变量一样。
指针是在较低级别的编程语言中表示间接的最实用的方式。
指针很重要!它们"指向"内存地址,许多内部结构表示为指针IE,字符串数组实际上是指针的列表!指针也可以用于更新传递给函数的变量。
如果要在运行时生成"对象"而不在堆栈上预先分配内存,则需要它们
只是间接的:不处理数据,而是说"我会引导我们到那里的一些数据"的能力。我们在Java和C#中具有相同的概念,但只是参考格式。
关键区别在于引用实际上是指向对象的不变标志。这是有用的,而且易于理解,但不如C指针模型灵活。 C指针是我们可以愉快地重写的路标。我们知道要查找的字符串在所指向的字符串的隔壁吗?好吧,只要稍微改变一下路标即可。
这与C的"接近骨骼,需要低水平的知识"方法相结合。我们知道," char * foo"由一组字符组成,这些字符从foo路标指向的位置开始。如果我们也知道字符串的长度至少为10个字符,则可以将路标更改为(foo + 5)
来指向相同的字符串,但长度应以一半为起始。
当我们知道自己在做什么以及不知道自己在做什么时,这种灵活性很有用("知道"不只是"了解语言",还在于"知道程序的确切状态")。弄错了,路标指引我们离开悬崖的边缘。引用不会让我们摆弄,因此我们更有信心可以无风险地遵循它们(尤其是在与诸如"被引用的对象永远不会消失"之类的规则结合使用时,就像大多数垃圾收集语言一样)。
这就像在问,对CPU指令的痴迷是什么?我是否由于没有在各处撒满x86 MOV指令而错过了某些东西?
在底层编程时,我们只需要指针。在大多数高级编程语言实现中,指针的使用与C语言一样广泛,但是由编译器对用户隐藏。
所以...别担心我们已经在使用指针-并且也没有这样做不正确的危险。 :)
我们错过了很多!在某些情况下,了解计算机在较低级别上的工作方式非常有用。 C和汇编程序将为我们完成此任务。
基本上,指针使我们可以将内容写入计算机内存中的任何点。在更原始的硬件/ OS或者嵌入式系统上,这实际上可能会做一些有用的事情。说打开和关闭眨眼灯。
当然,这在现代系统上不起作用。操作系统是主内存的主宰者。如果我们尝试访问错误的内存位置,进程将为其生命力付出一切。
在C语言中,指针是将引用传递给数据的方式。调用函数时,我们不想将一百万个位复制到堆栈中。相反,我们只需告诉数据在主内存中的位置即可。换句话说,我们将指向数据。
在某种程度上,即使使用Java也会发生这种情况。我们传递对对象的引用,而不是对象本身。请记住,最终每个对象都是计算机主内存中的一组位。
由于我们一直在使用面向对象的语言进行编程,所以让我这样说。
我们获得对象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%相似。指针不仅可以通过"按引用"传递,还可以做更多的事情。指针允许函数将存储器的任意位置操纵到所需的任何值。指针还用于指向执行代码的新地址,以动态执行任意逻辑,而不仅仅是数据变量。指针甚至可以指向其他指针(双指针)。这很强大,但也很容易引入难以发现的错误和安全漏洞。
传递指针(整数4个字节)的参数效率,而不是复制整个(任意大)对象。
Java类也是通过引用(基本上是指针)通过btw传递的,这只是在Java中对程序员隐藏的。
我将指针看作是汽车中的手动变速器。如果我们学习驾驶具有自动变速箱的汽车驾驶,那不会对驾驶不利。而且,我们仍然可以完成在手动变速箱上学习的驱动程序可以执行的大多数操作。驾驶知识只会有一个漏洞。如果我们必须驾驶手册,可能会遇到麻烦。当然,很容易理解它的基本概念,但是一旦必须重新开始,就会被搞砸了。但是,仍然有手动变速箱的地方。例如,赛车手需要能够换挡,以使赛车以最佳方式响应当前的赛车状况。手动变速器对于他们的成功非常重要。
这与现在的编程非常相似。需要在某些软件上进行C / C ++开发。例如高端3D游戏,低级嵌入式软件,速度是软件目标的关键部分以及低级语言(可让我们更紧密地访问需要处理的实际数据)是该性能的关键。 。但是,对于大多数程序员而言,情况并非如此,并且不知道指针并没有削弱。但是,我确实相信每个人都可以从学习C和指针以及手动传输中受益。
目前,我在设计一些高级企业软件时一头雾水,其中由1个或者多个其他实体引用数据块(在这种情况下,存储在SQL数据库中)。如果没有更多实体引用该数据,则将浪费大量存储空间。如果参考指出了不存在的数据,那也是一个大问题。
在我们的问题和使用指针的语言进行的内存管理之间存在一个很强的类比。能够以此类比与同事交谈非常有用。不删除未引用的数据是"内存泄漏"。无处可寻的引用是"悬空指针"。我们可以选择明确的"免费",也可以使用"引用计数"实现"垃圾回收"。
因此,在这里,了解低级内存管理有助于设计高级应用程序。
在Java中,我们一直在使用指针。大多数变量是指向对象的指针,这就是为什么:
StringBuffer x = new StringBuffer("Hello"); StringBuffer y = x; x.append(" boys"); System.out.println(y);
...打印" Hello boys"而不是" Hello"。
C语言的唯一区别是从指针进行加法和减法是很常见的,如果我们弄错了逻辑,最终可能会弄乱我们不应该接触的数据。
在日常工作中,我经常使用指针和引用……在托管代码(C#,Java)和非托管代码(C ++,C)中。我了解了如何由主人亲自处理指针以及指针是什么... [别扭!] [1]
指针和引用之间的区别是这样。指针是某个内存块的地址。它可以被重写,或者换句话说,可以重新分配给其他内存块。引用只是对某些对象的重命名。它只能分配一次!一旦将其分配给对象,就无法将其分配给另一个对象。引用不是地址,而是变量的另一个名称。有关更多信息,请查看C ++常见问题解答。
链接1
排位2
使用C和C ++之类的语言进行编程,我们将更接近"金属"。指针保存变量,数据,函数等所在的存储位置。我们可以传递指针而不是按值传递(复制变量和数据)。
使用指针有两点困难:
- 指针,寻址等上的指针会变得非常隐蔽。这会导致错误,并且很难阅读。
- 指针指向的内存通常是从堆中分配的,这意味着我们有责任释放该内存。应用程序变得越来越大,难以满足这一要求,最终导致内存泄漏难以追踪。
我们可以将指针行为与Java对象的传递方式进行比较,但在Java中,我们不必担心释放内存,因为这是由垃圾回收处理的。这样,我们可以获得有关指针的好处,而不必处理负面因素。当然,如果不取消引用对象,仍然会在Java中导致内存泄漏,但这是另一回事。
字符串是C(和其他相关语言)的基础。使用C进行编程时,必须管理内存。我们不只是说"好吧,我需要一串琴弦";我们需要考虑数据结构。我们需要多少内存?我们什么时候分配?我们什么时候可以释放它?假设我们想要10个字符串,每个字符串不超过80个字符。
好的,每个字符串都是一个字符数组(81个字符,我们一定不要忘记null,否则会后悔!),然后每个字符串本身就是一个数组。最终结果将是一个多维数组,例如
char dict[10][81];
注意,顺便说一句,该字典不是"字符串","数组"或者" char"。这是一个指针。当我们尝试打印这些字符串之一时,我们要做的只是传递单个字符的地址。 C假设,如果它只是开始打印字符,它将最终命中null。并且假定如果我们位于一个字符串的开头,并且向前跳了81个字节,那么我们将位于下一字符串的开头。而且,实际上,获取指针并向其添加81个字节是跳转到下一个字符串的唯一可能方法。
那么,指针为什么重要?因为没有它们,我们将无能为力。我们甚至无法做一些简单的事情,例如打印出一堆字符串;我们当然不能做任何有趣的事情,例如实现链表,哈希,队列,树,文件系统,内存管理代码,内核或者其他任何东西。我们需要了解它们,因为C只是给我们一块内存,其余的让我们完成,而使用一块原始内存进行任何操作都需要指针。
也有许多人建议理解指针的能力与编程技能高度相关。乔尔已经提出了这一论点。例如
Now, I freely admit that programming with pointers is not needed in 90% of the code written today, and in fact, it's downright dangerous in production code. OK. That's fine. And functional programming is just not used much in practice. Agreed. But it's still important for some of the most exciting programming jobs. Without pointers, for example, you'd never be able to work on the Linux kernel. You can't understand a line of code in Linux, or, indeed, any operating system, without really understanding pointers.
从这里。优秀的文章。
很长一段时间我都不了解指针,但是我了解数组寻址。因此,我通常会为数组中的对象放一些存储区,然后使用该数组的索引作为"指针"概念。
SomeObject store[100]; int a_ptr = 20; SomeObject A = store[a_ptr];
这种方法的一个问题是,在我修改了" A"之后,必须将其重新分配给"存储"数组,以使更改永久生效:
store[a_ptr] = A;
在幕后,该编程语言正在执行多次复制操作。在大多数情况下,这不会影响性能。它主要使代码易于出错且重复。
在学会了理解指针之后,我不再实现数组寻址方法。这个比喻仍然很有效。只需考虑"存储"数组由编程语言的运行时进行管理即可。
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.
如今,我仅在无法合法复制对象时才使用指针。造成这种情况的原因有很多:
- 为避免性能而进行昂贵的对象复制操作。
- 某些其他因素不允许进行对象复制操作。
- 我们希望函数调用对一个对象有副作用(不要传递该对象,而将指针传递给该对象)。
- 在某些语言中-如果我们想从一个函数返回多个值(尽管通常避免这样做)。
如果我们以前从未见过指针,那么我们肯定会错过这个迷你宝石:
void strcpy(char *dest, char *src) { while(*dest++ = *src++); }
同样要注意的是,可以通过将代码块标记为不安全来在C中使用指针(与普通引用相反)。然后,我们可以直接更改内存地址,进行指针算术和所有有趣的工作。它非常适合非常快速的图像处理(我个人唯一使用过的地方)。
据我所知,Java和ActionScript不支持不安全的代码和指针。
从历史上看,使编程成为可能的是,人们意识到内存位置可以保存计算机指令,而不仅仅是数据。
产生这种认识的指针是,存储位置也可以保存其他存储位置的地址,从而为我们提供了间接性。没有指针(处于低水平),最复杂的数据结构将是不可能的。没有链表,二叉树或者哈希表。没有通过引用,仅通过值。由于指针可以指向代码,没有它们,我们也将没有虚拟函数或者函数查找表。
老实说,如果我们不了解指针,大多数经验丰富的开发人员都会笑(希望如此)。
在我以前的工作中,去年有两名新员工(刚刚毕业),他们不了解指针,仅此一个话题就是与他们交谈大约一周。没有人会相信有人会在不知道指针的情况下毕业。
我一直对专注于高级语言中的指针或者引用之类的东西感到沮丧。相对于"让我看看,如果我把这个东西的地址发送到那里,那东西会给我一个指向别的东西的指针"
甚至考虑一个简单的交换函数。如果你有
无效交换(int&a,int&b)
或者
过程Swap(var a,b:整数)
然后将其解释为可以更改的值。通过传递变量的地址来实现这一事实,只是对目的的干扰。
与对象相同-不要将对象标识符视为指向"东西"的指针或者引用。而是将它们视为对象,可以向其发送消息。即使在像C ++这样的原始语言中,也可以通过尽可能高级别的思考(和编写)来更快得多。