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

quo_et 发布于 2019-11-10 c 最后更新 2019-11-10 11:50 188 浏览

为什么C或C++中许多新的,甚至是老年的大学水平的学生都指出这种混乱的主要原因?是否有任何工具或思维过程帮助您理解指针在变量,函数和更高级别上的工作方式? 有什么好的做法可以使人们达到“啊,我明白了”的水平,而不会让他们陷入整体概念?基本上,钻类似的情况。

已邀请:

walias

赞同来自:

混淆来自于“指针”概念中混合在一起的多个抽象层。程序员不会对Java / Python中的普通引用感到困惑,但指针的不同之处在于它们暴露了底层内存架构的特性。 清晰地分离抽象层是一个很好的原则,而指针则不这样做。

podio

赞同来自:

每个C / C++初学者都有同样的问题,这个问题不是因为“指针难以学习”而是“谁以及如何解释”。有些学习者在视觉上口头收集它,最好的解释方法是使用“训练”的例子(适用于口头和视觉的例子)。 “机车”是指不能容纳任何东西的指针,“货车”是“机车”试图拉(或指向)的东西。之后,您可以将“旅行车”本身分类,它可以容纳动物,植物或人(或它们的混合物)。

rtotam

赞同来自:

我认为理解指针的主要障碍是坏老师。 几乎每个人都被教导关于指针的谎言:它们只不过是内存地址,或者它们允许你指向任意位置。 当然,他们很难理解,危险和半魔法。 这些都不是真的。指针实际上是相当简单的概念,只要你坚持使用C++语言对它们所说的内容并且不要给它们灌输“通常”在实践中发挥作用的属性,但是语言并不能保证这些属性。 ,所以不是指针实际概念的一部分。 几个月前我试图在this blog post中写下这个解释 - 希望它能帮到某个人。 (注意,在任何人对我嗤之以鼻之前,是的,C++标准确实说指针代表了内存地址。但它并没有说“指针是内存地址,只是内存地址,可以使用或者可以与内存交换使用地址“。区别很重要)

reum

赞同来自:

上面的一些答案断言“指针并不是很难”,但还没有直接解决“指针很难!”的问题。来自。几年前,我辅导了一年级的CS学生(仅仅一年,因为我很清楚它),我很清楚指针的想法并不难。什么是难以理解为什么以及何时需要指针。 我不认为你可以解释这个问题 - 为什么以及何时使用指针 - 来解释更广泛的软件工程问题。为什么每个变量都不应该是一个全局变量,为什么要将类似的代码分解为函数(为此,使用指针将其行为专门化为其调用站点)。

tqui

赞同来自:

我认为使得指针难以学习的是,直到指针你对“在这个内存位置是一组表示int,double,a character,what”的想法感到满意。 当你第一次看到一个指针时,你并没有真正得到那个内存位置的内容。 “你是什么意思,它有一个地址?” 我不同意“你要么得到它们要么你没得到”这一概念。 当您开始为它们寻找实际用途时(例如不将大型结构传递到函数中),它们变得更容易理解。

sed_et

赞同来自:

指针的复杂性超出了我们可以轻易教授的范围。让学生互相指出并使用带有住址的纸张都是很好的学习工具。他们在介绍基本概念方面做得很好。实际上,学习基本概念对于成功使用指针至关重要。但是,在生产代码中,除了这些简单的演示可以封装之外,进入更复杂的场景是很常见的。 我参与过系统,我们的结构指向指向其他结构的其他结构。其中一些结构也包含嵌入式结构(而不​​是指向其他结构的指针)。这是指针变得非常混乱的地方。如果你有多个级别的间接,那么你开始使用这样的代码:

widget->wazzle.fizzle = fazzle.foozle->wazzle;
它可以很快变得混乱(想象更多的线条,可能更多的水平)。抛出指针数组,节点到节点指针(树,链表),它会变得更糟。我看到一些非常优秀的开发人员一旦开始研究这样的系统就会迷路,甚至开发人员也非常了解基础知识。 指针的复杂结构不一定表示编码不良(尽管它们可以)。组合是良好的面向对象编程的重要组成部分,在具有原始指针的语言中,它将不可避免地导致多层间接。此外,系统通常需要使用具有在样式或技术上彼此不匹配的结构的第三方库。在这样的情况下,复杂性自然会出现(尽管如此,我们应该尽可能地对抗它)。 我认为大学可以帮助学生学习指针的最好方法是使用良好的演示,结合需要使用指针的项目。对于指针理解而言,一个困难的项目将比一千次演示更多。演示可以让你有一个浅薄的理解,但要深入掌握指针,你必须真正使用它们。

get

赞同来自:

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

xid

赞同来自:

我喜欢房子地址类比,但我一直都想到地址是邮箱本身。这样,您可以可视化取消引用指针(打开邮箱)的概念。 例如,遵循链表: 1)从你的论文开始,带有地址 2)转到纸上的地址 3)打开邮箱,找到一张带有下一个地址的新纸 在线性链接列表中,最后一个邮箱中没有任何内容(列表的末尾)。在循环链接列表中,最后一个邮箱具有其中第一个邮箱的地址。 请注意,步骤3是取消引用的地方,以及当地址无效时您将崩溃或出错的地方。假设你可以走到一个无效地址的邮箱,想象那里有一个黑洞或其他什么东西可以把世界彻底改变掉:)

hea

赞同来自:

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


设置舞台: 考虑一个有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) 你的手指(指针)不是我的问题的答案。看着你的手指什么也没告诉我,但如果我看到你手指指向的地方(取消引用指针),我就能找到我正在寻找的汽车(数据)。
重新指定指针 现在我可以要求你找一辆红色轿车而你可以将你的手指重定向到一辆新车。现在你的指针(和以前一样)向我展示了相同类型(汽车)的新数据(可以找到红色汽车的停车位)。 指针没有物理变化,它仍然是你的手指,只是它显示我改变的数据。 (“停车位”地址)
双指针(或指针指针) 这也适用于多个指针。我可以问一下指针在哪里,指向红色汽车,你可以用另一只手,用手指指向第一根手指。 (这就像int **finger_two = &finger) 现在,如果我想知道蓝色汽车在哪里,我可以按照第一根手指的方向指向第二根手指,向汽车(数据)。
悬垂的指针 现在让我们说你感觉非常像雕像,你想要无限期地用手指着红色的汽车。如果那辆红色车开走了怎么办?
   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |
你的指针仍然指向红色车的位置,但不再指向。让我们说一辆新车拉进去......一辆橙色汽车。如果我再次问你,“红色汽车在哪里”,你仍指着那里,但现在你错了。那不是一辆红色的车,那是橙色的。
指针算术 好的,所以你还是指着第二个停车位(现在被Orange车占用)
   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |
那么我现在有一个新问题......我想知道下一个停车位的汽车颜色。你可以看到你指向第2点,所以你只需加1就可以指向下一个位置。 (finger+1),因为现在我想知道的数据是什么在那里,你必须检查点(不只是手指),所以你可以尊重指针(*(finger+1))看到有一个绿色的汽车目前还(在数据那个位置)

est_et

赞同来自:

指针的问题不是概念。这是涉及的执行和语言。当教师认为这是困难的指针的概念,而不是行话,或者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”的开头是什么,以及它打算用它做什么。该语言不支持实践中遇到的嵌套级别。就像当我必须将“指针”交给一个创建一个新缓冲区的函数时,它会修改指针以指向缓冲区的新位置。它真的需要指针或指针的指针,因此它知道去哪里修改指针的内容。大多数时候我只需要猜测“指针”是什么意思,而且大部分时间我都错了,不管我猜测有多少经验。 “指针”太过分了。指针是值的地址吗?或者它是一个保存值的地址的变量。当一个函数想要一个指针时,它是否需要指针变量所持有的地址,或者它是否希望地址指向变量? 我很困惑。

faut

赞同来自:

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

jautem

赞同来自:

我发现Ted Jensen的“C指针和数组教程”是学习指针的绝佳资源。它分为10节课,首先解释指针是什么(以及它们用于什么)以及用函数指针完成。 http://home.netcom.com/~tjensen/ptr/cpoint.htm 从那里开始,Beej的网络编程指南教授Unix套接字API,您可以从中开始做有趣的事情。 http://beej.us/guide/bgnet/

wnihil

赞同来自:

我喜欢解释它的方式是数组和索引 - 人们可能不熟悉指针,但他们通常知道索引是什么。 所以我想说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]]。 我会在纸张列表上绘制一个数组,并使用它来显示指向同一内存的许多指针,指针算法,指针指针等等。

xminus

赞同来自:

我起初很难理解指针的原因是,很多解释都包含很多关于通过引用传递的废话。所有这一切都是混淆了这个问题。当您使用指针参数时,您仍然按值传递;但是值恰好是一个地址而不是一个int。 其他人已经链接到本教程,但我可以突出我开始理解指针的那一刻: A Tutorial on Pointers and Arrays in C: Chapter 3 - Pointers and Strings

int puts(const char *s);
For the moment, ignore the const. The parameter passed to puts() is a pointer, that is the value of a pointer (since all parameters in C are passed by value), and the value of a pointer is the address to which it points, or, simply, an address. Thus when we write puts(strA); as we have seen, we are passing the address of strA[0].
在我读完这些文字的那一刻,云层分开,一束阳光笼罩着我,指针理解。 即使你是VB .NET或C#开发人员(就像我一样)并且从不使用不安全的代码,仍然值得理解指针是如何工作的,或者你不会理解对象引用是如何工作的。然后你会有一个常见但错误的概念,即将对象引用传递给方法会复制该对象。

godio

赞同来自:

我认为它实际上可能是一个语法问题。指针的C / C++语法似乎不一致,而且比它需要的更复杂。 具有讽刺意味的是,实际帮助我理解指针的东西是在c ++ Standard Template Library中遇到迭代器的概念。这很具有讽刺意味,因为我只能假设迭代器被认为是指针的泛化。 有时在你学会忽视树木之前,你根本看不到森林。

cut

赞同来自:

通过迭代器抓住它并不是一个糟糕的方法..但继续看你会看到Alexandrescu开始抱怨他们。 许多前C++开发人员(在转储语言之前从未理解迭代器是一个现代指针)跳转到C#并且仍然认为他们有合适的迭代器。 嗯,问题是所有迭代器都在运行时平台(Java / CLR)试图实现的完全赔率:新的,简单的,每个人都是开发者的用法。这可能是好的,但他们曾在紫皮书中说过,他们甚至在C之前和之前都说过: 间接。 一个非常强大的概念,但如果你一直这样做,那就永远不会这样。迭代器很有用,因为它们有助于抽象算法,另一个例子。编译时是算法的地方,非常简单。你知道代码+数据,或者用其他语言C#: IEnumerable + LINQ + Massive Framework = 300MB运行时惩罚间接糟糕,通过大量的引用类型实例拖动应用程序。 “Le Pointer很便宜。”

oalias

赞同来自:

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

lsint

赞同来自:

邮政信箱号码。 这是一条允许您访问其他内容的信息。 (如果你对邮局票号进行算术运算,你可能会遇到问题,因为这封信出错了。如果有人进入另一个状态 - 没有转发地址 - 那么你就有一个悬空指针。另一方面 - 如果邮局转发邮件,那么你有一个指针指针。)

daut

赞同来自:

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

   int *mypointer;
您首先了解到变量创建的最左侧部分定义了变量的类型。在C和C++中,指针声明不起作用。相反,他们说变量指向左侧的类型。在这种情况下:*mypointer指向一个int。 我没有完全掌握指针,直到我尝试在C#中使用它们(不安全),它们以完全相同的方式工作,但具有逻辑和一致的语法。指针本身就是一种类型。这里mypointer是一个指向int的指针。
  int* mypointer;
甚至不让我开始使用函数指针...

ret

赞同来自:

Why are pointers such a leading factor of confusion for many new, and even old, college level students in the C/C++ language?
价值占位符的概念 - 变量 - 映射到我们在学校教授的东西 - 代数。没有现成的并行,你可以在不了解内存如何在计算机中进行物理布局的情况下进行绘制,并且在他们处理低级事物之前没有人会考虑这种事情 - 在C / C++ /字节通信级别。
Are there any tools or thought processes that helped you understand how pointers work at the variable, function, and beyond level?
地址框。我记得当我学习将BASIC编程到微型计算机中的时候,有些这些漂亮的书中都有游戏,有时候你不得不把价值观点到特定的地址。他们有一堆盒子的图片,逐渐标记为0,1,2 ......并且解释说这些盒子中只有一个小东西(一个字节)可以放入,而且它们中有很多 - 一些计算机有多达65535!他们彼此相邻,他们都有一个地址。
What are some good practice things that can be done to bring somebody to the level of, "Ah-hah, I got it," without getting them bogged down in the overall concept? Basically, drill like scenarios.
对于演习?制作一个结构:
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
也许这通过例子解释了一些基础知识?

hquia

赞同来自:

我不认为指针作为一个概念特别棘手 - 大多数学生的心理模型都映射到这样的东西,一些快速的盒子草图可以帮助。 困难,至少是我过去经历过和其他人看到过的难点,就是C / C++中指针的管理可能会不经意地复杂化。

det

赞同来自:

只是为了混淆一些东西,有时你必须使用句柄而不是指针。句柄是指针的指针,因此后端可以在内存中移动内容以对堆进行碎片整理。如果指针在中间例程中发生变化,则结果是不可预测的,因此您首先必须锁定句柄以确保没有任何内容在任何地方。 http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5谈论它比我更连贯一点。 :-)

tqui

赞同来自:

我没有看到关于指针的混乱。它们指向内存中的某个位置,即存储内存地址。在C / C++中,您可以指定指针指向的类型。例如:

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

nomnis

赞同来自:

指针似乎让很多人感到困惑的原因是,他们在计算机体系结构方面几乎没有任何背景。由于许多人似乎并不知道计算机(机器)是如何实现的 - 在C / C++中工作似乎很陌生。 演习是要求他们实现一个简单的基于字节码的虚拟机(使用他们选择的任何语言,python非常适合这一点),其指令集主要集中在指针操作(加载,存储,直接/间接寻址)。然后让他们为该指令集编写简单的程序。 任何需要稍微多于简单添加的东西都会涉及到指针,他们肯定会得到它。

reos

赞同来自:

在我的第一个Comp Sci课程中,我们进行了以下练习。当然,这是一个有大约200名学生的演讲厅...... 教授在董事会上写道:int john; 约翰站起来 教授写道:int sally = &john; 约翰站起来,指着约翰 教授:int bill = sally; 比尔站起来,指着约翰 教授:int sam; 萨姆站起来 教授:bill = &sam; 比尔现在指向萨姆。 我想你应该已经明白了。我想我们花了大约一个小时做这个,直到我们完成了指针赋值的基础知识。

lquia

赞同来自:

我认为人们遇到麻烦的主要原因是因为它通常不是以有趣和引人入胜的方式教授的。我希望看到一位讲师从人群中获得10名志愿者并给他们一个1米的标尺,让他们以一定的配置站立并使用统治者指向对方。然后通过移动人们(以及他们指向统治者的位置)来显示指针算术。它是一种简单但有效(并且最重要的是令人难忘)的方式来展示概念,而不会在机制中陷入困境。 一旦你进入C和C++,对某些人来说似乎变得更难了。我不确定这是不是因为他们最终把理论说他们没有正确地把握到实践中,或者因为指针操作在这些语言中本质上更难。我不记得我自己的过渡那么好,但我知道帕斯卡尔的指针,然后转移到C并完全失去了。

xet

赞同来自:

内容太长未翻译

baut

赞同来自:

An example of a tutorial with a good set of diagrams helps greatly with the understanding of pointers。 Joel Spolsky在他的Guerrilla Guide to Interviewing文章中提出了一些关于理解指针的好点:

For some reason most people seem to be born without the part of the brain that understands pointers. This is an aptitude thing, not a skill thing – it requires a complex form of doubly-indirected thinking that some people just can't do.