8

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

4

27 回答 27

21

指针和类在 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 的书可能会让您很幸运。

于 2008-09-18T20:05:57.563 回答
20

理解 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' 具有相同的值所证明的那样。

于 2008-09-19T01:03:57.753 回答
8

这个链接有一个视频,描述了指针是如何工作的,使用粘土化。内容丰富,易于消化。

这个页面有一些关于类基础的​​很好的信息。

于 2008-09-18T20:05:23.507 回答
4

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

于 2008-09-18T20:03:24.043 回答
3

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

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

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

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

于 2008-09-18T20:04:45.490 回答
2

在其他答案中似乎已经解决了指针(没有双关语)。

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

于 2008-09-18T20:13:54.470 回答
2

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

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

如今,如果编译器不能更好地优化数组操作,那么您可以使用指针,那么您的编译器就会损坏。

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

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

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

于 2008-09-18T20:16:31.093 回答
1

对于指针和类,这是我的类比。我会用一副纸牌。一副牌有面值和类型(红桃 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。每种语言都有类似的方式来定义或“分类”你想要的东西,但它们都有相同的概念!希望这有帮助...

于 2008-09-18T20:59:04.470 回答
1

In a sense, you can consider "pointers" to be one of the two most fundamental types in software - the other being "values" (or "data") - that exist in a huge block of uniquely-addressable memory locations. Think about it. Objects and structs etc don't really exist in memory, only values and pointers do. In fact, a pointer is a value too....the value of a memory address, which in turn contains another value....and so on.

So, in C/C++, when you declare an "int" (intA), you are defining a 32bit chunk of memory that contains a value - a number. If you then declare an "int pointer" (intB), you are defining a 32bit chunk of memory that contains the address of an int. I can assign the latter to point to the former by stating "intB = &intA", and now the 32bits of memory defined as intB, contains an address corresponding to intA's location in memory.

When you "dereference" the intB pointer, you are looking at the address stored within intB's memory, finding that location, and then looking at the value stored there (a number).

Commonly, I have encountered confusion when people lose track of exactly what it is they're dealing with as they use the "&", "*" and "->" operators - is it an address, a value or what? You just need to keep focused on the fact that memory addresses are simply locations, and that values are the binary information stored there.

于 2008-09-18T20:35:09.753 回答
1

对于指针:

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

另外,请查看下面 Kent Fredric 的帖子,以另一种方式向您介绍指针。

于 2008-09-18T20:02:09.817 回答
1

要理解指针,我不能高度推荐K&R书。

于 2008-09-18T20:03:57.270 回答
1

对我有启发的书是Donald Alcock 的Illustrating Ansi C。它充满了手绘风格的方框和箭头图,说明了指针、指针算术、数组、字符串函数等......

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

于 2008-09-18T20:11:36.770 回答
1

来自 lassevek对 SO 上类似问题的回答:

指针是一个概念,很多人一开始可能会感到困惑,特别是在复制指针值并仍然引用同一个内存块时。

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

  • 复制指针值,把地址写在新纸上
  • 链接列表,房子里的一张纸,上面有下一个房子的地址
  • 释放内存,拆房子,抹掉地址
  • 内存泄露,纸丢了找不到房子
  • 释放内存但保留一个(现在无效的)引用,拆除房子,擦除其中一张纸,但有另一张带有旧地址的纸,当你去地址时,你不会找到房子,但你可能会发现一些类似于某人的废墟的东西
  • 缓冲区溢出,你把更多的东西搬进房子里,超出了你的承受能力,溢出到邻居的房子里
于 2008-09-18T20:12:47.610 回答
1

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

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

就我个人而言,我在 C++ 上坚持了几年,直到我觉得我牢牢掌握了 C 以及汇编语言的幕后情况。虽然这是很久以前的事了,但现在我认为了解计算机如何在低层次上工作确实对我的职业生涯有益。

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

于 2008-09-18T20:18:32.027 回答
1

没有什么可以代替练习。

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

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

  • 将另一个子类添加到层次结构
  • 向现有类添加方法
  • 将向前遍历集合的算法更改为向后迭代。

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

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

于 2008-09-18T20:24:58.733 回答
1

就课程而言,我掌握了三种真正帮助我跳入真正的面向对象编程的技术。

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

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

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

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

于 2008-09-22T16:42:58.100 回答
0

你读过 Bjarne Stroustrup 的The C++ Programming Language吗?他创建了 C++。

C++ FAQ Lite 也不错。

于 2008-09-18T20:45:39.247 回答
0

The point at which I really got pointers was coding TurboPascal on a FatMac (around 1984 or so) - which was the native Mac language at the time.

The Mac had an odd memory model whereby when allocated the address the memory was stored in a pointer on the heap, but the location of that itself was not guaranteed and instead the memory handling routines returned a pointer to the pointer - referred to as a handle. Consequently to access any part of the allocated memory it was necessary to dereference the handle twice. It took a while, but constant practice eventually drove the lesson home.

Pascal's pointer handling is easier to grasp than C++, where the syntax doesn't help the beginner. If you are really and truly stuck understanding pointers in C then your best option might be to obtain a copy a a Pascal compiler and try writing some basic pointer code in it (Pascal is near enough to C you'll get the basics in a few hours). Linked lists and the like would be a good choice. Once you're comfortable with those return to C++ and with the concepts mastered you'll find that the cliff won't look so steep.

于 2008-09-18T20:32:23.833 回答
0

Your problem seems to be the C core in C++, not C++ itself. Get yourself the Kernighan & Ritchie (The C Programming Language). Inhale it. It's very good stuff, one of the best programming language books ever written.

于 2008-09-21T21:08:52.853 回答
0

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

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

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

当我了解 CI 有一些宏时,我或多或少地允许您使用指针,就像它们内存中的数组索引一样。但我早就丢失了那个代码,也早就忘记了。

我记得它开始于

#define MEMORY 0; 
#define MEMORYADDRESS( a ) *a;

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

于 2008-09-18T20:01:54.467 回答
0

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

于 2008-09-18T20:03:57.363 回答
0

对于课程:

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

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

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

于 2008-09-18T20:10:35.803 回答
0

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

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

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

于 2008-09-18T20:12:30.137 回答
0

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

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

如果您想更好地了解 C++ 对象模型下的内容,可以阅读深入了解 C++ 对象模型。

于 2008-09-18T20:12:47.717 回答
0

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

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

于 2008-09-18T20:24:25.380 回答
0

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

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

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

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

于 2008-09-18T22:25:57.710 回答
0

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

诠释一个;

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

当你说:

诠释*一个;

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

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

于 2008-09-21T20:45:31.707 回答