理解指针的障碍是什么?如何克服这些障碍?
为什么对于许多新的甚至老的C或者C ++大学水平的学生来说,指针是导致混乱的主要因素?是否有任何工具或者思维过程可以了解指针在变量,函数以及更高级别上的工作方式?
有什么好的做法可以使某人达到"啊哈,我明白了"的程度,而又不会使他们陷入整体观念的泥潭?基本上,像钻探一样的场景。
解决方案
回答
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
也许通过示例可以解释一些基本知识?
回答
我认为指针作为一个概念并不是特别棘手,大多数学生的心理模型映射到这样的东西,并且一些快速的方框草图可以有所帮助。
困难,至少是我过去所经历的以及其他人所面对的困难,是,不必要地复杂化了C / C ++中的指针管理。
回答
我看不出指针有什么令人困惑的地方。它们指向内存中的一个位置,即它存储内存地址。在C / C ++中,我们可以指定指针指向的类型。例如:
int* my_int_pointer;
说my_int_pointer包含指向包含int的位置的地址。
指针的问题是它们指向内存中的某个位置,因此很容易跳入我们不应该进入的某个位置。作为证据,着眼于C / C ++应用程序中缓冲区溢出引起的大量安全漏洞(增加指针)超过分配的边界)。
回答
指针似乎使很多人感到困惑的原因是,它们在计算机体系结构中大多没有或者几乎没有背景。由于许多人似乎对如何真正实现计算机(计算机)如何在C / C ++中工作一无所知。
钻研是要求他们实现一个简单的基于字节码的虚拟机(使用他们选择的任何语言,python都可以很好地实现这一点),其指令集专注于指针操作(加载,存储,直接/间接寻址)。然后要求他们为该指令集编写简单的程序。
除了简单的加法之外,任何需要更多的东西都将涉及指针,他们肯定会得到它。
回答
我认为人们遇到麻烦的主要原因是因为通常没有以一种有趣且引人入胜的方式来教授它。我希望看到一位讲师从人群中招募10名志愿者,每人给他们1米长的尺子,让他们以一定的配置站立并使用尺子互相指点。然后通过使人们四处移动(以及他们指向标尺的位置)来显示指针算法。这是一种简单而有效(最重要的是令人难忘的)的概念展示方式,而不会在原理上陷入困境。
一旦我们接触到C和C ++,对于某些人来说似乎会越来越难。我不确定这是因为他们最终将他们没有正确掌握的理论付诸实践,还是因为在这些语言中指针操作从本质上来说更加困难。我不太记得自己的转换,但是我知道Pascal中的指针,然后移到C并完全迷失了。
回答
指针是一个概念,很多人一开始可能会感到困惑,特别是在围绕周围复制指针值并仍然引用同一内存块的时候。
我发现最好的类比是将指针视为一张纸,上面有一个房屋的地址,而它所引用的内存块就是实际的房屋。因此可以容易地解释各种操作。
我在下面添加了一些Delphi代码,并在适当的地方添加了一些注释。我之所以选择Delphi,是因为我的其他主要编程语言C#不会以相同的方式展现诸如内存泄漏之类的东西。
如果仅希望学习指针的高级概念,则应忽略下面说明中标有"内存布局"的部分。它们旨在举例说明操作后的存储器外观,但是本质上它们是更底层的。但是,为了准确地解释缓冲区溢出是如何工作的,重要的是添加了这些图。
免责声明:出于所有意图和目的,本解释和示例记忆
布局大大简化了。会有更多的开销和更多的细节
需要知道是否需要低级处理内存。但是,对于
解释内存和指针的意图,它足够准确。
假设下面使用的THouse类如下所示:
type THouse = class private FName : array[0..9] of Char; public constructor Create(name: PChar); end;
初始化house对象时,将给构造函数的名称复制到私有字段FName中。将其定义为固定大小的数组是有原因的。
在内存中,将有一些与房屋分配相关的开销,我将在下面举例说明:
---[ttttNNNNNNNNNN]--- ^ ^ | | | +- the FName array | +- overhead
" tttt"区域是开销,对于各种类型的运行时和语言,例如8或者12个字节,通常会有更多的开销。至关重要的是,除内存分配器或者核心系统例程外,任何存储在该区域中的值都不得更改,否则我们有崩溃程序的风险。
分配内存
找一个企业家来盖房子,然后给你住所的地址。与现实世界相反,不能告诉内存分配在哪里分配,而是会找到一个有足够空间的合适位置,并将地址报告给分配的内存。
换句话说,企业家将选择地点。
THouse.Create('My house');
内存布局:
---[ttttNNNNNNNNNN]--- 1234My house
保留地址变量
把地址写到你的新房子里,写在纸上。本文将作为我们房屋的参考。没有这张纸,除非我们已经在里面,否则我们会迷失方向,也找不到房子。
var h: THouse; begin h := THouse.Create('My house'); ...
内存布局:
h v ---[ttttNNNNNNNNNN]--- 1234My house
复制指针值
只需将地址写在一张新纸上。现在,我们有两张纸可以将我们带到同一个房子,而不是两个单独的房子。任何试图从一个纸上的地址开始,然后重新布置该房屋的家具的尝试,都会使另一房屋看起来已经以相同的方式进行了修改,除非我们可以明确地发现它实际上只是一所房屋。
注意这通常是我向人们解释最多的问题,两个指针并不意味着两个对象或者内存块。
var h1, h2: THouse; begin h1 := THouse.Create('My house'); h2 := h1; // copies the address, not the house ...
h1 v ---[ttttNNNNNNNNNN]--- 1234My house ^ h2
释放内存
拆房子。然后,我们以后可以根据需要将纸张重新用于新的地址,也可以清除该文件以忘记该地址的地址不再存在。
var h: THouse; begin h := THouse.Create('My house'); ... h.Free; h := nil;
在这里,我首先建造房屋,并掌握其地址。然后我对房子做一些事情(使用它,...代码,作为练习留给读者使用),然后释放它。最后,我从变量中清除地址。
内存布局:
h <--+ v +- before free ---[ttttNNNNNNNNNN]--- | 1234My house <--+ h (now points nowhere) <--+ +- after free ---------------------- | (note, memory might still xx34My house <--+ contain some data)
悬空指针
我们告诉企业家要摧毁房屋,但我们忘记从纸上擦除地址。当我们稍后看纸时,我们已经忘了那所房子不再存在了,而是去参观它,结果失败了(另请参见下面有关无效引用的部分)。
var h: THouse; begin h := THouse.Create('My house'); ... h.Free; ... // forgot to clear h here h.OpenFrontDoor; // will most likely fail
在调用.Free
之后使用h
可能有效,但这纯粹是运气。在关键操作过程中,它很可能会在客户位置失败。
h <--+ v +- before free ---[ttttNNNNNNNNNN]--- | 1234My house <--+ h <--+ v +- after free ---------------------- | xx34My house <--+
如我们所见,h仍指向内存中数据的剩余部分,但是
由于它可能不完整,因此像以前一样使用它可能会失败。
内存泄漏
我们丢了张纸,找不到房子。不过这所房子仍然屹立在某个地方,当我们以后要建造一所新房子时,我们将无法再使用该位置。
var h: THouse; begin h := THouse.Create('My house'); h := THouse.Create('My house'); // uh-oh, what happened to our first house? ... h.Free; h := nil;
在这里,我们用新房子的地址覆盖了h
变量的内容,但是旧房子仍然屹立在某处。编写完此代码后,我们将无法到达那所房子,它将被搁置。换句话说,分配的内存将保持分配状态,直到应用程序关闭为止,这时操作系统会将其拆除。
第一次分配后的内存布局:
h v ---[ttttNNNNNNNNNN]--- 1234My house
第二次分配后的内存布局:
h v ---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN] 1234My house 5678My house
获取此方法的更常见方法是忘记释放某些内容,而不是像上面那样覆盖它。用Delphi术语,这将通过以下方法发生:
procedure OpenTheFrontDoorOfANewHouse; var h: THouse; begin h := THouse.Create('My house'); h.OpenFrontDoor; // uh-oh, no .Free here, where does the address go? end;
执行完此方法后,在我们的变量中没有房屋的地址存在的地方,但是房屋仍然在那里。
内存布局:
h <--+ v +- before losing pointer ---[ttttNNNNNNNNNN]--- | 1234My house <--+ h (now points nowhere) <--+ +- after losing pointer ---[ttttNNNNNNNNNN]--- | 1234My house <--+
如我们所见,旧数据保留在内存中,并且不会
由内存分配器重用。分配器跟踪哪个
内存区域已被使用,除非我们
释放它。
释放内存,但保留一个(现在无效)引用
拆除房屋,擦除其中一张纸,但是我们还有另一张上面有旧地址的纸,当我们到达该地址时,找不到房屋,但是我们可能会发现类似于废墟的东西一。
也许我们甚至会找到一所房子,但它并不是我们最初指定的住所,因此任何试图使用它(好像它属于我们)的尝试都可能会失败。
有时,我们甚至可能会发现相邻地址上建有一个相当大的房子,该房子占据了三个地址(大街1-3号),而地址移到了房子的中间。任何将三地址大房子中的那部分视为单个小房子的尝试都可能会失败。
var h1, h2: THouse; begin h1 := THouse.Create('My house'); h2 := h1; // copies the address, not the house ... h1.Free; h1 := nil; h2.OpenFrontDoor; // uh-oh, what happened to our house?
通过" h1"中的引用,这里的房屋被拆了,虽然" h1"也被清除了,但" h2"仍然有旧的过时地址。进入不再站立的房屋可能会或者可能不会起作用。
这是上面悬空指针的变体。查看其内存布局。
缓冲区溢出
我们将更多的东西搬进房子,超出了承受能力,溢出到邻居的房子或者院子里。当隔壁房子的主人后来回家时,他会发现各种各样的东西,他会考虑自己的。
这就是我选择固定大小的数组的原因。为了做好准备,假设
由于某种原因,我们分配的第二座房屋将被安置在
内存中的第一个。换句话说,第二座房屋的高度会降低
地址比第一个。而且,它们彼此相邻分配。
因此,此代码:
var h1, h2: THouse; begin h1 := THouse.Create('My house'); h2 := THouse.Create('My other house somewhere'); ^-----------------------^ longer than 10 characters 0123456789 <-- 10 characters
第一次分配后的内存布局:
h1 v -----------------------[ttttNNNNNNNNNN] 5678My house
第二次分配后的内存布局:
h2 h1 v v ---[ttttNNNNNNNNNN]----[ttttNNNNNNNNNN] 1234My other house somewhereouse ^---+--^ | +- overwritten
最常导致崩溃的部分是当我们覆盖重要部分时
我们存储的数据中,实际上不应随意更改的数据。例如
更改h1房屋名称的一部分可能不是问题,
在崩溃程序方面,但覆盖了程序的开销
当我们尝试使用损坏的对象时,该对象很可能会崩溃,
和覆盖存储到的链接一样
对象中的其他对象。
链表
当我们在一张纸上写一个地址时,我们会到达一所房子,在那座房子上还有另一张纸,上面有新的地址,用于链中的下一个房子,依此类推。
var h1, h2: THouse; begin h1 := THouse.Create('Home'); h2 := THouse.Create('Cabin'); h1.NextHouse := h2;
在这里,我们创建了从房屋到小屋的链接。我们可以按照链条进行操作,直到一所房子没有" NextHouse"参考,这意味着它是最后一个。要访问我们所有的房屋,我们可以使用以下代码:
var h1, h2: THouse; h: THouse; begin h1 := THouse.Create('Home'); h2 := THouse.Create('Cabin'); h1.NextHouse := h2; ... h := h1; while h <> nil do begin h.LockAllDoors; h.CloseAllWindows; h := h.NextHouse; end;
内存布局(将NextHouse添加为对象中的链接,标记为
下图中的四个LLLL):
h1 h2 v v ---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL] 1234Home + 5678Cabin + | ^ | +--------+ * (no link)
从根本上讲,什么是内存地址?
从基本的角度来说,内存地址只是一个数字。如果你想起记忆
作为一个大字节数组,第一个字节的地址为0,下一个字节
地址1,依此类推。这是简化的,但足够好。
所以这个内存布局:
h1 h2 v v ---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN] 1234My house 5678My house
可能有这两个地址(最左边是地址0):
- h1 = 4
- h2 = 23
这意味着我们上面的链接列表实际上可能是这样的:
h1 (=4) h2 (=28) v v ---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL] 1234Home 0028 5678Cabin 0000 | ^ | +--------+ * (no link)
通常将"无处指向"的地址存储为零地址。
从根本上讲,什么是指针?
指针只是一个保存内存地址的变量。我们通常可以要求编程
语言来提供编号,但是大多数编程语言和运行时都试图
隐藏数字下面没有数字的事实,只是因为数字本身没有
真的对我们没有任何意义。最好将指针视为黑匣子,即。
我们并不真正知道或者关心它的实际实现方式,只要它
作品。
回答
一个带有一组图表的教程示例可以极大地理解指针。
乔尔·斯波斯基(Joel Spolsky)在其《游击面试指南》中提出了一些有关理解指针的观点:
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.
回答
在我的第一个Comp Sci课上,我们做了以下练习。当然,这是一个演讲厅,里面大约有200名学生...
教授在黑板上写道:" int john;"
约翰站起来
教授写道:" int * sally =&john;"
莎莉站起来,指着约翰
教授:int * bill = sally;
比尔站起来,指着约翰
教授:int sam;
山姆站起来
教授:bill =&sam;
比尔现在指向山姆。
我想你应该已经明白了。我认为我们花了大约一个小时来完成这项工作,直到我们了解了指针分配的基础知识为止。
回答
指针的复杂性超出了我们可以轻松教导的范围。让学生互相指点并在纸上写上带有住所的地址都是很好的学习工具。他们在介绍基本概念方面做得很好。确实,学习基本概念对于成功使用指针至关重要。但是,在生产代码中,进入比这些简单演示所封装的复杂得多的场景是很常见的。
我参与的系统中,我们的结构指向其他结构,而其他结构指向其他结构。这些结构中的一些还包含嵌入式结构(而不是指向其他结构的指针)。这是指针真正令人困惑的地方。如果我们有多个间接级别,那么我们将以如下代码开始结尾:
widget->wazzle.fizzle = fazzle.foozle->wazzle;
它会很快变得令人迷惑(想象更多的行,并可能会有更多的关卡)。放入指针数组和节点到节点指针(树,链接列表)的数组,情况还会变得更糟。我已经看到一些真正优秀的开发人员一旦开始在这样的系统上工作,他们就会迷失方向,甚至那些非常了解基础知识的开发人员也是如此。
指针的复杂结构也不一定表示编码不好(尽管它们可以)。组合是良好的面向对象编程的重要组成部分,在带有原始指针的语言中,组合不可避免地会导致多层间接。此外,系统经常需要使用第三方库,这些库的结构在样式或者技术上都不匹配。在这种情况下,自然会出现复杂性(尽管可以肯定,我们应该尽可能地与之抗争)。
我认为大学可以帮助学生学习指针的最好的方法就是结合使用良好的演示和需要使用指针的项目。一个困难的项目对指针的理解将比一千个示范做更多的事情。演示可以使我们有一个浅浅的理解,但是要深刻掌握指针,我们必须真正使用它们。
回答
我不认为指针本身会造成混淆。大多数人都可以理解这个概念。现在我们可以考虑多少个指针,或者我们对多少个间接级别感到满意。让人们处于优势地位并不需要太多。它们可能会被程序中的错误意外更改,这也使它们在代码出错时很难调试。
回答
我发现Ted Jensen的" C语言中的指针和数组教程"是学习指针的绝佳资源。它分为10节课,首先说明什么是指针(以及它们的作用),最后以函数指针结束。 http://home.netcom.com/~tjensen/ptr/cpoint.htm
从那里继续前进,Beej的《网络编程指南》讲授Unix套接字API,我们可以从中开始做一些真正有趣的事情。 http://beej.us/guide/bgnet/
回答
我喜欢房子地址的类比,但我一直认为地址是邮箱本身。这样,我们可以形象化取消引用指针(打开邮箱)的概念。
例如下面的链接列表:
1)从论文开始,并写上地址
2)转到纸上的地址
3)打开邮箱,找到一张新纸,上面有下一个地址
在线性链接列表中,最后一个邮箱(列表结尾)中没有任何内容。在循环链接列表中,最后一个邮箱具有第一个邮箱的地址。
请注意,第3步是发生取消引用的地方,地址无效时我们将崩溃或者出错。假设我们可以走到无效地址的邮箱,想象一下那里有一个黑洞或者东西,这会使世界变得反过来:)
回答
我发现一个有助于解释指针的类比是超链接。大多数人都可以理解,网页上的链接"指向"互联网上的另一页,如果我们可以复制并粘贴该超链接,则它们都将指向同一原始网页。如果我们去编辑该原始页面,然后单击这些链接(指针)中的任何一个,我们将获得该新的更新页面。
回答
只是为了使事情更加混乱,有时我们必须使用句柄而不是指针。句柄是指向指针的指针,以便后端可以移动内存中的内容以对堆进行碎片整理。如果指针在例行程序中更改,则结果是不可预测的,因此我们首先必须锁定该句柄以确保任何地方都不会移动。
http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5比我讲得更连贯。 :-)
回答
我认为使指针学习起来很棘手的是,直到我们对指针感到满意为止,即"在此存储位置处是一组表示int,double,character等的位"。
当我们第一次看到指针时,我们并没有真正获得该内存位置的内容。 "你是什么意思,它有一个地址?"
我不同意"或者得到或者不得到"的观念。
当我们开始找到它们的实际用途时(例如不将大型结构传递给函数),它们将变得更易于理解。
回答
邮政信箱号。
这是一条信息,可让我们访问其他内容。
(如果我们对邮政信箱号码进行算术运算,则可能会遇到问题,因为字母放在错误的信箱中。如果有人移动到另一种状态-没有转发地址-那么我们将有一个悬空指针。 -如果邮局转发邮件,则我们有一个指向指针的指针。)
回答
指针的问题不是这个概念。它涉及执行和语言。当教师认为困难的是指针的概念,而不是行话,或者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",以及它打算做什么。该语言不支持在实践中遇到的嵌套级别。就像我必须向创建新缓冲区的函数中传递"指针"一样,它会修改指针以指向缓冲区的新位置。它是否确实需要该指针或者指向该指针的指针,因此它知道去哪里可以修改指针的内容。大多数时候,我只需要猜测"指针"的含义,大多数时候我就错了,不管我在猜测中获得了多少经验。
"指针"太重载了。指针是指向值的地址吗?还是将地址保存为值的变量。当一个函数需要一个指针时,它是想要该指针变量保存的地址,还是想要该指针变量的地址?
我很困惑。
回答
我认为这实际上可能是语法问题。指针的C / C ++语法似乎不一致,并且比所需的语法更复杂。
具有讽刺意味的是,实际上帮助我理解指针的一件事是在c ++标准模板库中遇到了迭代器的概念。具有讽刺意味的是,因为我只能假设迭代器被认为是指针的一般化。
有时,直到学会忽略树木,我们才看不到森林。
回答
通过迭代器来掌握它不是一个坏方法。但是继续看下去,我们会看到Alexandrescu开始抱怨它们。
许多前C ++开发人员(在转储语言之前从未理解过迭代器是现代的指针)跳到Cand仍然相信他们有不错的迭代器。
嗯,问题在于所有迭代器都与运行时平台(Java / CLR)试图达到的目标完全相悖:新的,简单的,每个人都是开发人员的用法。可能不错,但是他们在紫色书中曾经说过,甚至在C之前和之前都说过:
间接的。
这是一个非常强大的概念,但是如果我们始终这样做,则永远不会如此。.迭代器非常有用,因为它们有助于抽象算法,这是另一个示例。编译时是算法的地方,非常简单。我们知道代码+数据,或者使用其他C#语言:
IEnumerable + LINQ + Massive Framework = 300MB糟糕的运行时间接代价,通过引用类型的实例堆拖动应用程序。
"勒珀特很便宜。"
回答
它之所以难以理解的原因不是因为它是一个困难的概念,而是因为语法不一致。
int *mypointer;
我们首先了解到变量创建的最左边部分定义了变量的类型。指针声明在C和C ++中不能像这样工作。相反,他们说该变量指向左侧的类型。在这种情况下:*
mypointer指向一个int。
直到我尝试在C中使用指针(不安全)时,我才完全掌握指针,它们的工作方式完全相同,但语法和逻辑一致。指针本身就是类型。这里mypointer是一个指向int的指针。
int* mypointer;
甚至不让我开始使用函数指针...
回答
我只知道C ++时可以使用指针。我有点知道在某些情况下该做什么,而试行/错误该不该做什么。但是让我完全理解的是汇编语言。如果我们对编写的汇编语言程序进行了认真的指令级调试,则我们应该能够理解很多内容。
回答
混淆来自在"指针"概念中混合在一起的多个抽象层。程序员不会被Java / Python中的普通引用所迷惑,但是指针的不同之处在于它们公开了底层内存体系结构的特征。
干净地分离抽象层是一个好原则,而指针则不能这样做。
回答
首先,我很难理解指针的原因是,许多解释都包含很多关于引用传递的废话。所有这一切都使这个问题感到困惑。当使用指针参数时,我们仍在按值传递。但是该值恰好是地址而不是int。
其他人已经链接到本教程,但是我可以重点介绍我开始理解指针的时刻:
C语言中的指针和数组教程:第3章指针和字符串
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或者Cdeveloper(就我而言)并且从不使用不安全的代码,仍然值得了解指针的工作方式,否则我们将不了解对象引用的工作方式。然后,我们将拥有一个常见但容易出错的概念,即将对象引用传递给方法会复制该对象。
回答
我认为理解指针的主要障碍是糟糕的老师。
几乎每个人都被教导关于指针的谎言:它们不过是内存地址,或者它们使我们可以指向任意位置。
当然,它们很难理解,危险和半魔术。
都不是真的。指针实际上是相当简单的概念,只要我们坚持使用C ++语言必须对它们说的话,并且不要给它们赋予"通常"可以在实践中使用的属性,但是该语言并不能保证这些属性,因此也不属于指针的实际概念。
几个月前,我试图在博客中写出对此的解释-希望它能对某人有所帮助。
(请注意,在任何人都对我不屑一顾之前,是的,C ++标准确实说过指针代表内存地址。但是它并没有说"指针是内存地址,除了内存地址外什么都没有,并且可以与内存互换使用或者想到"地址"。区别很重要)