C語言:關於指針學習以及理解
文章目錄
一、什麼是指針
首先,指針是一種數據類型,使用它定義的變量叫指針變量。指針變量是一個特殊的變量,它裏面存儲的數值被解釋成爲內存裏的一個地址,指針的值是一個地址。
指針的四個要素?:指針的類型、指針所指向的類型、指針的值 (指針所指向的內存區,即指向的地址) 、指針本身所佔據的內存區(指針本身的地址)。
(根據上圖)舉個例子:
int a=111;
int *p = &a;
上面兩句語句中可得出:
p 是指針變量
指針類型: p 是一個 int 類型的指針
指向類型: p 指向一個 int 類型的數據 a
指針的值: p 的值是0x30009 ( 即 p 指向的地址是0x30009 )
指針的地址:p 所佔據的內存區 (即 p 本身的地址爲0x30001)
所以要注意:指針的值 和 指針的地址 不是同一個東西,一個代表 p ,另一個代表&p 。
二、爲什麼使用指針、什麼情況下使用指針
1、函數之間無法通過傳參共享變量。
函數的形參變量屬於被調用於者,實參屬於調用者,函數之間的變量名字空間相互獨立是可以重名的,函數之間的數據傳遞都是值傳遞(賦值、內存拷貝)。
2、使用指針可以優化函數之間傳參的效率。(無需傳遞數組中所有參數,直接傳遞數組首地址)
3、堆內存無法與標識符建立聯繫,只能配合指針使用。
三、如何使用指針
? 定義:類型 * 變量名p;
1、指針變量與普通變量使用方法有很大區別,一般以 p 結尾,與普通變量區分開。
2、* 表示此變量是指針變量,一個 * 只能定義出一個指針變量,不能連續定義。
int* p1,p2,p3; // p1是指針,p2,p3是int類型變量
int *p1,*p2,*p3; // 三個指針變量
3、類型表示的是存儲是什麼類型變量的地址,它決定當通過地址訪問這塊內存時訪問的字節數。
4、指針變量的默認值也是不確定,一般初始化爲NULL(空指針)。
? 賦值操作:指針變量 = 地址
棧地址賦值:
int num = 0;
int* p = NULL;
p = #
堆地址賦值:
int* p = NULL;
p = malloc(4);
? 解引用(根據地址訪問內存): * 指針變量名 <=> 變量
1、根據變量中存儲的內存編號(即地址)去訪問內存中的數據,訪問多少個字節要根據指針變量的類型。
2、如果指針變量中存儲的地址出錯,此時可能發生段錯誤(這是賦值產生的錯誤)。
四、使用指針要注意的問題
? 空指針:
1、指針變量的值爲NULL(大多數是0,也有特殊情況是1),這種指針變量叫空指針,空指針不能進行解引用(* 指針變量),NULL被操作系統當作是復位地址(存儲了系統重啓所需要的數據),當操作系統察覺到程序訪問NULL位置的數以的時就會向程序發送段錯誤的信號,程序就會死亡。
2、空指針還被當作錯誤標誌,如果一個函數的返回值是指針類型,實際返回的值是NULL,則說明函數執行失敗或出錯。
在C語言代碼中應該杜絕對空指針進行解引用,當使用來歷不明的指針(調用者提供的)前應該先判斷是否爲NULL。
void func(int* p)
{
if(NULL == p)
{
//函數內容
}
}
? 野指針:指針變量的值是不確定的或都是無效的,這種指針叫野指針。
使用野指針不一定會出問題,可能產生的後果如下
1、一切正常
2、段錯誤
3、髒數據
-
雖然野指針不一定會出錯,但野指針比空指針的危險更大,因爲野指針是無法判斷出來、也無法測試出來,也就意味着一旦產生無法杜絕。
-
雖然野指針無法判斷也無法測試出來,但是所有的野指針都是人爲製造出來的,最好方法就是不生產野指針:
1、定義指針變量時要初始化。
2、不返回局部變量的地址。
3、資源釋放後,指向它的指針及時置空。
五、指針與數組的關係
數組名就是個指針(常指針),數組名與數組首地址是映射關係,而指針是指向關係。
由於數組名就是指針,所以數組名可以使用指針的解引用運算符,而指針也可以使用數組的 [ ] 運算符。
注意:使用數組當函數的參數時,數組會蛻變成指針,長度也就丟失,因此需要額外添加一個參數用來傳遞數組的長度。
六、指針的運算
指針的本質就是個整數,因此從語法上來說整數能使用的運算符它都能使用。
- 不是所有的運算符對指針運算都是有意義的。
指針+ 整數 <=> 指針+ 寬度* 整數 向右移動
指針 - 整數 <=> 指針 - 寬度* 整數 向左移動
指針 - 指針 <=> 指針 - 指針/寬度 計算出兩個指針之間相隔多少個元素。
七、指針與const配合
const int* p; 保護指針指向的數據,不能通過指針解引用修改指針指向的內存地址中的值。
int const *p; //同上
int * const p; 保護指針變量,指針變量(地址)初始化之後不能再顯式的賦值,即指針的值不能被顯示的修改。
const int *const p; 既不能修改指針的值,也不能修改指針指向的內存地址中的值。
int const * const p;//同上。
八、什麼是二級指針,什麼情況下使用
二級指針:指向指針的指針。
一級指針與二級指針的比較:一級指針的值爲地址,該值(即地址)需要空間來存放,存放在指針的地址中,是空間就具有地址,二級指針就是爲了獲取這一空間的地址。一級指針所關聯的是其值(一個地址)名下空間裏的數據,這個數據可以是任意類型並做任意用途,但二級指針所關聯的數據只有一個類型一個用途,就是地址。
一級指針(圖中 p): 其指針的值是一個變量的地址(a的地址),對其解引用得到的是一級指針所指向變量的值(a的值10086)。
二級指針 (圖中 pp):其指針的值是一個指針的地址(p的地址),對其解引用得到的是二級指針所指向的指針的值(p的值0x100A2)。
指針的用途:提供目標的讀取或改寫,而二級指針就是對於內存地址的讀取和改寫。
二級指針作爲函數參數的作用:在函數外部定義一個指針p,在函數內給指針賦值,函數結束後對指針p生效,那麼我們就需要二級指針。
九、指針函數和函數指針
函數具有可賦值給指針的物理內存地址,一個函數的函數名就是一個指針,它指向函數的代碼。一個函數的地址是該函數的進入點,也是調用函數的地址。函數的調用可以通過函數名,也可以通過指向函數的指針來調用。函數指針還允許將函數作爲變元傳遞給其他函數。
指針函數:簡單的來說,就是一個返回指針的函數,其本質是一個函數,而該函數的返回值是一個指針。
聲明格式爲:類型標識符 *函數名(參數表)
int fun(int x,int y);
這種函數應該都很熟悉,其實就是一個函數,然後返回值是一個 int 類型,是一個數值。
int * fun(int x,int y);
這和上面那個函數唯一的區別就是在函數名前面多了一個*號,而這個函數就是一個指針函數。
其返回值是一個 int 類型的指針,是一個地址。
這樣描述應該很容易理解了,所謂的指針函數也沒什麼特別的,和普通函數對比不過就是其返回了一個指針(即地址值)而已。
函數指針:其本質是一個指針變量,該指針指向這個函數。總結來說,函數指針就是指向函數的指針。
聲明格式:類型說明符 (*函數名) (參數)
int (*fun)(int x,int y);
函數指針是需要把一個函數的地址賦值給它,有兩種寫法:
fun = &Function;
fun = Function;
取地址運算符&不是必需的,因爲一個函數標識符就表示了它的地址,
如果是函數調用,還必須包含一個圓括號括起來的參數表。
調用函數指針的方式也有兩種:
x = (*fun)();
x = fun();
兩種方式均可,其中第二種看上去和普通的函數調用沒啥區別,如果可以的話,建議使用第一種,因爲可以清楚的指明這是通過指針的方式來調用函數。當然,也要看個人習慣,如果理解其定義,隨便怎麼用都行啦。
十、數組指針和指針數組
數組指針(也稱行指針)
定義 int (*p)[n];
()優先級高,首先說明p是一個指針,指向一個整型的一維數組,這個一維數組的長度是n,也可以說是p的步長。也就是說執行p+1時,p要跨過n個整型數據的長度。
如要將二維數組賦給一指針,應這樣賦值:
int a[3][4];
int (*p)[4]; //該語句是定義一個數組指針,指向含4個元素的一維數組。
p=a; //將該二維數組的首地址賦給p,也就是a[0]或&a[0][0]
p++; //該語句執行過後,也就是p=p+1;p跨過行a[0][]指向了行a[1][]
所以數組指針也稱指向一維數組的指針,亦稱行指針。
指針數組
定義 int * p[n];
[]優先級高,先與p結合成爲一個數組,再有int* 說明這是一個整型指針數組,它有n個指針類型的數組元素。這裏執行p+1時,則p指向下一個數組元素,這樣賦值是錯誤的:p=a;因爲p是個不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它們分別是指針變量可以用來存放變量地址。但可以這樣 * p=a; 這裏*p表示指針數組第一個元素的值,a的首地址的值。
如要將二維數組賦給一指針數組:
int *p[3];
int a[3][4];
p++; //該語句表示p數組指向下一個數組元素。注:此數組每一個元素都是一個指針
for(i=0;i<3;i++)
p[i]=a[i]
這裏int *p[3] 表示一個一維數組內存放着三個指針變量,分別是p[0]、p[1]、p[2]所以要分別賦值。
這樣兩者的區別就豁然開朗了,數組指針只是一個指針變量,似乎是C語言裏專門用來指向二維數組的,它佔有內存中一個指針的存儲空間。指針數組是多個指針變量,以數組形式存在內存當中,佔有多個指針的存儲空間。
優先級:()>[]>*
(近期更新中…)