指標變數的初始化,C語言指標變數初始化詳解

2020-07-16 10:04:22
本節來解決如何給一個指標變數初始化。即怎樣使一個指標變數指向另一個變數。

前面章節中的某些程式實際上已經使用了,即可以用賦值語句使一個指標變數得到另一個變數的地址,從而使它指向該變數。比如:
int i, *j;
j = &i;
這樣就將變數 i 的地址放到了指標變數 j 中,通過 i 的地址,j 就能找到 i 中的資料,所以 j 就“指向”了變數 i。其中 & 是“取地址運算子”,與 scanf 中的 & 是一樣的概念;* 為“指標運算子”,功能是取其內部所存變數地址所指向變數中的內容。因為 j 是定義成指標型變數,所以 j 中只能存放變數的地址,所以變數i前一定要加 &。需要注意的是,指標變數中只能存放地址,不要將一個整數或任何其他非地址型別的資料賦給一個指標變數。

此外,還有兩點需要注意:
  1. j 不是 i,i 也不是 j。修改j的值不會影響i的值,修改 i 的值也不會影響 j 的值。j 是變數 i 的地址,而 i 是變數 i 裡面的資料。一個是“記憶體單元的地址”,另一個是“記憶體單元的內容”。
  2. 定義指標變數時的“*j”和程式中用到的“*j”含義不同。定義指標變數時的“*j”只是一個宣告,此時的“*”僅表示該變數是一個指標變數,並沒有其他含義。而且此時該指標變數並未指向任何一個變數,至於具體指向哪個變數要在程式中指定,即給指標變數初始化。而當指定 j 指向變數 i 之後,*j 就完全等同於 i 了,可以相互替換。

下面給大家寫一個程式:
# include <stdio.h>
int main(void)
{
    int i = 3, *j;  //*j表示定義了一個指標變數j
    j = &i;
    printf("*j = %dn", *j);  //此時*j完全等同於i
    printf("j = %dn", j);    //j裡面儲存的是變數i的地址
    return 0;
}
輸出結果是:
*j = 3
j = 1245052

下面再將上面這個程式修改一下:
# include <stdio.h>
int main(void)
{
    int i = 3;
    int *j = &i;  //*j表示定義了一個指標變數j, 並將變數i的地址賦給它
    printf("*j = %dn", *j);  //此時*j完全等同於i
    printf("j = %dn", j);    //j裡面儲存的是變數i的地址
    return 0;
}
輸出結果是:
*j = 3
j = 1245052

這個程式與第一個程式有什麼不同?同樣是將變數 i 的地址賦給指標變數 j,第一個程式是“j=&i;”,而第二個程式是“*j=&i;”。原因是,前者是定義指標變數後對它初始化,即先定義後初始化;而後者是定義指標變數時對它進行初始化,即定義時初始化。通過這個對比我們可以更鮮明地看出定義指標變數時的“*j”和程式中用到的“*j”含義的不同。

那麼指標變數和指標變數之間可不可以相互賦值呢?我們看看下面這個程式:
# include <stdio.h>
int main(void)
{
    int *i, *j;
    int k = 3;
    i = &k;
    j = i;  //直接指標變數名之間進行賦值
    printf("*j = %dn", *j);  //此時*j完全等同於k
    printf("j = %dn", j);    // j裡面儲存的是變數k的地址
    return 0;
}
輸出結果是:
*j = 3
j = 1245044

可見,可以直接將一個指標變數賦給另一個指標變數,只要將指標變數名賦給另一個指標變數名即可。但是需要注意的是:
  1. 這兩個指標變數的基本類型一定要相同。
  2. 在賦值之前,賦值運算子“=”右邊的指標變數必須是已經初始化過的。也就是說,切忌將一個沒有初始化的指標變數賦給另一個指標變數。這是非常嚴重的語法錯誤。

同樣,也可以在定義指標變數時就給它賦初值:
# include <stdio.h>
int main(void)
{
    int k = 3;
    int *i = &k;
    int *j = i;
    printf("*j = %dn", *j);  //此時*j完全等同於k
    printf("j = %dn", j);    //j裡面儲存的是變數k的地址
    return 0;
}
輸出結果是:
*j = 3
j = 1245048

注意,“int*j=i;”千萬不要寫成“int*j=*i;”。因為此時 *i 不是定義指標變數 i,而是完全等同於變數 k。所以 int 型變數不能賦給 int* 型的變數。

指標常見錯誤

1) 參照未初始化的指標變數

試圖參照未初始化的指標變數是初學者最容易犯的錯誤。未初始化的指標變數就是“野”指標,它指向的是無效的地址。

有些書上說:“如果指標變數不初始化,那麼它可能指向記憶體中的任何一個儲存單元,這樣就會很危險。如果正好指向儲存著重要資料的記憶體單元,而且又不小心向這個記憶體單元中寫入了資料,把原來的重要資料給覆蓋了,這樣就會導致系統崩潰。”這種說法是不正確的!如果真是這樣的話就是編譯器的一個嚴重的 BUG!

編譯器的設計人員是不會允許這麼大的 BUG 存在的。那麼如果指標變數未初始化,編譯器的設計人員是如何處理這個問題的呢?肯定不可能讓它亂指。以VC++6.0這個編譯器為例,如果指標變數未初始化,那麼編譯器會讓它指向一個固定的、不用的地址。下面來寫一個程式:
# include <stdio.h>
int main(void)
{
    int *p, *q;
    printf("p = %#Xn", p);
    printf("q = %#Xn", q);
    return 0;
}
輸出結果是:
p = 0XCCCCCCCC
q = 0XCCCCCCCC

可見,在 VC++6.0 中只要指標變數未初始化,那麼編譯器就讓它指向 0XCCCCCCCC 這個記憶體單元。而且這個記憶體單元是程式所不能存取的,存取就會觸發異常,所以也不怕往裡面寫東西。

而如果在 VS 2008 這個編譯器中,程式雖然能編譯通過,但是在執行的時候直接出錯,它並不會像 VC++6.0 那樣還能輸出所指向的記憶體單元的地址。

下面來看一個程式:
# include <stdio.h>
int main(void)
{
    int i = 3, *j;
    *j = i;
    return 0;
}
程式中,j 是 int* 型的指標變數。j 中存放的應該是記憶體空間的地址,然後“變數 i 賦給 *j”表示將變數i中的值放到該地址所指向的記憶體空間中。但是現在 j 中並沒有存放一個地址,程式中並沒有給它初始化,那麼它指向的就是 0XCCCCCCCC 這個記憶體單元。這個記憶體單元是不允許存取的,即不允許往裡面寫資料。而把 i 賦給 *j 就是試圖往這個記憶體空間中寫資料,程式執行時就會出錯。但這種錯誤在編譯的時候並不會報錯,只有在執行的時候才會出錯,即傳說中的“段錯誤”。所以,一定要確保指標變數在參照之前已經被初始化為指向有效的地址。

在實際程式設計中,這種錯誤常見的另一個地方是用 scanf 給指標變數所指向的記憶體單元賦值。我們看看下面這個程式:
# include <stdio.h>
int main(void)
{
    int *i;
    scanf("%d", i);
    return 0;
}
該程式試圖給指標變數 i 所指向的記憶體單元賦值。但現在指標變數 i 並沒有初始化,所以程式執行時出錯。所以同樣,在使用 scanf 時必須要先給指標變數 i 初始化。比如像下面這樣寫:
# include <stdio.h>
int main(void)
{
    int *i, j;
    i = &j;  //先給指標變數i初始化
    scanf("%d", i);  //i本身就是地址, 所以不用加&
    printf("%dn", *i);
    return 0;
}
輸出結果是:
10
10

能不能使用 scanf 給指標變數初始化?指標變數裡面存放的是地址,而記憶體中有數不清的單元,每個單元都有一個地址,你知道每個單元的地址嗎?你知道哪些地址是空閒可用的,而哪些地址正儲存著重要資料不能用嗎?不知道的話怎麼用scanf給它初始化呢?萬一隨便寫一個地址正好是儲存著非常重要的資料的記憶體單元地址,那系統就真的崩潰了!

隨著大家程式設計能力的不斷提高,慢慢地就會發現,其實程式設計最重要、最核心的就是如何處理記憶體的問題,如何與記憶體打交道。

2) 往一個存放NULL地址的指標變數裡面寫入資料

這也是程式設計中最容易犯的錯誤,不僅是初學程式設計的,即使是有一些經驗的程式設計師也會不小心犯這個錯誤。我們把前面的程式改一下:
# include <stdio.h>
int main(void)
{
    int i = 3;
    int *j = NULL;
    *j = i;
    return 0;
}
之前是沒有給指標變數j初始化,現在初始化了,但是將它初始化為指向 NULL。NULL 也是一個指標變數。NULL 指向的是記憶體中地址為 0 的記憶體空間。以 32 位元運算系統為例,記憶體單元地址的範圍為 0x00000000~0xffff ffff。其中 0x00000000 就是 NULL 所指向的記憶體單元的地址。但是在作業系統中,該記憶體單元是不可用的。凡是試圖往該記憶體單元中寫入資料的操作都會被視為非法操作,從而導致程式錯誤。同樣,這種錯誤在編譯的時候也不會報錯,只有在執行的時候才會出錯。這種錯誤也屬於“段錯誤”。

然而雖然這麼寫是錯誤的,但是將一個指標變數初始化為指向 NULL,這在實際程式設計中是經常使用的。就跟前面講普通變數在定義時給它初始化為 0 一樣,指標變數如果在定義時不知道指向哪裡就將其初始化為指向 NULL。只是此時要注意的是,在該指標變數指向有效地址之前不要往該地址中寫入資料。也就是說,該指標變數還要二次賦值。

既然不能往裡面寫資料,而且還容易犯錯,為什麼還要這樣給它初始化呢?直接同前面定義普通變數時一樣,在定義時也不初始化,等到後面知道該給它賦什麼值時再給它賦值不行嗎?可以!但還是建議大家將它初始化為 NULL,就同前面將普通變數在定義時初始化為 0 一樣。這是很好的一種程式設計習慣。

最後關於 NULL 再補充一點,NULL 是定義在 stdio.h 標頭檔案中的符號常數,它表示的值是 0。