C語言指標【筆記】
C語言指標【筆記】
記憶體和地址
如果把計算機的記憶體比作一條長街上的房屋。每座房屋都可以容納資料,通過一個房號(門牌號)來標識。
計算機的記憶體由數以億萬計的位(bit)組成,每個位可以容納的值位 0 或 1 。因為這麼比喻也是有一定的侷限性,每個單獨的位用處不大。因此通常將許多位合成一組來表示一個單位,這樣就可以儲存範圍較大的值。下面展示現實機器中的一些記憶體位置。
每一個位置被稱為一個位元組(byte),每個位元組包含8位,可以儲存的無符號值為 0~255,有符號值為 -128~127。方框中包含一些資料(該圖未顯示)。
為了儲存更大的值,我們通常將兩個或者多個位元組合為一個更大的記憶體單位。現在的機器一般以字為單位儲存整數,每個字由 2 個或 4 個位元組組成。以 4 個位元組為例,每個字可以儲存的無符號值為 0 ~ 4394967295(2^32 - 1),有符號值為 -2147483648(-2^31) ~ 2147483648(2^31 - 1)。
程式設計師只對兩件事情感興趣:
- 記憶體中的每個位置都由一個獨一無二的地址標識。
- 記憶體中的每個位置都包含一個值。
地址與內容
上圖顯示了 5 個整數,每個都位於自己的字中。圖中第一排是根據地址取這個值。但是高階語言的特性之一就是提供了通過名字而不是地址來訪問記憶體位置的功能。第二排就是通過變數名來取這個值。
變數名字與記憶體位置之間的關聯並不是硬體提供的,是我們編譯器實現的。硬體仍然通過地址來訪問記憶體位置
未初始化和非法指標
初學者在使用指標時經常會犯一個錯誤,如下:
int* a;
*a = 20;
int* a;:宣告一個指標變數, 指標變數名字叫做 a,型別為 int*
*a:表示 a 所指向的位置所儲存的值;
第一條語句是一個宣告,宣告一個叫做 a 的指標變數,第二條語句把 20 儲存到指標 a 所指向的記憶體位置。
警告
- 如果一個變數是靜態的,它會被初始化為 0;
- 如果這個變數是動態的,它根本不會被初始化。
因此無論哪種情況,宣告一個指向整型的指標變數都不會"建立"用於儲存整型值的記憶體空間。
當賦值操作執行時,a 的初始值會是個非法地址,這樣賦值語句會出錯,終止程式。
但是:在這裡我想做個類比。如果因為終止程式,你發現了一個BUG,這就好比在房間裡看到了一隻老鼠,是不是很可怕;但是我認為這不是最可怕的,可怕的是當你知道房間裡有一隻老鼠,並且發現了他的位置,可是突然之間這個老鼠不見了!不見了!不見了!!!(自己體會一下)
下面我來說一下這種情況會導致的一種更為嚴重的情況:這個指標偶爾可能包含一個合法的地址。接下來的事情就是位於這個地址上的值被修改,雖然你並無意去修改他,但是這件事確實發生了。因此引發錯誤的程式碼可能和原先用於操作該值的程式碼完全不相干。所以,在對指標進行間接訪問之前,必須非常謹慎,保證指標已經被初始化。 PS: 我在這裡入過坑
指標、間接訪問和變數
能看懂下面表示式嗎?看懂的話,這一小段就不必看了。(純屬技巧,不實用)
*&a = 20;
答案:把值 20 賦值給變數 a 。
下面分析一下:
&操作符表示取變數 a 的地址,是一個指標變數,因此 &a 表示一個地址,也就是 a 的地址。* 操作符會訪問 &a 所表示的地址( a 的地址),會取得地址 &a 中的值(就是 a )。這個值就是 20 。
這個過程會涉及到更多的操作,產生的目的碼會更大,而且會使原始碼可讀性變差。因此很少有人會這麼寫。
指標常量
假設變數 a 在計算機中儲存於地址 100 位置處,下面這條語句是什麼作用呢?
*100 = 20;
看上去像是把 20 賦給地址為 100 的位置所儲存的變數。其實是錯的,因為 100 的型別為整型,而間接訪問操作(通過地址來訪問變數值)只能用於指標型別表示式。如果想把 25 儲存於地址 100 ,必須要用強制型別轉換。
*(int *)100 = 20;
也是個基本用不到的操作。強制型別轉換把 100 從整型轉換成指向整型的指標,這樣的地址訪問就是合法的了。 基本用不到,瞭解就行。嵌入式可能會用到。
指標的指標
現在有一下宣告:
int a = 20;
int* b = &a;
記憶體分配如下:
我們看到指標變數 b 可以訪問 a 的地址;( b 中的資料儲存的即為 a 的地址 )
c = &b;
記憶體分配如下:
是什麼意思呢?表示的是取 b 的地址賦給 c;(c 中的資料儲存的即為指標 b 的地址,指標 b 中的資料儲存的即為指標 a 的地址)
c 是什麼型別呢?顯然 c 是一個指標
這種表示式合法嗎?合法的,指標變數和其它變數一樣,佔據記憶體中某個特定的位置,所以用 & 操作符取得它的地址是合法的。
這個變數怎麼宣告呢?如下:
int** c;
表示變數 a 的地址的地址,也就是指標變數 b 的地址,為一個 int 型指標變數 **c 。
int a = 20;
int* b = &a;
int** c = &b;
表示式 | 相當的表示式 |
---|---|
a | 20 |
b | &a |
*b | a,20 |
c | &b |
*c | b,&a |
**c | *b,a,20 |
指標表示式
首先做以下宣告:
char ch = 'a';
char* cp = &ch;
我們有兩個變數,初始化如下:
宣告:以下虛線 ----- 表示無間接訪問操作;實線 —表示有間接訪問操作(*)。
很好理解:指標變數 cp 中的資料儲存的為變數 ch 的地址;
從一個簡單的表示式開始:
表示式 | 右值 | 左值 |
---|---|---|
ch |
當 ch 作為左值使用時,該表示式的值為該變數 ch 的值’a’;
當 ch 作為右值使用時,該表示式的值為該變數ch 記憶體的地址(而不是該地址包含的值);
表示式 | 右值 | 左值 |
---|---|---|
&ch | 非法 |
當 &ch 作為右值使用時,該表示式的值為變數 ch 的地址;(注意:這個值和變數 cp 中儲存的值是一樣的,但是這個表示式沒提到 cp,因此這個結果不是它產生的,這個地址存在於某個位置的資料裡,不一定是 cp );
當 &ch 作為左值使用時,該表示式是非法的,當表示式 &ch 進行求值時,它的結果肯定會存在於某個地方,但是我們無法知道它位於何處。可以結合 ## 未初始化和非法指標 ##的警告看看。
表示式 | 右值 | 左值 |
---|---|---|
cp |
當 cp 作為右值使用時,表示的就是 cp 的值;
當 cp 作為左值使用時,表示的就是 cp 所處的記憶體地址;
表示式 | 右值 | 左值 |
---|---|---|
&cp | 非法 |
當 &cp 作為右值使用時,表示的是指標變數 cp 的地址,型別是指向字元指標的指標;
當 &cp 作為左值使用時,是非法的。(同樣是儲存位置未清晰定義)
表示式 | 右值 | 左值 |
---|---|---|
*cp |
當 *cp 作為右值時,通過間接訪問操作訪問變數 ch 記憶體地址裡面的值,得到‘a’;
當 *cp 作為左值時,變數 ch 的地址;
表示式 | 右值 | 左值 |
---|---|---|
*cp + 1 | 非法 |
當 cp + 1 作為右值時,的優先順序高於+,所以首先執行間接訪問操作,得到的值為a,然後取得這個值的一份拷貝並把它與 1 相加,表示式的最終結果為字元b。
當 *cp + 1 作為左值時,不合法。(同樣是最終結果的儲存位置未清晰定義)
notice:優先順序表格證實+的結果不能作為左值。
表示式 | 右值 | 左值 |
---|---|---|
*(cp + 1) |
當 *(cp + 1) 作為右值時,得到的是 ch 之後的記憶體位置上的值;
當 *(cp + 1)作為左值時,得到的是 ch 之後的記憶體位置的地址(它本身);
表示式 | 右值 | 左值 |
---|---|---|
++cp | 非法 |
++ 和 – 操作符在指標變數中使用的相當頻繁;
當 ++cp 作為右值時,表示 ch 之後的位置的地址,字首 ++/-- 先增加/減少它的運算元的值再返回這個結果,因此 cp 本身和 ++cp 均指向 ch 之後的位置;
當 ++cp 作為左值時,是一個不合法的左值。(同樣是儲存位置不清晰,++cp 得到結果後沒有指定儲存位置)
表示式 | 右值 | 左值 |
---|---|---|
cp++ | 非法 |
當 cp++ 作為右值時,++ 操作先返回 cp 的值的一份拷貝然後再增加 cp 的值,因此這個表示式 cp++ 的值是 cp 原來的值的一份拷貝,仍指向 ch 。但是 cp 本身的值已經是指向 ch 的下一個位置。
當 ++cp 作為左值時,是一個不合法的左值。(同樣是儲存位置不清晰,cp++ 得到結果後沒有指定儲存位置)
表示式 | 右值 | 左值 |
---|---|---|
*++cp |
++/-- 的操作符優先順序要高於 * 操作符的優先順序。
當表示式 ++cp作為右值時,先做地址的自加操作,得到 ch 的下一個位置地址,然後通過間接訪問操作符,該表示式得到的是 ch 下一個位置上的值;
當表示式++cp作為左值時,得到的是 ch 下一個位置的地址;
表示式 | 右值 | 左值 |
---|---|---|
*cp++ |
警告:該表達時有點繞
++/-- 的操作符優先順序要高於 * 操作符的優先順序。
當表示式 *cp++作為右值時,先做地址的自加操作, cp 得到 ch 的下一個位置地址,但是表示式 cp++ 返回的仍是原地址 cp 的拷貝,ch 的地址(參考前面表示式),然後通過間接訪問操作符,該表示式得到的是 ch 位置上的值;
當表示式*cp++作為左值時,得到的是 ch 位置的地址;
優先順序表格顯示字尾 ++ 操作符的優先順序高於 * 操作符,這裡表示式看上去好像先是執行間接訪問操作。
事實上,這裡涉及 3 個步驟:
- ++ 操作符產生 cp 的一份拷貝;
- 然後 ++ 操作符增加 cp 的值;
- 最後,在 cp 的拷貝上執行間接訪問操作。
表示式 | 右值 | 左值 |
---|---|---|
++*cp | 非法 |
當表示式++*cp作為右值時,在這個表示式中,由於這兩個操作符的結合性都是從右向左,所以首先執行的是間接訪問操作。然後,cp 所指向的位置的值增加 1 ,變成 b,表示式的結果是這個增值後的值的一份拷貝,也就是b。
當表示式++*cp作為左值時,非法的。(同樣是儲存位置不清晰,沒有指定儲存位置)。
後面三個表示式應用較少
表示式 | 右值 | 左值 |
---|---|---|
(*cp)++ | 非法 |
字尾 ++ 操作符的優先順序高於 * 操作符。
該表示式(*cp)++作為右值時,先執行括號內的間接訪問操作,得到 ch 的值,然後執行 ++ 操作符,這時 產生一個拷貝,值為a,ch 的值會被修改為b,但是該表示式返回的值仍未拷貝的值 a。
該表示式(*cp)++作為左值時,非法。(原因同上)
以下兩個作為自己理解
表示式 | 右值 | 左值 |
---|---|---|
++*++cp | 非法 |
表示式 | 右值 | 左值 |
---|---|---|
++*cp++ | 非法 |
指標變數
指標變數: 本身也是一個變數,用來儲存在記憶體中的地址。
普通變數: 本身也是一個變數,用來儲存資料本身的值。
定義
因為C語言是一種相對自由形式的語言,相信你在一些指標程式碼中可以看到下面兩種定義,其實他們是等價的。第二種定義是不是看起來更舒服,更能理解指標的含義呢。其實可以看成一個指標變數pb,他的指標型別是int*。
int* pb;
等價於
int* pb;
但是下面這種看似很舒服的定義在下面這種情況可能會引起另一個誤解。下面是這種案例
int* pa, pb, pc;
等價於
int pb, pc, *pa;
等價於
int* pa;
int pb, pc;
現在可以理解定義了嗎,如果還是一頭霧水,不妨結合下面例子 去理解。
int pa = 10;
int* pb = &pa;
printf("value pa = %d\n", pa);
printf("value *pb = %d\n", *pb);
printf("value &pa = %d\n", &pa);
printf("value pb = %d\n", pb);
printf("value &pb = %d\n", &pb);
printf("value *&pb = %d\n", *&pb);
輸出
value pa = 10
value *pb = 10
value &pa = 651283788
value pb = 651283788
value &pb = 651283776
value *&pb = 651283788
我們可以看到*pb 和pa 是等價的,pb ,&pa,&*pb是等價的。