又名 - 对指针的痴迷是什么?

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

有帮助吗?

解决方案

这只是间接的:能够不处理数据,而是说“我将引导您查看那边的一些数据”。Java 和 C# 中有相同的概念,但只是参考格式。

关键的区别在于引用实际上是不可变的路标——它们总是指向某些东西。这很有用,并且易于理解,但不如 C 指针模型灵活。C 指针是您可以愉快地重写的路标。您知道您要查找的字符串与所指向的字符串相邻吗?好吧,稍微改变一下路标就可以了。

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

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

其他提示

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

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

当然,这在现代系统上不起作用。操作系统是主存的主宰。如果您尝试访问错误的内存位置,您的进程将为它的狂妄付出生命的代价。

在 C 语言中,指针是传递数据引用的方式。当您调用函数时,您不想将一百万位复制到堆栈中。相反,您只需告诉数据驻留在主内存中的位置即可。换句话说,你给一个 指针 到数据。

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

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

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

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

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

这就像在问:“对 CPU 指令的痴迷是什么?如果我不到处散布 x86 MOV 指令,我会错过一些东西吗?”

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

所以...不用担心。您已经在使用指针了——而且也不会有错误使用的危险。:)

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

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

既然您一直在使用面向对象语言进行编程,那么让我这样说吧。

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

因为你传入了一个 参考 将对象 B 复制到对象 C,而不是制作对象 B 的另一个副本。因此,对象 A 和对象 C 都在内存中保存对同一对象 B 的引用。从一处发生的变化,在另一处也能看到。这称为“按引用”。

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

你可能已经知道了。

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

“嘿,这是我的整数变量的内存地址。将新值放入该地址位置,我稍后会取。”

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

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

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

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

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

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

指针和引用的区别就在于此。指针是某个内存块的地址。它可以被重写,或者换句话说,可以重新分配给其他一些内存块。引用只是对某个对象的重命名。只能分配一次!一旦它被分配给一个对象,它就不能被分配给另一个对象。引用不是地址,而是变量的另一个名称。查看 C++ 常见问题解答 了解更多相关信息。

链接1

链接2

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

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

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

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

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

...打印“Hello boy”而不是“Hello”。

C 中唯一的区别是,对指针进行加法和减法是很常见的 - 如果你的逻辑错误,你最终可能会弄乱你不应该接触的数据。

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

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

char dict[10][81];

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

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

许多人还认为,理解指针的能力与编程技能高度相关。乔尔(Joel)提出了这个论点以及其他论点。例如

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

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

这里. 。优秀的文章。

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

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

C++ 引用或 .NET byref(我将使用后一个术语,以区别于 .NET 引用)是一种特殊类型,它不保存变量,而是保存足以识别变量(或可以表现的内容)的信息。作为一个,例如数组槽)保存在其他地方。Byref 通常仅用作函数参数/参数,并且是短暂的。将 byref 传递给函数的代码保证所标识的变量至少在该函数返回之前存在,并且函数通常保证在返回后不保留 byref 的任何副本(请注意,在 C++ 中,后一个限制不是强制执行)。因此,byref 不能比由此识别的变量更长寿。

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

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

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

指针和引用(任一类型)之间的主要区别在于,始终可以询问引用是否有效(它们要么有效,要么可识别为空),并且如果观察到有效,则只要它们有效,它们就会保持不变。存在。不能询问指针是否有效,并且系统不会采取任何措施来确保指针不会变得无效,也不会允许无效的指针被识别为无效。

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

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

这种方法的一个问题是,在修改“A”之后,我必须将其重新分配给“store”数组才能使更改永久生效:

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. 在某些语言中 - 如果您想从函数中返回多个值(尽管通常避免)。

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

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

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

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

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

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

指针有两件事比较困难:

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

您可以将指针行为与 Java 对象的传递方式进行比较,但在 Java 中,您不必担心释放内存,因为这是由垃圾回收处理的。通过这种方式,您可以获得指针的优点,但不必处理缺点。当然,如果你不取消引用你的对象,你仍然可能会在 Java 中遇到内存泄漏,但这是另一回事。

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

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

我总是对高级语言中对指针或引用之类的关注感到苦恼。从对象(甚至只是函数)的行为的角度进行更高抽象层次的思考非常有用,而不是“让我看看,如果我将这个东西的地址发送到那里,那么那个东西”会返回给我一个指向其他东西的指针”

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

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

或者

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top