29

AKA - 对指针的痴迷是什么?

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

4

22 回答 22

80

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

主要区别在于引用实际上是不可变的路标——它们总是指向某些东西。这很有用且易于理解,但不如 C 指针模型灵活。C 指针是您可以愉快地重写的路标。你知道你要找的绳子就在被指向的绳子的隔壁吗?好吧,只是稍微改变一下路标。

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

当您知道自己在做什么时,这种灵活性很有用,如果您不知道则死亡(“知道”不仅仅是“知道语言”,它是“知道程序的确切状态”)。弄错了,你的路标会指引你离开悬崖的边缘。引用不会让您摆弄,因此您更有信心可以毫无风险地遵循它们(尤其是在与“引用的对象永远不会消失”之类的规则相结合时,就像在大多数垃圾收集语言中一样)。

于 2008-09-18T12:26:09.773 回答
28

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

基本上,指针可让您将内容写入计算机内存中的任何点。在更原始的硬件/操作系统或嵌入式系统中,这实际上可能会做一些有用的事情。说再次打开和关闭blinkenlichts。

当然,这不适用于现代系统。操作系统是主存的主宰。如果您尝试访问错误的内存位置,您的进程将以其生命为傲慢付出代价。

在 C 中,指针是传递对数据的引用的方式。调用函数时,您不想将一百万位复制到堆栈中。相反,您只需告诉数据在主存储器中的位置。换句话说,你给了一个指向数据的指针。

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

于 2008-09-18T12:31:40.790 回答
14

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

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

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

[编辑:这是对“对指针的痴迷是什么?”这个问题的答案。我所比较的只是带有 Java 引用的汇编器/C 风格的指针。此后问题标题已更改:如果我开始回答新问题,我可能会提到 Java 以外的其他语言的引用]

于 2008-09-18T12:21:22.860 回答
12

这就像在问,“对 CPU 指令的痴迷是什么?如果没有将 x86 MOV 指令撒在各处,我是否会错过一些东西?”</p>

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

所以……别担心。您已经在使用指针了——而且也没有错误地使用指针的危险。:)

于 2008-09-18T12:31:08.590 回答
10

我将指针视为汽车中的手动变速器。如果您学会驾驶配备自动变速器的汽车,那不会成为一个糟糕的司机。而且您仍然可以完成驾驶员在手动变速箱上学到的大部分内容。你的驾驶知识只会有一个漏洞。如果您必须手动驾驶,您可能会遇到麻烦。当然,它的基本概念很容易理解,但是一旦你必须从坡道开始,你就完蛋了。但是,手动变速器仍有一席之地。例如,赛车手需要能够换档以使汽车以最佳方式响应当前的赛车条件。拥有手动变速箱对他们的成功非常重要。

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

于 2008-09-18T12:43:37.843 回答
5

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

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

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

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

你可能已经知道了。

回到 C 语言,函数 A 将一些变量传递给函数 B。这些函数参数是本机副本,按值。为了让函数 B 操作属于函数 A 的副本,函数 A 必须传递一个指向变量的指针,这样它就变成了按引用传递。

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

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

于 2008-09-18T12:36:13.213 回答
4

我在日常工作中大量使用指针和引用……在托管代码(C#、Java)和非托管代码(C++、C)中。我了解了如何处理指针以及大师自己的指针是什么...[Binky!][1] 无需多说;)

指针和引用之间的区别是这样的。指针是指向某个内存块的地址。它可以被重写,或者换句话说,重新分配给其他一些内存块。引用只是对某个对象的重命名。只能分配一次!一旦分配给一个对象,就不能再分配给另一个对象。引用不是地址,它是变量的另一个名称。查看C++ FAQ了解更多信息。

链接1

链接2

于 2008-09-18T13:04:22.520 回答
4

如果您以前没有看过指针,那么您肯定会错过这个迷你宝石:

void strcpy(char *dest, char *src)
{    
        while(*dest++ = *src++);
}
于 2008-09-18T13:42:39.777 回答
4

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

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

于 2008-09-18T14:49:10.680 回答
2

我目前正在设计一些高级企业软件,其中数据块(在本例中存储在 SQL 数据库中)被 1 个或多个其他实体引用。如果在没有更多实体引用时保留了一大块数据,我们就是在浪费存储空间。如果参考点如此不存在的数据,那也是一个大问题。

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

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

在 Java 中,您一直在使用指针。大多数变量都是指向对象的指针——这就是为什么:

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

...打印“你好男孩”而不是“你好”。

C 语言中唯一的区别在于,指针的加减法很常见——如果你弄错了逻辑,你最终可能会弄乱你不应该接触的数据。

于 2008-09-18T12:46:20.427 回答
2

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

好的,每个字符串都是一个字符数组(81 个字符 - 你不能忘记 null 否则你会很抱歉!)然后每个字符串本身就是一个数组。最终结果将是一个多维数组,例如

char dict[10][81];

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

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

还有很多人认为,理解指针的能力与编程技能高度相关。乔尔提出了这个论点,等等。例如

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

但它对于一些最令人兴奋的编程工作仍然很重要。例如,如果没有指针,您将永远无法在 Linux 内核上工作。如果不真正理解指针,你就无法理解 Linux 中的一行代码,或者实际上,任何操作系统。

这里。优秀的文章。

于 2008-09-18T13:09:08.190 回答
2

老实说,如果您不知道指针,大多数经验丰富的开发人员都会笑(希望是友好的)。在我以前的工作中,我们去年有两个新员工(刚毕业)不知道指针,仅这一点就成为与他们交谈大约一周的话题。没有人会相信一个人在不知道指针的情况下如何毕业......

于 2008-09-18T15:02:48.353 回答
2

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

C++ 引用或 .NET byref(我将使用后一个术语,以与 .NET 引用区分开来)是一种特殊类型,它不包含变量,而是包含足以识别变量(或可以运行的东西)的信息作为一个,例如阵列插槽)在别处举行。Byrefs 通常仅用作函数参数/参数,并且旨在是短暂的。将 byref 传递给函数的代码保证由此标识的变量将至少在该函数返回之前存在,并且函数通常保证在返回后不保留任何 byref 副本(请注意,在 C++ 中,后一个限制不是强制执行)。因此,byrefs 不能超过由此确定的变量。

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

Java 和 .NET 语言中引用的特殊之处在于,作为绝对不变量,只要该引用存在,每个非空引用都将继续标识同一个堆对象。一旦在宇宙中的任何地方都不存在对堆对象的引用,堆对象将简单地停止存在,但是当对堆对象的任何引用存在时,堆对象无法停止存在,也没有任何方法可以实现“正常” " 对堆对象的引用自发地变成对该对象的引用以外的任何东西。Java 和 .NET 都具有特殊的“弱引用”类型,但即使它们也支持不变量。如果 Universe 中任何地方都不存在对对象的非弱引用,则任何现有的弱引用都将失效;一旦发生,就不会有'

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

指针和引用(任一类型)之间的主要区别在于,始终可以询问引用是否有效(它们将有效或可识别为 null),并且如果观察到有效,它们将一直存在,只要它们存在。不能询问指针是否有效,并且系统不会做任何事情来确保指针不会变为无效,也不会允许无效的指针被识别为这样。

于 2015-05-28T17:19:53.080 回答
1

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

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.

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

  1. 为了性能而避免昂贵的对象复制操作。
  2. 其他一些因素不允许对象复制操作。
  3. 您希望函数调用对对象产生副作用(不要传递对象,将指针传递给它)。
  4. 在某些语言中 - 如果您想从函数返回多个值(尽管通常避免)。
于 2008-09-18T13:24:30.693 回答
0

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

于 2008-09-18T12:21:59.737 回答
0

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

于 2008-09-18T12:24:45.720 回答
0

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

于 2008-09-18T12:25:41.380 回答
0

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

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

于 2008-09-18T12:40:39.363 回答
0

使用 C 和 C++ 等语言进行编程,您更接近“金属”。指针保存变量、数据、函数等所在的内存位置。您可以传递指针而不是按值传递(复制变量和数据)。

指针有两点困难:

  1. 关于指针、寻址等的指针可能会变得非常神秘。它会导致错误,并且难以阅读。
  2. 指针指向的内存通常是从堆中分配的,这意味着您负责释放该内存。应用程序越大,就越难满足这一要求,最终会导致难以追踪的内存泄漏。

您可以将指针行为与 Java 对象的传递方式进行比较,但在 Java 中您不必担心释放内存,因为这是由垃圾收集处理的。通过这种方式,您可以获得关于指针的好处,但不必处理负面因素。当然,如果您不取消引用您的对象,您仍然可以在 Java 中获得内存泄漏,但那是另一回事。

于 2008-09-18T13:06:46.853 回答
0

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

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

于 2008-09-18T14:39:25.803 回答
0

我总是对高级语言中对指针或引用等事物的关注感到苦恼。在更高的抽象层次上考虑对象(甚至只是函数)的行为确实很有用,而不是考虑“让我看看,如果我将这个东西的地址发送到那里,那么那个东西会给我一个指向别的东西的指针”

甚至考虑一个简单的交换函数。如果你有

无效交换(int&a,int&b)

或者

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

然后将这些解释为可以更改值。事实上,这是通过传递变量的地址来实现的,这只是分散了目的。

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

于 2009-05-15T11:20:38.693 回答
-3

写两行以上的 c 或 c++,你就会发现。

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

于 2008-09-18T12:21:39.690 回答