C++ 理解指针的障碍是什么?如何克服这些障碍?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:12:24  来源:igfitidea点击:

What are the barriers to understanding pointers and what can be done to overcome them?

c++cpointers

提问by David McGraw

Why are pointers such a leading factor of confusion for many new, and even old, college level students in C or C++? Are there any tools or thought processes that helped you understand how pointers work at the variable, function, and beyond level?

为什么指针对于 C 或 C++ 的许多新的甚至老的大学水平的学生来说是一个如此混乱的主要因素?是否有任何工具或思维过程可以帮助您了解指针在变量、函数和其他级别上的工作方式?

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.

有哪些好的做法可以让某人达到“啊哈,我明白了”的水平,而又不会让他们陷入整体概念的泥潭?基本上,像场景一样演练。

回答by Lasse V. Karlsen

Pointers is a concept that for many can be confusing at first, in particular when it comes to copying pointer values around and still referencing the same memory block.

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

I've found that the best analogy is to consider the pointer as a piece of paper with a house address on it, and the memory block it references as the actual house. All sorts of operations can thus be easily explained.

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

I've added some Delphi code down below, and some comments where appropriate. I chose Delphi since my other main programming language, C#, does not exhibit things like memory leaks in the same way.

我在下面添加了一些 Delphi 代码,并在适当的地方添加了一些注释。我选择了 Delphi,因为我的其他主要编程语言 C# 不会以相同的方式表现出内存泄漏等问题。

If you only wish to learn the high-level concept of pointers, then you should ignore the parts labelled "Memory layout" in the explanation below. They are intended to give examples of what memory could look like after operations, but they are more low-level in nature. However, in order to accurately explain how buffer overruns really work, it was important that I added these diagrams.

如果你只想学习指针的高级概念,那么你应该忽略下面解释中标有“内存布局”的部分。它们旨在举例说明操作后内存的外观,但它们本质上更底层。然而,为了准确地解释缓冲区溢出的实际工作原理,我添加了这些图表很重要。

Disclaimer: For all intents and purposes, this explanation and the example memory layouts are vastly simplified. There's more overhead and a lot more details you would need to know if you need to deal with memory on a low-level basis. However, for the intents of explaining memory and pointers, it is accurate enough.

免责声明:出于所有意图和目的,此解释和示例内存布局已大大简化。如果您需要在低级别基础上处理内存,则需要了解更多开销和更多细节。但是,对于解释内存和指针的意图,它已经足够准确了。



Let's assume the THouse class used below looks like this:

让我们假设下面使用的 THouse 类如下所示:

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

When you initialize the house object, the name given to the constructor is copied into the private field FName. There is a reason it is defined as a fixed-size array.

当您初始化房屋对象时,给构造函数的名称被复制到私有字段 FName 中。它被定义为固定大小的数组是有原因的。

In memory, there will be some overhead associated with the house allocation, I'll illustrate this below like this:

在内存中,会有一些与房屋分配相关的开销,我将在下面这样说明:

---[ttttNNNNNNNNNN]---
     ^   ^
     |   |
     |   +- the FName array
     |
     +- overhead

The "tttt" area is overhead, there will typically be more of this for various types of runtimes and languages, like 8 or 12 bytes. It is imperative that whatever values are stored in this area never gets changed by anything other than the memory allocator or the core system routines, or you risk crashing the program.

“tttt”区域是开销,对于各种类型的运行时和语言,通常会有更多的开销,例如 8 或 12 字节。除了内存分配器或核心系统例程之外,任何存储在该区域中的值都必须永远不会改变,否则您将面临程序崩溃的风险。



Allocate memory

分配内存

Get an entrepreneur to build your house, and give you the address to the house. In contrast to the real world, memory allocation cannot be told where to allocate, but will find a suitable spot with enough room, and report back the address to the allocated memory.

找一个企业家来盖你的房子,并给你房子的地址。与现实世界不同的是,内存分配不能被告知分配到哪里,而是会找到一个有足够空间的合适位置,并将地址报告给分配的内存。

In other words, the entrepreneur will choose the spot.

换句话说,企业家会选择地点。

THouse.Create('My house');

Memory layout:

内存布局:

---[ttttNNNNNNNNNN]---
    1234My house


Keep a variable with the address

保留一个带有地址的变量

Write the address to your new house down on a piece of paper. This paper will serve as your reference to your house. Without this piece of paper, you're lost, and cannot find the house, unless you're already in it.

在一张纸上写下你新房子的地址。这篇论文将作为您对房屋的参考。没有这张纸,你就迷路了,找不到房子,除非你已经在里面了。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

Memory layout:

内存布局:

    h
    v
---[ttttNNNNNNNNNN]---
    1234My house


Copy pointer value

复制指针值

Just write the address on a new piece of paper. You now have two pieces of paper that will get you to the same house, not two separate houses. Any attempts to follow the address from one paper and rearrange the furniture at that house will make it seem that the other househas been modified in the same manner, unless you can explicitly detect that it's actually just one house.

只需在一张新纸上写下地址。您现在有两张纸可以让您到达同一所房子,而不是两所不同的房子。任何试图按照一张纸上的地址并重新布置那所房子的家具的任何尝试都会使另一所房子看起来已经以相同的方式进行了修改,除非您可以明确地检测到它实际上只是一所房子。

NoteThis is usually the concept that I have the most problem explaining to people, two pointers does not mean two objects or memory blocks.

注意这通常是我向人们解释最多的概念,两个指针并不意味着两个对象或内存块。

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
---[ttttNNNNNNNNNN]---
    1234My house
    ^
    h2


Freeing the memory

释放内存

Demolish the house. You can then later on reuse the paper for a new address if you so wish, or clear it to forget the address to the house that no longer exists.

拆房子。如果您愿意,您可以稍后将纸张重新用于新地址,或者清除它以忘记不再存在的房子的地址。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

Here I first construct the house, and get hold of its address. Then I do something to the house (use it, the ... code, left as an exercise for the reader), and then I free it. Lastly I clear the address from my variable.

在这里,我首先建造了房子,并得到了它的地址。然后我对房子做一些事情(使用它,...代码,留给读者练习),然后我释放它。最后,我从变量中清除地址。

Memory layout:

内存布局:

    h                        <--+
    v                           +- before free
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

    h (now points nowhere)   <--+
                                +- after free
----------------------          | (note, memory might still
    xx34My house             <--+  contain some data)


Dangling pointers

悬空指针

You tell your entrepreneur to destroy the house, but you forget to erase the address from your piece of paper. When later on you look at the piece of paper, you've forgotten that the house is no longer there, and goes to visit it, with failed results (see also the part about an invalid reference below).

你告诉你的企业家摧毁房子,但你忘记从你的纸上擦掉地址。稍后当您查看那张纸时,您已经忘记了房子已经不在了,然后去访问它,结果失败了(另请参阅下面有关无效参考的部分)。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

Using hafter the call to .Freemightwork, but that is just pure luck. Most likely it will fail, at a customers place, in the middle of a critical operation.

h在调用 to 之后使用.Free可能会起作用,但这只是纯粹的运气。它很可能会在客户的关键操作中失败。

    h                        <--+
    v                           +- before free
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

    h                        <--+
    v                           +- after free
----------------------          |
    xx34My house             <--+

As you can see, h still points to the remnants of the data in memory, but since it might not be complete, using it as before might fail.

如您所见,h 仍然指向内存中的剩余数据,但由于它可能不完整,因此像以前一样使用它可能会失败。



Memory leak

内存泄漏

You lose the piece of paper and cannot find the house. The house is still standing somewhere though, and when you later on want to construct a new house, you cannot reuse that spot.

你丢了那张纸,找不到房子。房子仍然站在某处,当你以后想要建造新房子时,你不能重复使用那个地方。

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;

Here we overwrote the contents of the hvariable with the address of a new house, but the old one is still standing... somewhere. After this code, there is no way to reach that house, and it will be left standing. In other words, the allocated memory will stay allocated until the application closes, at which point the operating system will tear it down.

在这里,我们h用新房子的地址覆盖了变量的内容,但旧的仍然存在……某处。在这个密码之后,就没有办法到达那个房子了,它就会被留下来。换句话说,分配的内存将保持分配状态,直到应用程序关闭,此时操作系统将销毁它。

Memory layout after first allocation:

第一次分配后的内存布局:

    h
    v
---[ttttNNNNNNNNNN]---
    1234My house

Memory layout after second allocation:

第二次分配后的内存布局:

                       h
                       v
---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN]
    1234My house       5678My house

A more common way to get this method is just to forget to free something, instead of overwriting it as above. In Delphi terms, this will occur with the following method:

获得此方法的更常见方法是忘记释放某些东西,而不是像上面那样覆盖它。在 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;

After this method has executed, there's no place in our variables that the address to the house exists, but the house is still out there.

执行此方法后,在我们的变量中没有房子的地址存在的地方,但房子仍然在那里。

Memory layout:

内存布局:

    h                        <--+
    v                           +- before losing pointer
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

    h (now points nowhere)   <--+
                                +- after losing pointer
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

As you can see, the old data is left intact in memory, and will not be reused by the memory allocator. The allocator keeps track of which areas of memory has been used, and will not reuse them unless you free it.

如您所见,旧数据在内存中完好无损,不会被内存分配器重用。分配器会跟踪哪些内存区域已被使用,除非您释放它,否则不会重用它们。



Freeing the memory but keeping a (now invalid) reference

释放内存但保留(现在无效)引用

Demolish the house, erase one of the pieces of paper but you also have another piece of paper with the old address on it, when you go to the address, you won't find a house, but you might find something that resembles the ruins of one.

把房子拆了,把一张纸擦掉,但你还有另一张纸,上面写着旧地址,当你去地址时,你不会找到房子,但你可能会发现类似废墟的东西其中之一。

Perhaps you will even find a house, but it is not the house you were originally given the address to, and thus any attempts to use it as though it belongs to you might fail horribly.

也许你甚至会找到一所房子,但它不是你最初得到地址的房子,因此任何试图使用它好像它属于你的尝试都可能会失败。

Sometimes you might even find that a neighbouring address has a rather big house set up on it that occupies three address (Main Street 1-3), and your address goes to the middle of the house. Any attempts to treat that part of the large 3-address house as a single small house might also fail horribly.

有时您甚至可能会发现相邻地址上设置了一个相当大的房子,占据了三个地址(Main Street 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?

Here the house was torn down, through the reference in h1, and while h1was cleared as well, h2still has the old, out-of-date, address. Access to the house that is no longer standing might or might not work.

在这里,房子被拆除了,通过参考中的参考h1,虽然h1也被清理了,h2但仍然有旧的,过时的地址。进入不再站立的房子可能会也可能不会。

This is a variation of the dangling pointer above. See its memory layout.

这是上面悬空指针的变体。查看它的内存布局。



Buffer overrun

缓冲区溢出

You move more stuff into the house than you can possibly fit, spilling into the neighbours house or yard. When the owner of that neighbouring house later on comes home, he'll find all sorts of things he'll consider his own.

你搬进房子里的东西比你可能容纳的多,溢出到邻居的房子或院子里。当隔壁房子的主人后来回家时,他会发现各种他认为属于自己的东西。

This is the reason I chose a fixed-size array. To set the stage, assume that the second house we allocate will, for some reason, be placed before the first one in memory. In other words, the second house will have a lower address than the first one. Also, they're allocated right next to each other.

这就是我选择固定大小数组的原因。为了设置舞台,假设我们分配的第二个房子由于某种原因将被放置在内存中的第一个之前。换句话说,第二个房子的地址会比第一个低。此外,它们彼此相邻分配。

Thus, this code:

因此,这段代码:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

Memory layout after first allocation:

第一次分配后的内存布局:

                        h1
                        v
-----------------------[ttttNNNNNNNNNN]
                        5678My house

Memory layout after second allocation:

第二次分配后的内存布局:

    h2                  h1
    v                   v
---[ttttNNNNNNNNNN]----[ttttNNNNNNNNNN]
    1234My other house somewhereouse
                        ^---+--^
                            |
                            +- overwritten

The part that will most often cause crash is when you overwrite important parts of the data you stored that really should not be randomly changed. For instance it might not be a problem that parts of the name of the h1-house was changed, in terms of crashing the program, but overwriting the overhead of the object will most likely crash when you try to use the broken object, as will overwriting links that is stored to other objects in the object.

最常导致崩溃的部分是当您覆盖存储的数据中真正不应随机更改的重要部分时。例如,就程序崩溃而言,更改 h1-house 的部分名称可能不是问题,但是当您尝试使用损坏的对象时,覆盖对象的开销很可能会导致崩溃覆盖存储到对象中其他对象的链接。



Linked lists

链表

When you follow an address on a piece of paper, you get to a house, and at that house there is another piece of paper with a new address on it, for the next house in the chain, and so on.

当你跟随一张纸上的地址时,你会到达一所房子,在那所房子里还有另一张纸,上面有一个新地址,链中的下一个房子,依此类推。

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

Here we create a link from our home house to our cabin. We can follow the chain until a house has no NextHousereference, which means it's the last one. To visit all our houses, we could use the following code:

在这里,我们创建了一个从我们家到我们小屋的链接。我们可以沿着链条直到房子没有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;

Memory layout (added NextHouse as a link in the object, noted with the four LLLL's in the below diagram):

内存布局(添加 NextHouse 作为对象中的链接,在下图中用四个 LLLL 标出):

    h1                      h2
    v                       v
---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL]
    1234Home       +        5678Cabin      +
                   |        ^              |
                   +--------+              * (no link)


In basic terms, what is a memory address?

基本上,什么是内存地址?

A memory address is in basic terms just a number. If you think of memory as a big array of bytes, the very first byte has the address 0, the next one the address 1 and so on upwards. This is simplified, but good enough.

内存地址在基本术语中只是一个数字。如果您将内存视为一个大字节数组,那么第一个字节的地址为 0,下一个字节的地址为 1,以此类推。这是简化的,但足够好。

So this memory layout:

所以这个内存布局:

    h1                 h2
    v                  v
---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN]
    1234My house       5678My house

Might have these two address (the leftmost - is address 0):

可能有这两个地址(最左边的 - 是地址 0):

  • h1 = 4
  • h2 = 23
  • h1 = 4
  • h2 = 23

Which means that our linked list above might actuall look like this:

这意味着我们上面的链表实际上可能是这样的:

    h1 (=4)                 h2 (=28)
    v                       v
---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL]
    1234Home      0028      5678Cabin     0000
                   |        ^              |
                   +--------+              * (no link)

It is typical to store an address that "points nowhere" as a zero-address.

通常将“无处指向”的地址存储为零地址。



In basic terms, what is a pointer?

在基本术语中,什么是指针?

A pointer is just a variable holding a memory address. You can typically ask the programming language to give you its number, but most programming languages and runtimes tries to hide the fact that there is a number beneath, just because the number itself does not really hold any meaning to you. It is best to think of a pointer as a black box, ie. you don't really know or care about how it is actually implemented, just as long as it works.

指针只是一个保存内存地址的变量。您通常可以要求编程语言给您它的编号,但大多数编程语言和运行时都试图隐藏下面有一个数字的事实,仅仅因为数字本身对您没有任何意义。最好将指针视为一个黑匣子,即。你并不真正知道或关心它是如何实际实现的,只要它有效。

回答by Tryke

In my first Comp Sci class, we did the following exercise. Granted, this was a lecture hall with roughly 200 students in it...

在我的第一节 Comp Sci 课上,我们做了以下练习。没错,这是一个大约有200名学生在里面的演讲厅……

Professor writes on the board: int john;

教授在黑板上写道: int john;

John stands up

约翰站起来

Professor writes: int *sally = &john;

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

Sally stands up, points at john

莎莉站起来,指着约翰

Professor: int *bill = sally;

教授: int *bill = sally;

Bill stands up, points at John

比尔站起来,指着约翰

Professor: int sam;

教授: int sam;

Sam stands up

山姆站起来

Professor: bill = &sam;

教授: bill = &sam;

Bill now points to Sam.

比尔现在指向山姆。

I think you get the idea. I think we spent about an hour doing this, until we went over the basics of pointer assignment.

我想你应该已经明白了。我想我们花了大约一个小时来做​​这件事,直到我们了解了指针分配的基础知识。

回答by Wilka

An analogy I've found helpful for explaining pointers is hyperlinks. Most people can understand that a link on a web page 'points' to another page on the internet, and if you can copy & paste that hyperlink then they will both point to the same original web page. If you go and edit that original page, then follow either of those links (pointers) you'll get that new updated page.

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

回答by JSN

The reason pointers seem to confuse so many people is that they mostly come with little or no background in computer architecture. Since many don't seem to have an idea of how computers (the machine) is actually implemented - working in C/C++ seems alien.

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

A drill is to ask them to implement a simple bytecode based virtual machine (in any language they chose, python works great for this) with an instruction set focussed on pointer operations (load, store, direct/indirect addressing). Then ask them to write simple programs for that instruction set.

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

Anything requiring slightly more than simple addition is going to involve pointers and they are sure to get it.

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

回答by Josh

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++ 语言中许多新的甚至老的大学水平学生的困惑的主要因素?

The concept of a placeholder for a value - variables - maps onto something we're taught in school - algebra. There isn't an existing parallel you can draw without understanding how memory is physically laid out within a computer, and no one thinks about this kind of thing until they're dealing with low level things - at the C/C++/byte communications level.

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

Are there any tools or thought processes that helped you understand how pointers work at the variable, function, and beyond level?

是否有任何工具或思维过程可以帮助您了解指针在变量、函数和其他级别上的工作方式?

Addresses boxes. I remember when I was learning to program BASIC into microcomputers, there were these pretty books with games in them, and sometimes you had to poke values into particular addresses. They had a picture of a bunch of boxes, incrementally labelled with 0, 1, 2... and it was explained that only one small thing (a byte) could fit in these boxes, and there were a lot of them - some computers had as many as 65535! They were next to each other, and they all had an address.

地址框。我记得当我学习将 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.

有哪些好的做法可以让某人达到“啊哈,我明白了”的水平,而又不会让他们陷入整体概念的泥潭?基本上,像场景一样演练。

For a drill? Make a struct:

为了演练?创建一个结构:

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;

Same example as above, except in C:

与上面相同的示例,除了在 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);

Output:

输出:

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

Perhaps that explains some of the basics through example?

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

回答by Ryan Lundy

The reason I had a hard time understanding pointers, at first, is that many explanations include a lot of crap about passing by reference. All this does is confuse the issue. When you use a pointer parameter, you're stillpassing by value; but the value happens to be an address rather than, say, an int.

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

Someone else has already linked to this tutorial, but I can highlight the moment when I began to understand pointers:

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

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

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].

暂时忽略const.传递给的参数puts()是指针,也就是指针的值(因为C中的所有参数都是按值传递的),指针的值就是它指向的地址,或者,干脆, 一个地址。因此,当我们puts(strA);像我们看到的那样写时,我们传递的是 strA[0] 的地址。

The moment I read these words, the clouds parted and a beam of sunlight enveloped me with pointer understanding.

读到这些文字的瞬间,乌云散开,一束阳光用指针式的理解笼罩着我。

Even if you're a VB .NET or C# developer (as I am) and never use unsafe code, it's still worth understanding how pointers work, or you won't understand how object references work. Then you'll have the common-but-mistaken notion that passing an object reference to a method copies the object.

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

回答by Ted Percival

I found Ted Jensen's "Tutorial on Pointers and Arrays in C" an excellent resource for learning about pointers. It is divided into 10 lessons, beginning with an explanation of what pointers are (and what they're for) and finishing with function pointers. http://home.netcom.com/~tjensen/ptr/cpoint.htm

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

Moving on from there, Beej's Guide to Network Programming teaches the Unix sockets API, from which you can begin to do really fun things. http://beej.us/guide/bgnet/

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

回答by Derek Park

The complexities of pointers go beyond what we can easily teach. Having students point to each other and using pieces of paper with house addresses are both great learning tools. They do a great job of introducing the basic concepts. Indeed, learning the basic concepts is vitalto successfully using pointers. However, in production code, it's common to get into much more complex scenarios than these simple demonstrations can encapsulate.

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

I've been involved with systems where we had structures pointing to other structures pointing to other structures. Some of those structures also contained embedded structures (rather than pointers to additional structures). This is where pointers get really confusing. If you've got multiple levels of indirection, and you start ending up with code like this:

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

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

it can get confusing really quickly (imagine a lot more lines, and potentially more levels). Throw in arrays of pointers, and node to node pointers (trees, linked lists) and it gets worse still. I've seen some really good developers get lost once they started working on such systems, even developers who understood the basics really well.

它会很快变得混乱(想象一下更多的行,以及可能更多的级别)。放入指针数组和节点到节点指针(树、链表),情况会变得更糟。我见过一些非常优秀的开发人员一旦开始在这样的系统上工作就会迷失方向,即使是非常了解基础知识的开发人员。

Complex structures of pointers don't necessarily indicate poor coding, either (though they can). Composition is a vital piece of good object-oriented programming, and in languages with raw pointers, it will inevitably lead to multi-layered indirection. Further, systems often need to use third-party libraries with structures which don't match each other in style or technique. In situations like that, complexity is naturally going to arise (though certainly, we should fight it as much as possible).

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

I think the best thing colleges can do to help students learn pointers is to to use good demonstrations, combined with projects that require pointer use. One difficult project will do more for pointer understanding than a thousand demonstrations. Demonstrations can get you a shallow understanding, but to deeply grasp pointers, you have to really use them.

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

回答by Mike

I thought I'd add an analogy to this list that I found very helpful when explaining pointers (back in the day) as a Computer Science Tutor; first, let's:

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



Set the stage:

设置舞台

Consider a parking lot with 3 spaces, these spaces are numbered:

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

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

In a way, this is like memory locations, they are sequential and contiguous.. sort of like an array. Right now there are no cars in them so it's like an empty array (parking_lot[3] = {0}).

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



Add the data

添加数据

A parking lot never stays empty for long... if it did it would be pointless and no one would build any. So let's say as the day moves on the lot fills up with 3 cars, a blue car, a red car, and a green car:

一个停车场永远不会空着很久……如果是这样,那将毫无意义,也没有人会建造任何停车场。因此,假设随着时间的推移,场地上有 3 辆汽车,一辆蓝色汽车、一辆红色汽车和一辆绿色汽车:

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

These cars are all the same type (car) so one way to think of this is that our cars are some sort of data (say an int) but they have different values (blue, red, green; that could be an color enum)

这些汽车都是相同的类型(汽车),所以一种思考方式是我们的汽车是某种数据(比如int),但它们具有不同的值(blue, red, green; 那可能是一种颜色enum



Enter the pointer

输入指针

Now if I take you into this parking lot, and ask you to find me a blue car, you extend one finger and use it to point to a blue car in spot 1. This is like taking a pointer and assigning it to a memory address (int *finger = parking_lot)

现在,如果我带你到这个停车场,让你给我找一辆蓝色的车,你伸出一根手指,用它指向第 1 点的一辆蓝色车。这就像拿一个指针,并将它分配给一个内存地址( int *finger = parking_lot)

Your finger (the pointer) is not the answer to my question. Looking atyour finger tells me nothing, but if I look where you're finger is pointing to(dereferencing the pointer), I can find the car (the data) I was looking for.

你的手指(指针)不是我问题的答案。看你的手指会告诉我什么,但如果我看你在哪里手指指向(解引用指针),我能找到我一直在寻找汽车(数据)。



Reassigning the pointer

重新分配指针

Now I can ask you to find a red car instead and you can redirect your finger to a new car. Now your pointer (the same one as before) is showing me new data (the parking spot where the red car can be found) of the same type (the car).

现在我可以让你找一辆红色的车代替,你可以将你的手指重定向到一辆新车。现在你的指针(和以前一样)正在向我显示相同类型(汽车)的新数据(可以找到红色汽车的停车位)。

The pointer hasn't physically changed, it's still yourfinger, just the data it was showing me changed. (the "parking spot" address)

指针在物理上没有改变,它仍然是你的手指,只是它显示给我的数据改变了。(“停车位”地址)



Double pointers (or a pointer to a pointer)

双指针(或指向指针的指针)

This works with more than one pointer as well. I can ask where is the pointer, which is pointing to the red car and you can use your other hand and point with a finger to the first finger. (this is like int **finger_two = &finger)

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

Now if I want to know where the blue car is I can follow the first finger's direction to the second finger, to the car (the data).

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



The dangling pointer

悬空的指针

Now let's say you're feeling very much like a statue, and you want to hold your hand pointing at the red car indefinitely. What if that red car drives away?

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

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

Your pointer is still pointing to where the red car wasbut is no longer. Let's say a new car pulls in there... a Orange car. Now if I ask you again, "where is the red car", you're still pointing there, but now you're wrong. That's not an red car, that's orange.

你的指针仍然指向红色的车厢,但不再。假设一辆新车停在那里……一辆橙色汽车。现在如果我再问你,“红色汽车在哪里”,你仍然指着那里,但现在你错了。那不是红色的车,那是橙色的。



Pointer arithmetic

指针运算

Ok, so you're still pointing at the second parking spot (now occupied by the Orange car)

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

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

Well I have a new question now... I want to know the color of the car in the nextparking spot. You can see you're pointing at spot 2, so you just add 1 and you're pointing at the next spot. (finger+1), now since I wanted to know what the data was there, you have to check that spot (not just the finger) so you can deference the pointer (*(finger+1)) to see there is a green car present there (the data at that location)

好吧,我现在有一个新问题……我想知道下一个停车位的汽车颜色。您可以看到您指向的是点 2,因此您只需添加 1 并指向下一个点。( finger+1),现在因为我想知道那里的数据是什么,你必须检查那个点(不仅仅是手指),这样你才能尊重指针 ( *(finger+1)) 看到那里有一辆绿色汽车(那个位置的数据)

回答by Matt Mitchell

I don't think pointers as a concept are particularly tricky - most students' mental models map to something like this and some quick box sketches can help.

我不认为指针作为一个概念特别棘手——大多数学生的心智模型映射到这样的东西,一些快速的方框草图会有所帮助。

The difficulty, at least that which I've experienced in the past and seen others deal with, is that the management of pointers in C/C++ can be unncessarily convoluted.

困难,至少我在过去经历过并看到其他人处理过的困难在于,C/C++ 中的指针管理可能会不必要地复杂化。