c++ - pointer教學 - pointers中文




理解指針有什麼障礙,克服它們有什麼可以做的? (19)

為什麼C或C ++中許多新的,甚至是老年的大學水平的學生對於混亂的主要原因是什麼? 是否有任何工具或思維過程幫助您理解指針在變量,函數和更高級別上的工作方式?

有什麼好的做法可以使人們達到“啊,我明白了”的水平,而不會讓他們陷入整體概念? 基本上,鑽類似的情況。


為什麼C / C ++語言中許多新的,甚至是老年的大學水平的學生都指出這種混亂的主要原因?

價值佔位符的概念 - 變量 - 映射到我們在學校教授的東西 - 代數。 如果不理解內存是如何在計算機中進行物理佈局的,並不存在並行繪圖,並且在C / C ++ /字節通信級別處理低級別內容之前,沒有人會考慮這種事情。

是否有任何工具或思維過程幫助您理解指針在變量,函數和更高級別上的工作方式?

地址框。 我記得當我正在學習將BASIC編程到微型計算機中時,這些漂亮的書中有遊戲,有時你不得不將價值戳到特定的地址。 他們有一堆盒子的圖片,用0,1,2 ......遞增標記,並解釋說只有一個小的東西(一個字節)可以放入這些盒子中,並且有很多盒子 - 一些電腦有多達65535! 他們彼此相鄰,他們都有一個地址。

有什麼好的做法可以使人們達到“啊,我明白了”的水平,而不會讓他們陷入整體概念? 基本上,鑽類似的情況。

進行演練? 製作一個結構體:

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

也許這通過例子解釋了一些基礎知識?



I could work with pointers when I only knew C++. I kind of knew what to do in some cases and what not to do from trial/error. But the thing that gave me complete understanding is assembly language. If you do some serious instruction level debugging with an assembly language program you've written, you should be able to understand a lot of things.


I don't see what is so confusing about pointers. They point to a location in memory, that is it stores the memory address. In C/C++ you can specify the type the pointer points to. 例如:

int* my_int_pointer;

Says that my_int_pointer contains the address to a location that contains an int.

The problem with pointers is that they point to a location in memory, so it is easy to trail off into some location you should not be in. As proof look at the numerous security holes in C/C++ applications from buffer overflow (incrementing the pointer past the allocated boundary).


I like the house address analogy, but I've always thought of the address being to the mailbox itself. This way you can visualize the concept of dereferencing the pointer (opening the mailbox).

For instance following a linked list: 1) start with your paper with the address 2) Go to the address on the paper 3) Open the mailbox to find a new piece of paper with the next address on it

In a linear linked list, the last mailbox has nothing in it (end of the list). In a circular linked list, the last mailbox has the address of the first mailbox in it.

Note that step 3 is where the dereference occurs and where you'll crash or go wrong when the address is invalid. Assuming you could walk up to the mailbox of an invalid address, imagine that there's a black hole or something in there that turns the world inside out :)


I think it might actually be a syntax issue. The C/C++ syntax for pointers seems inconsistent and more complex than it needs to be.

Ironically, the thing that actually helped me to understand pointers was encountering the concept of an iterator in the c++ Standard Template Library . It's ironic because I can only assume that iterators were conceived as a generalization of the pointer.

Sometimes you just can't see the forest until you learn to ignore the trees.


I think that what makes pointers tricky to learn is that until pointers you're comfortable with the idea that "at this memory location is a set of bits that represent an int, a double, a character, whatever".

When you first see a pointer, you don't really get what's at that memory location. "What do you mean, it holds an address ?"

I don't agree with the notion that "you either get them or you don't".

They become easier to understand when you start finding real uses for them (like not passing large structures into functions).


I think the main barrier to understanding pointers is bad teachers.

Almost everyone are taught lies about pointers: That they are nothing more than memory addresses , or that they allow you to point to arbitrary locations .

And of course that they are difficult to understand, dangerous and semi-magical.

None of which is true. Pointers are actually fairly simple concepts, as long as you stick to what the C++ language has to say about them and don't imbue them with attributes that "usually" turn out to work in practice, but nevertheless aren't guaranteed by the language, and so aren't part of the actual concept of a pointer.

I tried to write up an explanation of this a few months ago in this blog post -- hopefully it'll help someone.

(Note, before anyone gets pedantic on me, yes, the C++ standard does say that pointers represent memory addresses. But it does not say that "pointers are memory addresses, and nothing but memory addresses and may be used or thought of interchangeably with memory addresses". The distinction is important)


Not a bad way to grasp it, via iterators.. but keep looking you'll see Alexandrescu start complaining about them.

Many ex-C++ devs (that never understood that iterators are a modern pointer before dumping the language) jump to C# and still believe they have decent iterators.

Hmm, the problem is that all that iterators are is in complete odds at what the runtime platforms (Java/CLR) are trying to achieve: new, simple, everyone-is-a-dev usage. Which can be good, but they said it once in the purple book and they said it even before and before C:

Indirection.

A very powerful concept but never so if you do it all the way.. Iterators are useful as they help with abstraction of algorithms, another example. And compile-time is the place for an algorithm, very simple. You know code + data, or in that other language C#:

IEnumerable + LINQ + Massive Framework = 300MB runtime penalty indirection of lousy, dragging apps via heaps of instances of reference types..

"Le Pointer is cheap."


Post office box number.

It's a piece of information that allows you to access something else.

(And if you do arithmetic on post office box numbers, you may have a problem, because the letter goes in the wrong box. And if somebody moves to another state -- with no forwarding address -- then you have a dangling pointer. On the other hand -- if the post office forwards the mail, then you have a pointer to a pointer.)


The confusion comes from the multiple abstraction layers mixed together in the "pointer" concept. Programmers don't get confused by ordinary references in Java/Python, but pointers are different in that they expose characteristics of the underlying memory-architecture.

It is a good principle to cleanly separate layers of abstraction, and pointers do not do that.


The problem with pointers is not the concept. It's the execution and language involved. Additional confusion results when teachers assume that it's the CONCEPT of pointers that's difficult, and not the jargon, or the convoluted mess C and C++ makes of the concept. So vast amounts of effort are poored into explaining the concept (like in the accepted answer for this question) and it's pretty much just wasted on someone like me, because I already understand all of that. It's just explaining the wrong part of the problem.

To give you an idea of where I'm coming from, I'm someone who understands pointers perfectly well, and I can use them competently in assembler language. Because in assembler language they are not referred to as pointers. They are referred to as addresses. When it comes to programming and using pointers in C, I make a lot of mistakes and get really confused. I still have not sorted this out. Let me give you an example.

When an api says:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

what does it want?

it could want:

a number representing an address to a buffer

(To give it that, do I say doIt(mybuffer) , or doIt(*myBuffer) ?)

a number representing the address to an address to a buffer

(is that doIt(&mybuffer) or doIt(mybuffer) or doIt(*mybuffer) ?)

a number representing the address to the address to the address to the buffer

(maybe that's doIt(&mybuffer) . or is it doIt(&&mybuffer) ? or even doIt(&&&mybuffer) )

and so on, and the language involved doesn't make it as clear because it involves the words "pointer" and "reference" that don't hold as much meaning and clarity to me as "x holds the address to y" and "this function requires an address to y". The answer additionally depends on just what the heck "mybuffer" is to begin with, and what doIt intends to do with it. The language doesn't support the levels of nesting that are encountered in practice. Like when I have to hand a "pointer" in to a function that creates a new buffer, and it modifies the pointer to point at the new location of the buffer. Does it really want the pointer, or a pointer to the pointer, so it knows where to go to modify the contents of the pointer. Most of the time I just have to guess what is meant by "pointer" and most of the time I'm wrong, regardless of how much experience I get at guessing.

"Pointer" is just too overloaded. Is a pointer an address to a value? or is it a variable that holds an address to a value. When a function wants a pointer, does it want the address that the pointer variable holds, or does it want the address to the pointer variable? 我很困惑。


The way I liked to explain it was in terms of arrays and indexes - people might not be familiar with pointers, but they generally know what an index is.

So I say imagine that the RAM is an array (and you have only 10-bytes of RAM):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

Then a pointer to a variable is really just the index of (the first byte of) that variable in the RAM.

So if you have a pointer/index unsigned char index = 2 , then the value is obviously the third element, or the number 4. A pointer to a pointer is where you take that number and use it as an index itself, like RAM[RAM[index]] .

I would draw an array on a list of paper, and just use it to show things like many pointers pointing to the same memory, pointer arithmetic, pointer to pointer, and so on.


一個比喻我發現有用的解釋指針是超鏈接。 大多數人可以理解,網頁上的鏈接“指向”互聯網上的另一個頁面,如果您可以復制並粘貼該超鏈接,則它們都會指向同一個原始網頁。 如果你去編輯原始頁面,然後按照這些鏈接(指針),你會得到新的更新頁面。


我不認為指針作為一個概念特別棘手 - 大多數學生的心智模式映射到這樣的東西,一些快速框圖可以提供幫助。

至少,我在過去經歷過的並且看到別人處理過的困難是,C / C ++中指針的管理可能會非常複雜。


我想我會在這個列表中添加一個類比,當我在解釋指針(當天)作為計算機科學講師時發現它非常有用; 首先,讓我們:

設置階段

考慮一個有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 |

你的指針仍然指向紅色車的位置但不再是。 比方說,一輛新車拉入那裡......一輛橙色汽車。 現在,如果我再次問你,“紅色車在哪裡”,你仍然指著那裡,但現在你錯了。 這不是一輛紅色的車,那是橙色的。

指針算術

好的,所以你仍然指著第二個停車位(現在被橙色汽車佔據)

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

那麼我現在有一個新的問題...我想知道下一個停車位的汽車顏色。 你可以看到你指向第2個點,所以你只需加1,然後指向下一個點。 ( finger+1 ),因為我想知道數據在那裡,所以你必須檢查那個點(不只是手指),所以你可以參考指針( *(finger+1) ),看看是否有綠色那裡有汽車(在那個位置的數據)


指針似乎混淆了很多人的原因是,他們大多在計算機體系結構中很少或根本沒有背景。 因為很多人似乎沒有了解計算機(機器)是如何實際實現的 - 在C / C ++中工作似乎是陌生的。

一個演練是要求他們用一個專注於指針操作(加載,存儲,直接/間接尋址)的指令集來實現一個簡單的基於字節碼的虛擬機(以他們選擇的任何語言,python對此很有用)。 然後讓他們為該指令集編寫簡單的程序。

任何需要稍微比簡單加法更多的事情都會涉及指針,他們一定會得到它。


指針是一個概念,對於許多人來說,最初可能會引起混淆,特別是當涉及到復制指針值並仍然引用相同的內存塊時。

我發現最好的比喻是將指針視為一張紙上有一個房子地址的紙片,以及它引用的內存塊作為實際的房子。 因此可以很容易地解釋各種操作。

我在下面添加了一些Delphi代碼,並在適當的地方添加了一些註釋。 我選擇了Delphi,因為我的其他主要編程語言C#並沒有以相同的方式顯示內存洩漏等內容。

如果您只想學習指針的高級概念,那麼您應該在下面的解釋中忽略標記為“內存佈局”的部分。 它們旨在舉例說明操作之後內存的樣子,但它們本質上更低級。 但是,為了準確解釋緩衝區溢出如何真正起作用,重要的是我添加了這些圖。

免責聲明:對於所有意圖和目的,此解釋和示例內存佈局大大簡化。 有更多的開銷和更多的細節,你需要知道你是否需要在低級別的基礎上處理內存。 但是,對於解釋內存和指針的意圖來說,這足夠準確。

假設下面使用的THouse類如下所示:

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

初始化房屋對象時,賦予構造函數的名稱將被複製到專用字段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

獲得此方法的更常見方法是忘記釋放某些內容,而不是像上面那樣覆蓋它。 用德爾菲術語來說,這將通過以下方法進行:

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),並且你的地址到了房子的中間。 任何試圖將這個大型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)

通常將“無處指向”地址存儲為零地址。

基本而言,什麼是指針?

指針只是一個保存內存地址的變量。 通常你可以讓編程語言給你它的編號,但是大多數編程語言和運行時都試圖隱藏下面有一個數字的事實,只是因為數字本身並沒有給你帶來任何意義。 最好把一個指針看作一個黑盒子,也就是說。 你不知道或關心它是如何實現的,只要它能夠工作。


起初,我很難理解指針的原因在於許多解釋都包含了大量有關通過引用的廢話。 所有這一切都是混淆了這個問題。 當你使用指針參數時,你仍然在傳遞值; 但價值恰好是一個地址,而不是一個整數。

其他人已經鏈接到本教程,但我可以強調我開始理解指針的時刻:

C語言中的指針和數組教程:第3章 - 指針和字符串

int puts(const char *s);

目前,忽略const. 傳遞給puts()的參數是一個指針, 是一個指針的值(因為C中的所有參數都是按值傳遞的),而指針的值就是它指向的地址,或者簡單地說就是一個地址。 因此,當我們寫puts(strA); 正如我們所看到的,我們正在傳遞strA [0]的地址。

當我讀到這些文字的時候,雲層分開了,一束陽光籠罩著我,指指點了解。

即使你是一個VB.NET或C#開發人員(就像我一樣)並且從不使用不安全的代碼,但仍然值得理解指針是如何工作的,或者你不會理解對象引用是如何工作的。 然後你會有一個共同但錯誤的概念,即將一個對象引用傳遞給一個方法來複製該對象。







pointers