理解指针有什么障碍,克服它们可以做些什么?

时间:2008-08-08 06:17:52

标签: c++ c pointers

为什么指针会成为C或C ++中许多新的,甚至是旧的大学学生混淆的主要因素?是否有任何工具或思维过程可以帮助您理解指针在变量,函数和更高级别的工作方式?

有什么好的做法可以让某人达到“Ah-hah,我得到它”的水平,而不会让他们陷入整体概念的困境?基本上,钻取方案。

28 个答案:

答案 0 :(得分:740)

答案 1 :(得分:150)

在我的第一个Comp Sci课程中,我们进行了以下练习。当然,这是一个有大约200名学生的演讲厅......

教授在董事会上写道:int john;

John站起来

教授写道:int *sally = &john;

莎莉站起来,指着约翰

教授:int *bill = sally;

比尔站起来,指着约翰

教授:int sam;

Sam站起来

教授:bill = &sam;

比尔现在指向萨姆。

我想你明白了。我想我们花了大约一个小时做这个,直到我们完成了指针赋值的基础知识。

答案 2 :(得分:124)

我发现有助于解释指针的类比是超链接。大多数人都可以理解,网页上的链接“指向”互联网上的另一个页面,如果您可以复制&粘贴该超链接然后它们将指向相同的原始网页。如果你去编辑那个原始页面,那么按照这些链接(指针)中的任何一个,你将获得新的更新页面。

答案 3 :(得分:48)

指针似乎让很多人感到困惑的原因是,他们大多数人在计算机体系结构方面很少或没有背景。由于许多人似乎并不了解计算机(机器)的实际实现方式 - 在C / C ++中工作似乎很陌生。

演习要求他们实现一个简单的基于字节码的虚拟机(使用他们选择的任何语言,python非常适合这一点),其指令集主要集中在指针操作(加载,存储,直接/间接寻址)。然后让他们为该指令集编写简单的程序。

任何需要稍微多于简单添加的东西都会涉及指针,他们肯定会得到它。

答案 4 :(得分:27)

  

对于C / C ++语言中的许多新的,甚至是老的大学生来说,为什么指针会成为混乱的主要因素?

价值占位符的概念 - 变量 - 映射到我们在学校教授的东西 - 代数。没有现成的并行可以在不了解内存如何在计算机中进行物理布局的情况下进行绘制,并且在处理低级事物之前没有人会考虑这种事情 - 在C / C ++ /字节通信级别

  

是否有任何工具或思维过程可以帮助您理解指针在变量,函数和更高级别的工作方式?

地址框。我记得当我学习将BASIC编程到微型计算机中的时候,有这些漂亮的书籍里面有游戏,有时候你不得不把价值观点到特定的地址。他们有一堆盒子的图片,逐渐标记为0,1,2 ......并且解释说只有一个小东西(一个字节)可以放在这些盒子里,而且它们中有很多 - 一些电脑有多达65535!他们彼此相邻,他们都有一个地址。

  

有什么好的做法可以让某人达到“Ah-hah,我得到它”的水平,而不会让他们陷入整体概念的困境?基本上,钻取方案。

钻头?制作一个结构:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

与上述相同,但在C:

中除外
// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

输出:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

或许通过示例解释了一些基础知识?

答案 5 :(得分:23)

起初我很难理解指针的原因是许多解释包括很多关于通过引用传递的废话。所有这一切都是混淆了这个问题。当你使用指针参数时,你仍然传递值;但是值恰好是一个地址而不是一个int。

其他人已经链接到本教程,但我可以突出我开始理解指针的那一刻:

A Tutorial on Pointers and Arrays in C: Chapter 3 - Pointers and Strings

int puts(const char *s);
  

目前,忽略const.传递给puts()的参数是一个指针,是指针的值(因为C中的所有参数都是按值传递的),并且指针的值是它指向的地址,或者简单地说是一个地址。因此,当我们看到puts(strA);时,我们正在传递strA [0]的地址。 / p>

在我读完这些文字的那一刻,云层分开,一束阳光笼罩着我,指针理解。

即使您是VB .NET或C#开发人员(就像我一样)并且从不使用不安全的代码,仍然值得理解指针是如何工作的,否则您将无法理解对象引用的工作方式。然后你会有一个常见但错误的概念,即将对象引用传递给方法会复制该对象。

答案 6 :(得分:19)

我发现Ted Jensen的“C指针和数组教程”是学习指针的绝佳资源。它分为10节课,首先解释指针是什么(以及它们的用途)和完成函数指针。 http://home.netcom.com/~tjensen/ptr/cpoint.htm

从那里开始,Beej的网络编程指南教授Unix套接字API,您可以从中开始做有趣的事情。 http://beej.us/guide/bgnet/

答案 7 :(得分:12)

指针的复杂性超出了我们可以轻易教授的范围。让学生互相指出并使用带有房屋地址的纸张都是很好的学习工具。他们在介绍基本概念方面做得很好。实际上,学习基本概念至关重要以成功使用指针。但是,在生产代码中,通常会遇到比这些简单的演示可以封装更复杂的场景。

我参与了系统,我们的结构指向指向其他结构的其他结构。其中一些结构也包含嵌入式结构(而不​​是指向其他结构的指针)。这是指针变得非常混乱的地方。如果您有多个间接级别,并且您开始使用这样的代码:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

它可能会很快变得混乱(想象更多的线条,可能更多的水平)。抛出指针数组,节点到节点指针(树,链表),它会变得更糟。我看到一些非常优秀的开发人员一旦开始研究这样的系统就会迷路,甚至是那些理解基础知识的开发人员。

指针的复杂结构不一定表示编码不良(尽管它们可以)。组合是良好的面向对象编程的重要组成部分,在具有原始指针的语言中,它将不可避免地导致多层间接。此外,系统通常需要使用第三方库,其结构在风格或技术上彼此不匹配。在这种情况下,复杂性自然会出现(尽管我们应该尽可能地对抗它)。

我认为大学可以帮助学生学习指针的最好方法是使用良好的演示,结合需要使用指针的项目。一个困难的项目将比一千次演示更多地用于指针理解。演示可以让你有一个浅薄的理解,但要深入掌握指针,你必须真正使用它们。

答案 8 :(得分:10)

我认为我在这个列表中添加了一个类比,在我作为计算机科学导师解释指针(当天)时,我发现它非常有用;首先,让我们:


设置舞台

考虑一个有3个停车位的停车场,这些停车位编号为:

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

在某种程度上,这就像内存位置,它们是连续的和连续的......有点像数组。现在它们里面没有车,所以它就像一个空阵列(parking_lot[3] = {0})。


添加数据

一个停车场永远不会空着......如果这样做会毫无意义,没有人可以建造任何停车场。所以,让我们说,当这一天的行动充满了3辆汽车,一辆蓝色汽车,一辆红色汽车和一辆绿色汽车:

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

这些车型都是同一型号(汽车)所以想到这一点的一种方法是我们的​​车是某种数据(比如int),但它们有不同的值(blueredgreen;可以是颜色enum


输入指针

现在,如果我带你进入这个停车场,并要求你找到一辆蓝色的汽车,你伸出一根手指并用它指向现场1的蓝色汽车。这就像拿指针并指定它一样内存地址(int *finger = parking_lot

你的手指(指针)不是我问题的答案。在 看 你的手指什么也没告诉我,但如果我看到你的手指指向(取消引用指针),我可以找到我正在寻找的汽车(数据)。


重新指定指针

现在我可以请你找一辆红色汽车,然后你可以将你的手指重定向到一辆新车。现在你的指针(和以前一样)向我展示了相同类型(汽车)的新数据(可以找到红色汽车的停车位)。

指针没有物理变化,它仍然是你的手指,只是它显示我改变的数据。 (&#34;停车位&#34;地址)


双指针(或指针指针)

这也适用于多个指针。我可以问指针在哪里,指向红色汽车,你可以用另一只手,用手指指向第一根手指。 (这就像int **finger_two = &finger

现在,如果我想知道蓝色车的位置,我可以按照第一根手指的方向指向第二根手指,指向汽车(数据)。


悬空指针

现在让我们说你感觉非常像一尊雕像,你想要无限期地用手指着红色的汽车。如果那辆红色车开走了怎么办?

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

您的指针仍然指向红色汽车 的位置但不再是。让我们说一辆新车在那里拉一辆橙色汽车。现在,如果我再次问你,&#34;红色汽车在哪里,你仍然指着那里,但现在你错了。那不是一辆红色的汽车,那是橙色的。


指针算术

好的,所以你仍然指着第二个停车位(现在被橙色汽车占用)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

我现在有一个新问题......我想知道下一个停车位的汽车颜色。您可以看到您指向第2点,因此您只需添加1即可指向下一个点。 (finger+1),现在因为我想知道数据是什么,你必须检查那个点(而不仅仅是手指)所以你可以用指针(*(finger+1))来看看有没有那里有绿色汽车(该地点的数据)

答案 9 :(得分:9)

我不认为指针作为一个概念特别棘手 - 大多数学生的心理模型都映射到这样的东西,一些快速的盒子草图可以帮助。

困难,至少我过去经历过的并且看到其他人处理的问题,是C / C ++中指针的管理可能会不经意地复杂化。

答案 10 :(得分:9)

An example of a tutorial with a good set of diagrams helps greatly with the understanding of pointers

Joel Spolsky在他的Guerrilla Guide to Interviewing文章中提出了一些关于理解指针的好点:

  

由于某种原因,大多数人似乎是在没有理解指针的大脑部分出生的。这是一种天赋,而不是技能 - 它需要一种复杂形式的双向间接思维,有些人根本无法做到。

答案 11 :(得分:8)

我认为理解指针的主要障碍是坏老师。

几乎每个人都被教导谎言:他们只是只是内存地址,或者它们允许你指向任意位置

当然,他们很难理解,危险和半魔法。

其中没有一个是真的。指针实际上是相当简单的概念,只要你坚持C ++语言对它们所说的内容并且不要给它们灌输“通常”在实践中发挥作用的属性,但仍然如此不受语言保证,因此不是指针实际概念的一部分。

几个月前我试图在this blog post中写下这个问题的解释 - 希望它能帮助别人。

(注意,在任何人对我嗤之以鼻之前,是的,C ++标准确实说指针代表内存地址。但它并没有说“指针是内存地址,只有内存地址并且可以与内存地址互换使用或思考“。区别很重要”

答案 12 :(得分:7)

指针的问题不是概念。这是涉及的执行和语言。当教师认为这是困难的指针的概念,而不是行话,或者C和C ++的复杂混乱时,会产生额外的混淆。因此,大量的努力都被用于解释这个概念(就像在这个问题的公认答案中一样)而且它几乎只是浪费在像我这样的人身上,因为我已经理解了所有这些。它只是解释了问题的错误部分。

为了让您了解我的来源,我是一个非常了解指针的人,我可以在汇编语言中胜任它们。因为在汇编语言中它们不被称为指针。它们被称为地址。当涉及到在C中编程和使用指针时,我犯了很多错误并且变得非常困惑。我还没有把它整理出来。我举个例子。

当api说:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

它想要什么?

它可能想要:

表示缓冲区地址的数字

(为此,我要说doIt(mybuffer)doIt(*myBuffer)吗?)

表示缓冲区地址的地址的数字

(是doIt(&mybuffer)还是doIt(mybuffer)还是doIt(*mybuffer)?)

一个数字,表示地址到缓冲区地址的地址

(也许是doIt(&mybuffer)。或者doIt(&&mybuffer)?甚至doIt(&&&mybuffer)

等等,所涉及的语言并没有明确表达,因为它涉及的词语“指针”和“引用”对我来说没有那么多意义和清晰度,因为“x将地址保持为y “和”此函数需要一个地址y“。答案另外还取决于“mybuffer”的开头是什么,以及它打算用它做什么。该语言不支持实践中遇到的嵌套级别。就像当我必须将“指针”交给一个创建一个新缓冲区的函数时,它会修改指针以指向缓冲区的新位置。它确实需要指针或指针指针,因此它知道在哪里修改指针的内容。大多数时候我只需要猜测“指针”是什么意思,而且大部分时间我都错了,不管我猜测有多少经验。

“指针”太过分了。指针是一个值的地址吗?或者它是一个保存值的地址的变量。当一个函数想要一个指针时,它是否需要指针变量所持有的地址,或者它是否希望地址指向变量? 我很困惑。

答案 13 :(得分:5)

当我只知道C ++时,我可以使用指针。我有点知道在某些情况下该做什么以及从试验/错误中无法做什么。但让我完全理解的是汇编语言。如果你使用你编写的汇编语言程序进行一些严格的指令级调试,你应该能够理解很多东西。

答案 14 :(得分:5)

它难以理解的原因并不是因为它是一个困难的概念,而是因为语法不一致

   int *mypointer;

首先,您了解到变量创建的最左侧部分定义了变量的类型。在C和C ++中,指针声明不起作用。相反,他们说变量指向左侧的类型。在这种情况下: * mypointer 指向

我没有完全掌握指针,直到我尝试在C#中使用它们(不安全),它们以完全相同的方式工作,但具有逻辑和一致的语法。指针本身就是一种类型。这里 mypointer 指向int的指针。

  int* mypointer;

甚至不让我开始使用函数指针......

答案 15 :(得分:5)

我认为使得指针难以学习的是,直到指针你觉得“在这个内存位置是一组表示int,double,a character,what”的想法。

当你第一次看到一个指针时,你并没有真正得到该内存位置的内容。 “你是什么意思,它有一个地址?”

我不同意“你要么得到它们要么得不到你”这一概念。

当你开始为它们寻找实际用途时(比如不将大型结构传递给函数),它们变得更容易理解。

答案 16 :(得分:4)

我喜欢房子地址类比,但我一直都想到地址是邮箱本身。这样,您可以可视化取消引用指针(打开邮箱)的概念。

例如,在链接列表后面: 1)从你的论文开始,带有地址 2)转到纸上的地址 3)打开邮箱,找到一张带有下一个地址的新纸

在线性链接列表中,最后一个邮箱中没有任何内容(列表末尾)。在循环链接列表中,最后一个邮箱具有其中第一个邮箱的地址。

请注意,步骤3是取消引用的地方,以及当地址无效时您将崩溃或出错的地方。假设你可以走到一个无效地址的邮箱,想象那里有一个黑洞或其他东西可以把这个世界彻底改变:)

答案 17 :(得分:3)

我认为人们遇到麻烦的主要原因是因为它通常不是以有趣和引人入胜的方式教授的。我希望看到一位讲师从人群中获得10名志愿者并给他们一个1米的标尺,让他们以一定的配置站立并使用统治者指向对方。然后通过移动人物(以及他们指向统治者的地方)来显示指针算术。它是一种简单但有效(并且最重要的是令人难忘)的方式来展示概念,而不会在机制中陷入困境。

一旦你接触到C和C ++,对某些人来说似乎变得更难了。我不确定这是不是因为他们最终把理论认为他们没有正确地把握到实践中,或者因为指针操作在这些语言中本质上更难。我不记得我自己的过渡,但我知道> Pascal中的指针,然后转移到C并完全失去了。

答案 18 :(得分:2)

我认为这实际上可能是语法问题。指针的C / C ++语法似乎不一致,而且比它需要的更复杂。

具有讽刺意味的是,实际帮助我理解指针的东西是在c ++ Standard Template Library中遇到迭代器的概念。这很具有讽刺意味,因为我只能假设迭代器被认为是指针的泛化。

有时在你学会忽视树木之前,你才能看到森林。

答案 19 :(得分:2)

我认为指针本身并不令人困惑。大多数人都能理解这个概念。现在您可以考虑多少指针或您熟悉多少级别的间接。把人放在边缘并不需要太多。它们可能会被程序中的错误意外更改,这也会使代码在出现问题时很难调试。

答案 20 :(得分:2)

混淆来自于“指针”概念中混合在一起的多个抽象层。程序员不会对Java / Python中的普通引用感到困惑,但指针的不同之处在于它们暴露了底层内存架构的特征。

清晰地分离抽象层是一个很好的原则,指针不会这样做。

答案 21 :(得分:2)

我喜欢解释它的方式是数组和索引 - 人们可能不熟悉指针,但他们通常知道索引是什么。

所以我想说RAM是一个数组(你只有10个字节的RAM):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

然后指向变量的指针实际上只是RAM中该变量的(第一个字节)的索引。

所以如果你有一个指针/索引unsigned char index = 2,那么这个值显然是第三个元素,或者是数字4.指向指针的指针就是你取这个数字并将它用作索引本身,比如RAM[RAM[index]]

我会在纸张列表上绘制一个数组,并使用它来显示指向同一内存,指针算术,指针指针等许多指针的内容。

答案 22 :(得分:1)

通过迭代器抓住它并不是一个糟糕的方法..但是继续看着你会看到Alexandrescu开始抱怨它们。

许多ex-C ++开发人员(在转储语言之前从未理解迭代器是一个现代指针)跳转到C#并且仍然认为他们有合适的迭代器。

嗯,问题在于所有迭代器都是运行时平台(Java / CLR)试图实现的完全赔率:新的,简单的,每个人都是开发者的用法。这可能是好的,但他们曾在紫皮书中说过,他们甚至在C:之前和之前都说过它:

间接。

一个非常强大的概念,但如果你一直这样做,那就永远不会这样。迭代器很有用,因为它们有助于抽象算法,另一个例子。编译时是算法的地方,非常简单。你知道代码+数据,或者用其他语言C#:

IEnumerable + LINQ + Massive Framework = 300MB运行时惩罚间接糟糕,通过大量的引用类型拖动应用程序..

“Le Pointer很便宜。”

答案 23 :(得分:1)

上面的一些答案断言“指针并不是很难”,但还没有直接解决“指针很难!”的问题。来自。几年前,我辅导了一年级的CS学生(仅仅一年,因为我明显很喜欢它),我很清楚指针的想法并不难。什么是难以理解为什么以及何时需要指针

我认为你不能解释这个问题 - 为什么以及何时使用指针 - 来解释更广泛的软件工程问题。为什么每个变量都是一个全局变量,为什么要将类似的代码分解为函数(为此,使用指针将其行为专门化为其调用站点)。

答案 24 :(得分:1)

邮政信箱号码。

这是一条允许您访问其他内容的信息。

(如果你对邮局票号进行算术运算,你可能会遇到问题,因为这封信出错了。如果有人进入另一个状态 - 没有转发地址 - 那么你就有一个悬空另一方面 - 如果邮局转发邮件,那么你有一个指针指针。)

答案 25 :(得分:0)

只是为了混淆一些事情,有时你必须使用句柄而不是指针。句柄是指针的指针,因此后端可以在内存中移动内容以对堆进行碎片整理。如果指针在中间例程中发生变化,则结果是不可预测的,因此您首先必须锁定句柄以确保没有任何内容。

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5谈论它比我更连贯一点。 : - )

答案 26 :(得分:0)

我看不出有什么令人困惑的指针。它们指向内存中的某个位置,即存储内存地址。在C / C ++中,您可以指定指针指向的类型。例如:

int* my_int_pointer;

表示my_int_pointer包含一个包含int的位置的地址。

指针的问题在于它们指向内存中的某个位置,因此很容易跟踪到您不应该进入的某个位置。作为证据,请查看缓冲区溢出中C / C ++应用程序中的众多安全漏洞(将指针递增超过分配的边界。)

答案 27 :(得分:0)

每个C / C ++初学者都有同样的问题,而且问题不是因为“指针难以学习”,而是“解释的是谁以及如何解释”。有些学习者在视觉上口头收集它,解释它的最佳方式是使用“train”示例(适用于口头和视觉示例)。

“locomotive”不能保留任何东西的指针而“wagon”是“机车”试图拉动(或点)至)。之后,您可以对“旅行车”本身进行分类,它可以包含动物,植物或人(或它们的混合物)。