【问题标题】:In C++ I Cannot Grasp Pointers and Classes在 C++ 中我无法掌握指针和类
【发布时间】:2010-09-10 21:51:01
【问题描述】:

我刚从大学毕业,已经使用 C++ 工作了一段时间。我了解并使用 C++ 的所有基础知识,但我很难掌握更高级的主题,例如指针和类。我已经阅读了一些书籍和教程,并且理解了其中的示例,但是当我查看一些高级现实生活示例时,我无法弄清楚它们。这让我很生气,因为我觉得它让我无法将我的 C++ 编程提升到一个新的水平。其他人有这个问题吗?如果是这样,你是如何突破的? 有谁知道任何真正描述指针和类概念的书籍或教程? 或者也许是一些使用高级指针和类技术的具有良好描述性 cmets 的示例代码? 任何帮助将不胜感激。

【问题讨论】:

  • 出于好奇,您在大学期间学习了哪些语言?除了纯函数式语言,我想不出任何没有指针或类的语言。
  • 仅供参考,指针和类是 C++ 的基础。
  • P.S. C++ 是一种非常丰富和广泛的语言。如果您无法理解高级示例,那并不一定意味着您缺少某些东西(“高级” C++ 兔子洞非常非常深)。不过,如果您提供更具体的信息会有所帮助。
  • 在没有这些知识的情况下,你是如何使用 C++ 工作一段时间的......?没有类的 C++ 听起来像 C,而没有指针的 C 听起来像嗯.....
  • 让我猜猜——您正在编写计算数学代码,而您对指针的缺乏理解妨碍了多维数据的处理。关闭?

标签: c++ class pointers


【解决方案1】:

指针和类在 C++ 中并不是真正的高级主题。它们非常基础。

对我来说,当我开始用箭头绘制方框时,指针就凝固了。为 int 画一个框。 int* 现在是一个单独的盒子,带有一个指向 int 盒子的箭头。

所以:

int foo = 3;           // integer
int* bar = &foo;       // assigns the address of foo to my pointer bar

使用我的指针框(栏),我可以选择查看框内的地址。 (这是 foo 的内存地址)。或者我可以操纵我有地址的任何东西。这种操作意味着我沿着那个箭头指向整数(foo)。

*bar = 5;  // asterix means "dereference" (follow the arrow), foo is now 5
bar = 0;   // I just changed the address that bar points to

课程完全是另一个话题。有一些关于面向对象设计的书籍,但我不知道适合初学者的好书。一本介绍 Java 的书可能会给您带来好运。

【讨论】:

  • 嗯,它们对我来说是高级主题,哈哈,我理解你的例子,但是当我试图理解别人的代码时,我无法弄清楚......也许我只是需要更多的经验。 ..感谢您的回复。
  • 我支持这个。对于初学者,总是将“指针”翻译为“地址”,将“取消引用”翻译为“前往地址”,就像开车一样。指向指针的指针变成了转发的邮政信箱——当你去那里时,你会找到另一个地址而不是存储位置。
  • 可能很多代码写得不太好。我曾经花费大量时间试图弄清楚一些指针繁重的代码是如何工作的。我放弃了,请教了上级。他看了两秒钟说:“嗯。那会导致崩溃的。最好修复它。”经验:能够看着一个标本,看到它做了一些奇怪的事情,并认为“这有问题”而不是“我有问题”。
【解决方案2】:

理解 C/C++ 中的指针

在了解指针的工作原理之前,有必要了解变量在程序中是如何存储和访问的。每个变量都有 2 个部分 - (1) 存储数据的内存地址和 (2) 存储数据的值。

内存地址通常称为变量的左值,存储的数据的值称为右值(l和r的意思是左右)。

考虑以下语句:

int x = 10;

在内部,程序将内存地址与变量 x 相关联。在这种情况下,让我们假设程序将 x 分配到地址 1001(不是实际地址,但为简单起见选择)。所以x的左值(内存地址)是1001,x的右值(数据值)是10。

通过简单地使用变量“x”来访问右值。为了访问左值,需要“地址”运算符('&')。表达式‘&x’读作“x的地址”。

Expression          Value
----------------------------------
x                   10
&x                  1001

x 中存储的值可以随时更改(例如 x = 20),但 x (&x) 的地址永远不能更改。

指针只是一个变量,可以用来修改另一个变量。它通过为其右值设置一个内存地址来做到这一点。也就是说,它指向内存中的另一个位置。

创建指向“x”的指针如下:

int* xptr = &x;

“int*”告诉编译器我们正在创建一个指向整数值的指针。 “= &x”部分告诉编译器我们将 x 的地址分配给 xptr 的右值。因此,我们告诉编译器 xptr “指向” x。

假设xptr被分配到1002的内存地址,那么程序的内存可能是这样的:

Variable    lvalue    rvalue
--------------------------------------------
x           1001      10   
xptr        1002      1001

下一个难题是“间接运算符”('*'),其用法如下:

int y = *xptr;

间接运算符告诉程序将 xptr 的右值解释为内存地址而不是数据值。即程序查找存储在xptr(1001)提供的地址处的数据值(10)。

把它们放在一起:

Expression      Value
--------------------------------------------
x                   10
&x                  1001
xptr                1001
&xptr               1002
*xptr               10

现在已经解释了这些概念,下面是一些代码来演示指针的强大功能:

int x = 10;
int *xptr = &x;

printf("x = %d\n", x);
printf("&x = %d\n", &x);        
printf("xptr = %d\n", xptr);
printf("*xptr = %d\n", *xptr);

*xptr = 20;

printf("x = %d\n", x);
printf("*xptr = %d\n", *xptr);

对于你会看到的输出(注意:内存地址每次都会不同):

x = 10
&x = 3537176
xptr = 3537176
*xptr = 10
x = 20
*xptr = 20

注意给‘*xptr’赋值如何改变‘x’的值。这是因为“*xptr”和“x”指的是内存中的相同位置,正如“&x”和“xptr”具有相同的值所证明的那样。

【讨论】:

  • 左值和右值与内存地址无关。
  • 我对 b/c 的输出感到困惑,你说 x = 10,但接下来 3 行你说 x = 20?这个变量 x 不能同时是两个值对吗?
  • @RobertHoughton,他通过取消引用 xptr 并将 20 分配给 *xptr 将 20 分配给 x。
【解决方案3】:

This link 有一个视频,描述了指针如何工作,并带有粘土化。内容丰富,易于消化。

This page 有一些关于类基础的​​很好的信息。

【讨论】:

  • 任何有粘土化的东西都会在我的书中获得 +1。
【解决方案4】:

我曾经在理解 pascal 中的指针时遇到问题 :) 一旦我开始做汇编程序指针,它确实是访问内存的唯一方法,它就击中了我。这听起来可能有点遥远,但尝试汇编程序(尝试了解计算机的真正含义总是一个好主意)可能会教你一些指导。课程——我不明白你的问题——你的学校教育是纯结构化编程吗?类只是查看现实生活模型的一种合乎逻辑的方式 - 您正在尝试解决一个可以概括为多个对象/类的问题。

【讨论】:

    【解决方案5】:

    指针和类是完全不同的主题,所以我不会像这样把它们混为一谈。在这两者中,我会说指针更为基础。

    了解什么是指针的一个很好的练习如下:

    1. 创建一个链表
    2. 从头到尾遍历它
    3. 反转它,使头部现在是背部,背部现在是头部

    首先在白板上完成所有操作。如果您可以轻松地做到这一点,那么您在理解什么是指针时应该不会有任何问题。

    【讨论】:

      【解决方案6】:

      其他答案中似乎已经提到了指针(不是双关语)。

      类是面向对象的基础。我在 OO 中遇到了巨大的麻烦——就像十年的失败尝试一样。最终帮助我的书是 Craig Larman 的“应用 UML 和模式”。我知道这听起来好像是关于一些不同的东西,但它确实很好地让你进入了类和对象的世界。

      【讨论】:

        【解决方案7】:

        我们只是在午餐时讨论了 C++ 和 OO 的一些方面,有人(实际上是一位伟大的工程师)说除非你在学习 C++ 之前有非常强大的编程背景,否则它会毁了你。

        我强烈建议先学习另一种语言,然后在需要时转向 C++。指针并没有什么了不起的地方,它们只是当编译器在没有它们的情况下很难将操作有效地转换为汇编时遗留下来的。

        现在如果编译器不能更好地优化数组操作,那么你可以使用指针,你的编译器就坏了。

        请不要误会我的意思,我并不是说 C++ 很糟糕或其他任何东西,也不想开始倡导性讨论,我已经使用它并且现在偶尔使用它,我只是建议你开始用别的东西。

        这真的不像学习驾驶手动汽车然后轻松地将其应用于自动驾驶汽车,它更像是学习驾驶其中一台巨大的建筑起重机然后假设当你开始驾驶汽车时会适用 - - 然后你会发现自己在开着应急灯的情况下以每小时 5 英里的速度开着你的车。

        [edit] 回顾最后一段——我认为这可能是我有史以来最准确的类比!

        【讨论】:

        • 您建议先学习哪些语言? python会是一个不错的选择吗?我目前正在尝试学习 php 和 c++,它们的语法非常相似。
        • C# 和 Java 很好,因为它们经过编译时检查,老实说,这是一个非常好的习惯。如果你只想闲逛,Python 很好。如果你在做一个网站,Ruby on Rails——句号。
        • 哦,如果你真的想学习前沿编程概念,看看 Scala。老实说,这可能是最好的,因为它让您立即了解所有概念。 Hascal 和 Lisp 也以教授良好的编程概念而闻名。
        【解决方案8】:

        为了理解指针,我不能高度推荐K&R这本书。

        【讨论】:

          【解决方案9】:

          Donald Alcock 的《Illustrating Ansi C》为我提供了指导。它充满了手绘风格的方框图和箭头图,说明了指针、指针算术、数组、字符串函数等......

          显然它是一本“C”类书籍,但核心基础知识很难被击败

          【讨论】:

            【解决方案10】:

            来自 lassevek 对similar question on SO 的回复:

            指针对于许多人来说是一个概念 起初可能会令人困惑,在 特别是在复制时 周围的指针值仍然 引用同一个内存块。

            我发现最好的比喻是 将指针视为一个 写有住址的纸,以及 它引用的内存块 实际的房子。各种操作 因此可以很容易地解释:

            • 复制指针值,把地址写在新纸上
            • 链接列表,房子里的一张纸,上面有下一个地址 房子在上面
            • 释放内存,拆房子,抹掉地址
            • 内存泄漏,纸丢了找不到房子
            • 释放内存但保留一个(现在无效的)引用,拆除 房子,抹去一块 纸,但有另一张纸 上面有旧地址,当你 去地址,你不会找到 房子,但你可能会找到一些东西 类似于一个人的废墟
            • 缓冲区溢出,你搬进房子的东西比你能多 可能适合,溢出到 邻居家

            【讨论】:

              【解决方案11】:

              对于指针:

              我发现this post 对指针进行了非常深思熟虑的讨论。也许这会有所帮助。你熟悉 C# 中的引用吗?那实际上是别的东西吗?这可能是理解指针的良好开端。

              另外,请查看下面 Kent Fredric 的帖子,了解另一种向指针介绍自己的方式。

              【讨论】:

                【解决方案12】:

                学习汇编语言,然后学习 C。然后你就会知道机器的基本原理是什么(以及前面的指针)。

                指针和类是 C++ 的基本方面。如果你不理解它们,那意味着你并不真正理解 C++。

                就我个人而言,几年来我一直对 C++ 持保留态度,直到我觉得自己牢牢掌握了 C 以及汇编语言背后发生的事情。虽然这是很久以前的事了,但现在我认为了解计算机在低级别的工作原理对我的职业生涯真的很有帮助。

                学习编程可能需要很多年,但您应该坚持下去,因为这是一个非常有回报的职业。

                【讨论】:

                  【解决方案13】:

                  对于指针和类,这是我的类比。我会用一副纸牌。一副牌有面值和类型(红桃 9、黑桃 4 等)。因此,在我们类似 C++ 的“Deck of Cards”编程语言中,我们会说以下内容:

                  HeartCard card = 4; // 4 of hearts!
                  

                  现在,您知道红桃 4 的位置了,因为天哪,您拿着牌,正面朝上,牌在顶部!所以关于其余的牌,我们只会说红心 4 是在 BEGINNING。所以,如果我在开始时问你是什么牌,你会说,“当然是红心 4!”。好吧,你只是“指向”我卡片所在的位置。在我们的“Deck of Cards”编程语言中,您也可以这样说:

                  HeartCard card = 4; // 4 of hearts!
                  print &card // the address is BEGINNING!
                  

                  现在,把你的牌翻过来。背面现在开始,你不知道卡是什么。但是,假设您可以随心所欲地制作它,因为您充满了魔力。让我们用我们的“纸牌”语言来做这件事!

                  HeartCard *pointerToCard = MakeMyCard( "10 of hearts" );
                  print pointerToCard // the value of this is BEGINNING!
                  print *pointerToCard // this will be 10 of hearts!
                  

                  好吧,MakeMyCard(“红桃 10”)是您在施展魔法,并且知道您想指向 BEGINNING,从而使卡片成为红桃 10!你把你的卡翻过来,瞧!现在,* 可能会让你失望。如果是这样,请查看:

                  HeartCard *pointerToCard = MakeMyCard( "10 of hearts" );
                  HeartCard card = 4; // 4 of hearts!
                  print *pointerToCard; // prints 10 of hearts
                  print pointerToCard; // prints BEGINNING
                  print card; // prints 4 of hearts
                  print &card; // prints END - the 4 of hearts used to be on top but we flipped over the deck!
                  

                  至于类,我们在示例中通过将类型定义为 HeartCard 来使用类。我们知道 HeartCard 是什么……这是一张具有价值和心型的卡片!因此,我们将其归类为 HeartCard。每种语言都有类似的方式来定义或“分类”你想要的东西,但它们都有相同的概念!希望这会有所帮助...

                  【讨论】:

                  • 这是一个相当糟糕的类比。
                  【解决方案14】:

                  在类的情况下,我掌握了三种真正帮助我进入真正的面向对象编程的技术。

                  第一个是我在一个游戏项目中大量使用类和对象,大量使用泛化(一种或是一种关系,例如学生是一种人)和组合(has-关系,例如学生有学生贷款)。分解这段代码需要做很多工作,但确实让事情变得清晰。

                  帮助我的第二件事是在我的系统分析课上,我必须在其中制作 http://www.agilemodeling.com/artifacts/classDiagram.htm">UML 类图。我刚刚发现的这些帮助我理解了程序中的类结构。

                  最后,我帮助我大学的学生辅导编程。关于这一点,我真正能说的是,通过教学和了解其他人解决问题的方法,你会学到很多东西。很多时候,学生会尝试一些我从未想过但通常很有意义的事情,但他们只是在实施他们的想法时遇到了问题。

                  我最好的忠告是,它需要大量的练习,而且你编程的越多,你就越能理解它。

                  【讨论】:

                    【解决方案15】:

                    在某种意义上,您可以将“指针”视为软件中两种最基本的类型之一——另一种是“值”(或“数据”)——它们存在于一大块可唯一寻址的内存位置中.想想看。对象和结构等并不真正存在于内存中,只有值和指针存在。事实上,指针也是一个值……内存地址的值,它又包含另一个值……等等。

                    因此,在 C/C++ 中,当您声明一个“int”(intA) 时,您定义了一个包含一个值的 32 位内存块 - 一个数字。如果您随后声明一个“int 指针”(intB),您将定义一个包含 int 地址的 32 位内存块。我可以通过声明“intB = &intA”将后者指定为指向前者,现在定义为 intB 的 32 位内存包含与 intA 在内存中的位置相对应的地址。

                    当您“取消引用”intB 指针时,您正在查看存储在 intB 内存中的地址,找到该位置,然后查看存储在那里的值(一个数字)。

                    通常情况下,当人们使用“&”、“*”和“->”运算符时,他们不知道他们正在处理的确切内容是什么——它是地址、值还是什么? ?您只需要继续关注内存地址只是位置的事实,而值是存储在那里的二进制信息。

                    【讨论】:

                      【解决方案16】:

                      练习是无可替代的。

                      通读一本书或听一场讲座很容易,感觉就像在关注正在发生的事情。

                      我的建议是获取一些代码示例(我假设您将它们放在磁盘上的某个地方),编译并运行它们,然后尝试更改它们以执行不同的操作。

                      • 向层次结构中添加另一个子类
                      • 向现有类添加方法
                      • 更改迭代算法 转发通过一个集合去 而是向后。

                      我认为没有什么“灵丹妙药”的书可以做到这一点。

                      对我来说,指针的含义是在汇编中工作,并且看到指针实际上只是一个地址,并且拥有指针并不意味着它指向的是一个有意义的对象。

                      【讨论】:

                        【解决方案17】:

                        我读过的关于这些主题的最好的书是 Bruce Eckel 的 Thinking in C++。可以here免费下载。

                        【讨论】:

                          【解决方案18】:

                          对于课程:

                          对我来说,突破的时刻是我了解了接口。抽象出你如何解决问题的细节,并仅提供与类交互的方法列表的想法非常有见地。

                          事实上,我的教授明确告诉我们,他会通过将我们的课程插入到他的测试工具中来对我们的程序进行评分。评分会根据他给我们的要求和程序是否崩溃来进行。

                          长话短说,类可以让您封装功能并以更简洁的方式调用它(大多数时候,总会有例外)

                          【讨论】:

                          • 测试工具是执行另一组代码的代码。我的教授基本上有一个在我们班上调用函数的主程序。他会运行测试,标记成绩,删除您的文件,然后在他的解决方案中插入下一个学生文件并重复整个过程。
                          【解决方案19】:

                          真正帮助我理解这些概念的一件事是学习 UML - 统一建模语言。以图形格式查看面向对象设计的概念确实帮助我了解了它们的含义。有时仅仅通过查看实现它们的源代码来试图理解这些概念可能很难理解。

                          以图形形式查看诸如继承之类的面向对象范式是掌握这一概念的一种非常有效的方法。

                          Martin Fowler 的 UML Distilled 是一个很好的简短介绍。

                          【讨论】:

                            【解决方案20】:

                            假装一个指针是一个数组地址。

                            x = 500; // memory address for hello;
                            MEMORY[x] = "hello"; 
                            print  MEMORY[x]; 
                            

                            这是一个过度简化的图形,但在大多数情况下,只要您不想知道该数字是什么或手动设置它就可以了。

                            当我理解 C 时,我有一些宏,它们或多或少地允许您使用指针,就像它们内存中的数组索引一样。但是我早已丢失了该代码并且早已忘记了。

                            我记得它是从

                            开始的
                            #define MEMORY 0; 
                            #define MEMORYADDRESS( a ) *a;
                            

                            而这本身几乎没有用。希望其他人可以扩展该逻辑。

                            【讨论】:

                            • 我认为这是一个非常有用的 excersize。在很多嵌入式系统中,我没有指向分配给我的东西的指针,我有一个数组索引:)。
                            • 对于那些因为这篇文章而贬低我的人,它有效。我用过它,我帮助过别人。操作系统为您抽象出来的事实对练习并不重要。
                            • 是的,我不明白,我认为这是理解指针的好方法。数组比指针更容易理解,IMO,所以我认为这是一个很好的起点..
                            【解决方案21】:

                            类比较容易掌握; OOP 可能需要你很多年。就个人而言,直到去年我才完全掌握真正的 OOP。 Smalltalk 在大学里没有应有的普及,这太糟糕了。它确实使人们明白 OOP 是关于对象交易消息的,而不是类是具有函数的自包含全局变量。

                            如果您真的不熟悉课程,那么这个概念可能需要一段时间才能掌握。当我在 10 年级第一次遇到他们时,直到有人知道他们在做什么,我才明白他们在做什么,并解释了发生了什么。这就是我建议你尝试的方法。

                            【讨论】:

                              【解决方案22】:

                              我认为,为了更好地理解指针,看看汇编语言如何处理指针可能会很有用。指针的概念实际上是汇编语言和 x86 处理器指令体系结构的基本部分之一。也许它会让你觉得指针是程序的自然组成部分。

                              关于类,除了 OO 范式之外,我认为从低级二进制的角度来看类可能会很有趣。在这方面,它们在基本层面上并没有那么复杂。

                              如果您想更好地了解 C++ 对象模型下的内容,可以阅读Inside the C++ Object Model

                              【讨论】:

                                【解决方案23】:

                                我真正得到指导的是在 FatMac 上编写 TurboPascal(大约在 1984 年左右)——这是当时的原生 Mac 语言。

                                Mac 有一个奇怪的内存模型,当分配地址时,内存存储在堆上的指针中,但它本身的位置并不能保证,而是内存处理例程返回一个指向指针的指针 - 引用作为把手。因此,要访问已分配内存的任何部分,必须取消对句柄的引用两次。花了一段时间,但不断的练习最终将课程带回家。

                                Pascal 的指针处理比 C++ 更容易掌握,后者的语法对初学者没有帮助。如果您真的并且真正地坚持理解 C 中的指针,那么您最好的选择可能是获取一个 Pascal 编译器的副本并尝试在其中编写一些基本的指针代码(Pascal 与 C 足够接近,您将在几个小时内获得基础知识)。链表之类的将是一个不错的选择。一旦您对那些回归 C++ 并掌握了概念感到满意后,您会发现悬崖看起来不会那么陡峭。

                                【讨论】:

                                  【解决方案24】:

                                  您是否阅读过 Bjarne Stroustrup 的C++ 编程语言?他创建了 C++。

                                  C++ FAQ Lite 也不错。

                                  【讨论】:

                                    【解决方案25】:

                                    您可能会发现这个article by Joel 很有启发性。顺便说一句,如果你已经“在 C++ 中工作了一段时间”并且已经从 CS 毕业,那么你可能已经上过 JavaSchool(我认为你根本没有在 C++ 中工作;你已经一直在 C 中工作,但使用 C++ 编译器)。

                                    另外,为了支持 hojou 和 nsanders 的回答,指针对于 C++ 来说是非常基础的。如果您不了解指针,那么您就不会了解 C++ 的基础知识(顺便说一句,承认这一事实是了解 C++ 的开始)。同样,如果您不了解类,那么您就不会了解 C++(或 OO)的基础知识。

                                    对于指针,我认为用方框绘图是个好主意,但在汇编中工作也是个好主意。我认为,任何使用相对寻址的指令都会让你很快理解什么是指针。

                                    至于类(以及更普遍的面向对象编程),我推荐 Stroustrups "The C++ Programming Language" 最新版本。它不仅是规范的 C++ 参考资料,而且还包含很多其他方面的资料,从基本的面向对象的类层次结构和继承一直到大型系统中的设计原则。这是一本非常好的读物(如果不是有点厚和简洁的话)。

                                    【讨论】:

                                    • 大声笑,我刚刚阅读了 Joel 的那篇文章,这就是让我来这里寻找关于指针的问题的原因。
                                    【解决方案26】:

                                    指针不是什么神奇的东西,你一直在使用它们!
                                    当你说:

                                    int a;

                                    编译器为“a”生成存储空间,您实际上是在声明
                                    一个 int 并且您想将其内存位置命名为“a”。

                                    当你说:

                                    int *a;

                                    您正在声明一个可以保存 int 内存位置的变量。 就是这么简单。另外,不要害怕指针算术,只要总是 当您处理指针并思考时,请记住“内存映射” 遍历内存地址。

                                    C++ 中的类只是定义抽象数据类型的一种方式。我建议阅读一本好的 OOP 书籍来理解这个概念,然后,如果你有兴趣,可以学习 C++ 编译器如何生成代码来模拟 OOP。但是,如果您坚持使用 C++ 足够长的时间,这些知识会及时出现:)

                                    【讨论】:

                                      【解决方案27】:

                                      您的问题似乎是 C++ 中的 C 核心,而不是 C++ 本身。让自己成为 Kernighan & Ritchie(C 编程语言)。吸入它。这是非常好的东西,是有史以来最好的编程语言书籍之一。

                                      【讨论】:

                                        猜你喜欢
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2018-04-25
                                        • 2011-10-07
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2012-03-31
                                        • 2011-05-29
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多