自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

指針的前世今生:寫給所有被C/C++折磨過的人

開發(fā)
指針的本質(zhì)很簡單:就是存儲地址的變量。它之所以被發(fā)明,是為了解決函數(shù)間數(shù)據(jù)共享、內(nèi)存管理和復雜數(shù)據(jù)結構的實際需求。

大家好,我是小康。今天聊聊讓編程新手頭疼的"指針"——這個 C 語言第一難點究竟是什么,為什么會被發(fā)明出來?

從直接操作內(nèi)存到編程語言的"導航員"

你有沒有過這樣的經(jīng)歷:學習編程時,一切都還算順利,直到遇見了"指針"這個概念,突然感覺像遇到了一道難以逾越的高坎?(我第一次接觸指針時也是這樣,一臉懵圈...

  • "指針是變量的地址?"
  • "指針是指向內(nèi)存的變量?"
  • "為什么要用指針?沒有指針不行嗎?"

如果你也有這些疑問,那么今天這篇文章就是為你準備的。我們不打算用晦澀的技術語言解釋指針,而是要講一個故事:指針是怎樣一步步被發(fā)明出來的。

聽完這個故事,你會發(fā)現(xiàn),原來指針就像我們生活中的門牌號和導航,是那么簡單自然的存在!

計算機內(nèi)存:一條超長的街道

想象一下,計算機的內(nèi)存就像一條超長的街道,街道上有成千上萬的房子,每個房子都有自己的門牌號(地址)。

在計算機里,這些"房子"被稱為內(nèi)存單元,每個內(nèi)存單元都可以存儲一個數(shù)據(jù)。計算機通過門牌號(內(nèi)存地址)來找到并操作這些數(shù)據(jù)。

現(xiàn)在,讓我們回到計算機發(fā)展的早期,看看指針是如何逐步被發(fā)明出來的。

階段一:最原始的數(shù)據(jù)存儲方式

在計算機發(fā)展的早期階段,程序員主要使用機器語言和匯編語言編程。在這些低級語言中,程序員需要直接操作內(nèi)存地址。

比如,要存儲數(shù)字 42 到特定內(nèi)存位置,匯編語言可能會這樣寫:

MOV [1000], 42    ; 將數(shù)值42存入內(nèi)存地址1000

要取出這個數(shù)字:

MOV AX, [1000]    ; 從地址1000讀取數(shù)據(jù)到寄存器AX

這種直接操作內(nèi)存地址的方式雖然給了程序員極大的控制權,但也極其麻煩。

想象一下,你的程序中有上百個數(shù)據(jù),你需要記住每個數(shù)據(jù)分別存在哪個具體地址,這簡直是噩夢!而且地址一旦寫錯,程序就會莫名其妙地崩潰。

看看這張簡單的內(nèi)存布局圖:

內(nèi)存地址    |    數(shù)據(jù)
-----------------------
1000       |    42      <-- 存放了數(shù)字42
1001       |    ?       <-- 其他數(shù)據(jù)
1002       |    ?
...        |    ...
2000       |    100     <-- 存放了數(shù)字100
...        |    ...

程序員需要記?。簲?shù)字 42 在地址1000,數(shù)字 100 在地址 2000...太麻煩了!

階段二:變量的誕生

為了解決上面的問題,聰明的程序員發(fā)明了"變量"。

變量就像是給內(nèi)存地址貼上的標簽。我們不再需要記住"地址1000",而是可以說:

int age = 42;

這里,age是一個變量名,編譯器會自動為它分配一個內(nèi)存地址(比如1000),并在那里存儲數(shù)值 42。

當我們需要使用這個數(shù)據(jù)時,只需要寫age,而不是"地址1000",編譯器會自動幫我們找到正確的地址。

現(xiàn)在內(nèi)存布局變成了這樣:

內(nèi)存地址    |    數(shù)據(jù)     |    變量名
----------------------------------------
1000       |    42      |    age      <-- 不用記地址,用變量名就行
1001       |    ?       |    
1002       |    ?       |
...        |    ...     |
2000       |    100     |    salary   <-- 同樣用變量名引用
...        |    ...     |

這下舒服多了!但是,新的問題又來了。

階段三:變量的局限性——共享數(shù)據(jù)的難題

變量確實解決了不少問題,但隨著程序變得復雜,程序員們發(fā)現(xiàn)僅僅使用變量還不夠。特別是當多個函數(shù)需要共享和修改同一份數(shù)據(jù)時,問題就來了。

看看下面這個簡單的例子:

// 兩個函數(shù),都試圖給一個數(shù)字加 1
void func1(int a) { 
    a = a + 1; 
    printf("在func1中,a = %d\n", a);  // 這里a等于3
}

void func2(int b) { 
    b = b + 1; 
    printf("在func2中,b = %d\n", b);  // 這里b等于3
}

int main() {
    int num = 2;
    func1(num);  // 調(diào)用func1,傳入num
    func2(num);  // 調(diào)用func2,傳入num
    printf("最后num = %d\n", num);  // 奇怪,num還是2!
    return0;
}

運行這段代碼,你會發(fā)現(xiàn)一個奇怪的現(xiàn)象:雖然func1和func2都把傳入的值加了1,但最后num的值仍然是2,沒有變化!

這是為什么呢?因為在C語言(和許多其他語言)中,當我們把變量傳給函數(shù)時,傳遞的是變量的值的復制品,而不是變量本身。func1和func2各自得到了num的一個副本,它們修改的是副本,而不是原始的num。

下面是這個過程的圖解:

+-------------+       +------------+       +-------------+
|    main     |       |   func1    |       |    main     |
|  函數(shù)內(nèi)存    |  復制 |  函數(shù)內(nèi)存   |       |   函數(shù)內(nèi)存   |
|             | ----->|            |      |              |
| num = 2     |  值   | a = 2      |      | num = 2      |
|             |       |            |       |     ^        |
+-------------+       +------------+       +------|-------+
                             |                    |
                          a加1操作               沒有變化
                             ↓
                      +------------+
                      |    func1   |
                      |  函數(shù)內(nèi)存   |
                      |            |
                      | a = 3      |
                      |            |
                      +------------+

同理,當調(diào)用func2時,又創(chuàng)建了一個新的副本,對這個副本的修改也不會影響原始的num:

+-------------+       +------------+       +-------------+
|    main     |       |   func2    |       |    main     |
|  函數(shù)內(nèi)存   |  復制  |  函數(shù)內(nèi)存   |       |   函數(shù)內(nèi)存   |
|             | ----->|            |       |             |
| num = 2     |  值   | b = 2      |       | num = 2     |
|             |       |            |       |     ^       |
+-------------+       +------------+       +------|------+
                             |                    |
                          b加1操作               沒有變化
                             ↓
                      +------------+
                      |    func2   |
                      |  函數(shù)內(nèi)存   |
                      |            |
                      | b = 3      |
                      |            |
                      +------------+

這就帶來了一個問題:如果多個函數(shù)需要共同操作同一個數(shù)據(jù),該怎么辦?

程序員們思考著:有沒有一種方法,可以讓函數(shù)直接訪問和修改原始數(shù)據(jù),而不是它的副本?

階段四:指針的誕生 — 傳遞地址解決共享問題

為了解決上面的問題,聰明的程序員引入了一個革命性的概念:指針!

指針本質(zhì)上就是一個存儲內(nèi)存地址的變量。它就像是一張寫有門牌號的紙條,告訴你:"嘿,你要找的東西在這個地址!"

讓我們用指針來改造前面的例子:

// 現(xiàn)在函數(shù)參數(shù)變成了指針(注意那個星號)
void func1(int *a) { 
    *a = *a + 1;  // 通過指針修改原始數(shù)據(jù)
    printf("在func1中,*a = %d\n", *a);  // 現(xiàn)在是3
}

void func2(int *b) { 
    *b = *b + 1;  // 通過指針修改原始數(shù)據(jù)
    printf("在func2中,*b = %d\n", *b);  // 現(xiàn)在是4
}

int main() {
    int num = 2;
    func1(&num);  // 傳遞num的地址
    func2(&num);  // 傳遞num的地址
    printf("最后num = %d\n", num);  // 現(xiàn)在num變成了4!
    return0;
}

神奇的事情發(fā)生了!這次num的值真的改變了,從 2 變成了 4。為什么呢?

下面是使用指針時的圖解:

+-------------+       +------------+       +-------------+
|    main     |       |   func1    |       |    main     |
|  函數(shù)內(nèi)存    |  傳遞 |  函數(shù)內(nèi)存   |       |   函數(shù)內(nèi)存   |
|             | ----->|            |       |              |
| num = 2     |  地址 | a = &num   |------>| num = 3      |
|  ^          |       |            |  修改 |              |
+--|----------+       +------------+  原值  +-------------+
   |                      |    |             ^
   |                      |    |             |
   |                   *a 操作  |             |
   |                      |    |             |
   +----------------------+    +-------------+

當我們調(diào)用func2時,同樣是傳遞地址,并通過這個地址修改原始的num值:

+-------------+       +------------+       +-------------+
|    main     |       |   func2    |       |    main     |
|  函數(shù)內(nèi)存   |  傳遞  |  函數(shù)內(nèi)存   |       |   函數(shù)內(nèi)存   |
|             | -----> |            |       |             |
| num = 3     |  地址  | b = &num   |------>| num = 4     |
|  ^          |       |            |  修改  |             |
+--|----------+       +------------+  原值  +-------------+
   |                      |    |             ^
   |                      |    |             |
   |                   *b 操作  |             |
   |                      |    |             |
   +----------------------+    +-------------+

與傳值不同,這次我們傳遞的是num的地址(&num)。函數(shù)接收到這個地址后,可以通過指針(*a或*b)直接訪問和修改原始的num變量。

這就好比:我不給你我家的鑰匙副本,而是直接告訴你我家的地址,你可以直接來我家拿東西或放東西。

這就是指針的核心作用之一:讓多個函數(shù)可以共享和修改同一個數(shù)據(jù)。

當然,指針的用途遠不止于此。它還能幫我們解決更多復雜的問題,比如處理大量數(shù)據(jù)時節(jié)省內(nèi)存。想想看,與其復制一大堆數(shù)據(jù),不如只傳遞一個小小的地址,這樣效率高多了!

圖解指針的實戰(zhàn)演練

讓我們通過一個簡單的例子,來實際感受一下指針的使用:

#include <stdio.h>

int main() {
    int number = 42;      // 一個普通變量
    int *pointer = &number; // 指針變量,存儲number的地址
    
    printf("number的值: %d\n", number);     // 輸出:42
    printf("number的地址: %p\n", &number);  // 輸出類似:004FFD98
    printf("pointer存儲的地址: %p\n", pointer); // 輸出同上:004FFD98
    printf("pointer指向的值: %d\n", *pointer);  // 輸出:42
    
    // 通過指針修改number的值
    *pointer = 100;
    printf("修改后number的值: %d\n", number); // 輸出:100
    
    return0;
}

這個例子展示了指針的基本操作:

  • &number:獲取變量 number 的地址
  • int *pointer:聲明一個指向 int 類型的指針
  • pointer = &number:讓指針存儲 number 的地址
  • *pointer:訪問指針指向的值(這叫做"解引用")

通過*pointer = 100,我們改變了指針所指向地址上的值,因此 number 的值也變成了100。

為了更直觀地理解指針,我們可以畫一張簡圖:

內(nèi)存地址    內(nèi)容               變量名
---------------------------------------------------
0x004FFD98   | 42 (后來變成100) | number
---------------------------------------------------
0x114FFD98   | 0x004FFD98      | pointer (存儲的是 number 的地址)
---------------------------------------------------

當我們寫*pointer = 100時,計算機會:

  • 查看 pointer 變量的值(0x004FFD98)
  • 找到地址 0x004FFD98
  • 把那里的值改為 100

這就是為什么 number 的值也會變成100!

生動實例:理解指針的本質(zhì)

讓我們用一個更生活化的例子來理解指針:

假設你和朋友約好去一家新開的餐廳吃飯。你可以有兩種方式告訴朋友餐廳在哪:

  • 詳細描述餐廳的樣子、菜單、服務員長相等(相當于復制數(shù)據(jù)本身)
  • 直接發(fā)個定位或地址給他(相當于傳遞指針)

顯然,第二種方式更簡單高效!

在代碼中也是如此:

// 方式 1:復制整個結構體
void sendRestaurantInfo1(Restaurant r) {
    // 這里 r 是原始餐廳數(shù)據(jù)的一個完整復制品
}

// 方式 2:只傳遞"地址"(指針)
void sendRestaurantInfo2(Restaurant *r) {
    // 這里 r 只是一個指向原始餐廳數(shù)據(jù)的指針
}

方式 2 不僅傳遞的數(shù)據(jù)量更小,而且如果函數(shù)中修改了餐廳信息,原始數(shù)據(jù)也會被更新——因為我們操作的就是原始數(shù)據(jù)所在的地址!

階段五:指針的進階用法 — 動態(tài)內(nèi)存分配

隨著編程的發(fā)展,程序員發(fā)現(xiàn)了指針的另一個強大用途:動態(tài)內(nèi)存分配。

我們都知道在 C 語言中,定義數(shù)組時必須指定一個固定的大?。?/p>

int numbers[100];  // 只能存儲100個整數(shù),多一個都不行!

但如果事先不知道需要多少空間怎么辦?比如,用戶輸入一個數(shù)字 n,我們需要創(chuàng)建一個包含 n 個元素的數(shù)組?

這時,指針和動態(tài)內(nèi)存分配就派上了用場:

int n;
printf("請輸入需要的數(shù)組大?。?);
scanf("%d", &n);

// 動態(tài)分配n個int大小的內(nèi)存空間
int *dynamicArray = (int*)malloc(n * sizeof(int));

// 使用這個動態(tài)數(shù)組
for (int i = 0; i < n; i++) {
    dynamicArray[i] = i * 2;
}

// 使用完畢后釋放內(nèi)存
free(dynamicArray);

讓我們用圖解來理解這個過程:

輸入 n = 5 后:

+-------------+          malloc           +-----------------+
|             |                           |                 |
| dynamicArray| ----------------------->  | [0][1][2][3][4] |
|  (指針變量)  |     分配5個整數(shù)大小的內(nèi)存   |  (堆上的內(nèi)存塊)  |
|             |                           |                 |
+-------------+                           +-----------------+
                                            ↑
                                         動態(tài)分配的內(nèi)存
                                        (可以根據(jù)需要變化大?。?/code>

在這個例子中,我們通過malloc函數(shù)在堆上分配了一塊內(nèi)存,并得到了這塊內(nèi)存的起始地址,存儲在指針dynamicArray中。

程序運行時才決定分配多少內(nèi)存,用完后還可以釋放它——這就是動態(tài)內(nèi)存分配的魅力,而這一切都是通過指針實現(xiàn)的!

沒有指針,我們就無法實現(xiàn)這種"按需分配"的內(nèi)存管理方式,程序的靈活性會大大降低。

階段六:復雜數(shù)據(jù)結構的實現(xiàn) — 指針的終極應用

到目前為止,我們已經(jīng)看到了指針如何幫助函數(shù)共享數(shù)據(jù)以及實現(xiàn)動態(tài)內(nèi)存分配。但指針的故事還沒有結束,它的最強大之處在于使各種復雜數(shù)據(jù)結構成為可能。

還記得我們小時候玩過的尋寶游戲嗎?一條線索指向下一條,最終找到寶藏。在編程中,這種"一個指向另一個"的結構就是通過指針實現(xiàn)的!

1. 鏈表 — 數(shù)組的靈活替代品

數(shù)組的一個大問題是:一旦創(chuàng)建,就無法輕松地插入或刪除中間的元素。而鏈表解決了這個問題:

// 定義鏈表節(jié)點
struct Node {
    int data;           // 節(jié)點中存儲的數(shù)據(jù)
    struct Node *next;// 指向下一個節(jié)點的指針
};

// 在鏈表頭部插入新節(jié)點
struct Node* insertAtBeginning(struct Node *head, int value) {
    // 創(chuàng)建新節(jié)點
    struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = value;
    
    // 將新節(jié)點鏈接到原鏈表頭部
    newNode->next = head;
    
    // 返回新的鏈表頭部
    return newNode;
}

圖解一下鏈表的結構:

+--------+    +--------+    +--------+    +--------+
|  數(shù)據(jù)1  |   |  數(shù)據(jù)2  |    |  數(shù)據(jù)3  |    |  數(shù)據(jù)4  |
|        |    |        |    |        |    |        |
| 指針   |--->| 指針    |--->| 指針    |--->| 指針    |--->其他節(jié)點
+--------+    +--------+    +--------+    +--------+
  節(jié)點1         節(jié)點2         節(jié)點3         節(jié)點4

就像一列火車車廂,每個車廂(節(jié)點)不僅存放數(shù)據(jù),還通過指針"勾住"下一個車廂。這種結構讓我們可以在任意位置輕松地插入或刪除節(jié)點,而不需要像數(shù)組那樣移動大量數(shù)據(jù)。

2. 樹、圖等更復雜的數(shù)據(jù)結構

除了鏈表,指針還使樹、圖等更復雜的數(shù)據(jù)結構成為可能。例如,二叉樹的每個節(jié)點可以有左右兩個"孩子"節(jié)點:

struct TreeNode {
    int data;
    struct TreeNode *left;   // 指向左子節(jié)點的指針
    struct TreeNode *right;  // 指向右子節(jié)點的指針
};
+--------+
          |  根節(jié)點 |
          |        |
       +--+---+---+--+
       |             |
       ↓             ↓
    +----+          +----+
    | 左 |          | 右 |
    +----+          +----+

這些復雜數(shù)據(jù)結構在現(xiàn)代編程中極其重要,它們構成了數(shù)據(jù)庫、操作系統(tǒng)、游戲引擎等幾乎所有復雜軟件的基礎。而這一切,都是因為有了指針這個簡單卻強大的概念!

指針到底是個啥?豁然開朗時刻

經(jīng)過這一路的探索,我們終于可以給指針下一個通俗易懂的定義了:指針就是存儲內(nèi)存地址的變量,它"指向"另一個數(shù)據(jù)的位置。

就像門牌號告訴你一棟房子在哪,指針告訴程序一個數(shù)據(jù)在哪。它不是數(shù)據(jù)本身,而是數(shù)據(jù)的"地址"。

指針的價值在于:

  • 可以直接訪問和修改指向的數(shù)據(jù)
  • 傳遞大數(shù)據(jù)時只需傳遞一個小小的地址
  • 可以實現(xiàn)動態(tài)內(nèi)存分配
  • 讓復雜的數(shù)據(jù)結構(如鏈表、樹)成為可能
  • 提高程序的執(zhí)行效率

歷史小插曲:C語言與指針的故事

說到指針,就不得不提一下C語言。1972年,貝爾實驗室的丹尼斯·里奇(Dennis Ritchie)在開發(fā)C語言時,將指針作為核心特性引入。

為什么?簡單來說,計算機資源那時非常有限,而指針恰好能同時滿足兩個目標:

  • 提供像匯編語言一樣直接操作內(nèi)存的能力(高效)
  • 提供比匯編更好的抽象和可讀性(易用)

C語言通過指針實現(xiàn)了高效和易用的完美平衡,這也是為什么幾十年過去了,C語言仍然是操作系統(tǒng)、嵌入式系統(tǒng)等領域的主力軍。

有趣的是,雖然 Java、Python 等現(xiàn)代語言隱藏了指針細節(jié),但在它們的底層實現(xiàn)中,指針的概念依然無處不在!

結語:指針不再可怕

現(xiàn)在,你對指針有了更清晰的認識了吧?它不再是那個可怕的編程概念,而是一個解決實際問題的實用工具。

指針的本質(zhì)很簡單:就是存儲地址的變量。它之所以被發(fā)明,是為了解決函數(shù)間數(shù)據(jù)共享、內(nèi)存管理和復雜數(shù)據(jù)結構的實際需求。

從最初的直接操作內(nèi)存地址,到變量的發(fā)明,再到指針的出現(xiàn),我們看到了編程技術的不斷進步。下次當你遇到指針時,不妨想象它就是一個寫著門牌號的紙條,告訴你:"嘿,你要找的東西在這個地址!"

相信我,當你真正理解了指針的本質(zhì),你會發(fā)現(xiàn)它其實很簡單,而且非常有用!

責任編輯:趙寧寧 來源: 跟著小康學編程
相關推薦

2025-03-10 08:30:00

2024-12-23 08:00:00

委托C#編程

2024-12-17 17:24:24

2025-03-17 09:00:00

C++引用編程

2011-04-11 11:09:50

this指針

2021-12-21 15:31:10

C++語言指針

2024-05-15 16:01:04

C++編程開發(fā)

2014-01-24 09:49:01

C++指針

2010-01-26 13:42:28

C++指針

2011-08-23 09:52:31

CSS

2011-04-19 16:38:00

對象指針指針C++

2014-07-30 10:55:27

2025-02-12 11:25:39

2015-11-18 14:14:11

OPNFVNFV

2011-04-19 09:19:09

C++指針

2011-07-15 01:38:56

C++this指針

2024-04-10 12:14:36

C++指針算術運算

2024-01-09 09:23:12

指針C++

2021-10-27 16:27:20

C++指針操控

2011-07-12 13:01:00

CC++
點贊
收藏

51CTO技術棧公眾號