深入理解C语言指针
一、指針的概念
要知道指針的概念,要先了解變量在內(nèi)存中如何存儲(chǔ)的。在存儲(chǔ)時(shí),內(nèi)存被分為一塊一塊的。每一塊都有一個(gè)特有的編號(hào)。而這個(gè)編號(hào)可以暫時(shí)理解為指針,就像酒店的門牌號(hào)一樣。
1.1、變量和地址
先寫一段簡(jiǎn)單的代碼:
void main(){int x = 10, int y = 20; }這段代碼非常簡(jiǎn)單,就是兩個(gè)變量的聲明,分別賦值了 10、20。我們把內(nèi)存當(dāng)做一個(gè)酒店,而每個(gè)房間就是一塊內(nèi)存。那么“int x = 10;”和“int y = 20;”的實(shí)際含義如下:
1.2、指針變量和指針的類型
指針變量就是一個(gè)變量,它存儲(chǔ)的內(nèi)容是一個(gè)指針。如果用前面的例子,可以理解為指針變量就是一張房卡,房卡存儲(chǔ)了房間號(hào)的信息。
在我們定義一個(gè)變量的時(shí)候,要確定它的類型。int x、char ch、float、、、在定義指針變量時(shí)也是一樣的,必須確定指針類型。int 變量的指針需要用 int 類型的指針存儲(chǔ),float 變量的指針需要用 float 類型的指針存儲(chǔ)。就像你只能用酒店 A 的房卡存儲(chǔ)酒店 A 中房間號(hào)的信息一樣。
二、變量的指針與指針變量
變量的指針就是變量的存儲(chǔ)地址,指針變量就是存儲(chǔ)指針的變量。
2.1、指針變量的定義及使用
(1)指針變量的定義
指針變量的定義形式如:數(shù)據(jù)類型 *指針名;例如:
//分別定義了 int、float、char 類型的指針變量 int *x; float *f; char *ch;如上面的定義,指針變量名為 x、f、ch。并不是*x、*f、*ch
(2)指針變量的使用
- 取地址運(yùn)算符&:單目運(yùn)算符&是用來(lái)取操作對(duì)象的地址。例:&i 為取變量 i 的地址。對(duì)于常量表達(dá)式、寄存器變量不能取地址(因?yàn)樗鼈兇鎯?chǔ)在存儲(chǔ)器中,沒(méi)有地址)。
- 指針運(yùn)算符*(間接尋址符):與&為逆運(yùn)算,作用是通過(guò)操作對(duì)象的地址,獲取存儲(chǔ)的內(nèi)容。例:x = &i,x 為 i 的地址,*x 則為通過(guò) i 的地址,獲取 i 的內(nèi)容。
代碼示例:
//聲明了一個(gè)普通變量 a int a; //聲明一個(gè)指針變量,指向變量 a 的地址 int *pa; //通過(guò)取地址符&,獲取 a 的地址,賦值給指針變量 pa = &a; //通過(guò)間接尋址符,獲取指針指向的內(nèi)容 printf("%d", *pa);(3)“&”和“*”的結(jié)合方向
“&”和“*”都是右結(jié)合的。假設(shè)有變量 x = 10,則*&x 的含義是,先獲取變量 x 的地址,再獲取地址中的內(nèi)容。因?yàn)椤?amp;”和“*”互為逆運(yùn)算,所以 x = *&x。
接下來(lái)做個(gè)小練習(xí),輸入 x、y 兩個(gè)整數(shù),然后將其中的值大的賦值給 x,小的賦值給 y。即:假設(shè)輸入 x = 8,y = 9。就將 9 賦值給 x,8 賦值給 y。
void main(){//聲明兩個(gè)普通變量int x, y;//聲明兩個(gè)指針變量int *px, *py;//聲明一個(gè)臨時(shí)變量,用于交換int t;//輸入兩個(gè)值,賦值給 x、yscanf("%d", &x);scanf("%d", &y);//給指針變量 px、py 賦初值(關(guān)聯(lián)變量 x、y)px = &x;py = &y;//利用指針來(lái)對(duì)比 x、y 的值,如果 x 的值比 y 的值小,就交換if(*px < *py){//交換步驟,其中*px == x、*py == yt = *px;*px = *py;*py = t;}printf("x = %d, y = %d", *px, *py); } 輸入:23 45 輸出結(jié)果為:x = 45, y = 232.2、指針變量的初始化
指針變量與其它變量一樣,在定義時(shí)可以賦值,即初始化。也可以賦值“NULL”或“0”,如果賦值“0”,此時(shí)的“0”含義并不是數(shù)字“0”,而是 NULL 的字符碼值。
//利用取地址獲取 x 的地址,在指針變量 px 定義時(shí),賦值給 px int x; int *px = &x; //定義指針變量,分別賦值“NULL”和“0” int *p1= NULL, *p2 = 0;2.3、指針運(yùn)算
(1)賦值運(yùn)算
指針變量可以互相賦值,也可以賦值某個(gè)變量的地址,或者賦值一個(gè)具體的地址
int *px, *py, *pz, x = 10; //賦予某個(gè)變量的地址 px = &x; //相互賦值 py = px; //賦值具體的地址 pz = 4000;(2)指針與整數(shù)的加減運(yùn)算
(3)關(guān)系運(yùn)算
假設(shè)有指針變量 px、py。
三、指針與數(shù)組
之前我們可以通過(guò)下標(biāo)訪問(wèn)數(shù)組元素,學(xué)習(xí)了指針之后,我們可以通過(guò)指針訪問(wèn)數(shù)組的元素。在數(shù)組中,數(shù)組名即為該數(shù)組的首地址,結(jié)合上面指針和整數(shù)的加減,我們就可以實(shí)現(xiàn)指針訪問(wèn)數(shù)組元素。
3.1、指向數(shù)組的指針
如以下語(yǔ)句:
int nums[10], *p;上面語(yǔ)句定義了一個(gè)數(shù)組 nums,在定義時(shí)分配了 10 個(gè)連續(xù)的int 內(nèi)存空間。而一個(gè)數(shù)組的首地址即為數(shù)組名nums,或者第一個(gè)元素的首地址也是數(shù)組的首地址。那么有兩種方式讓指針變量 p 指向數(shù)組 nums:
//數(shù)組名即為數(shù)組的首地址 p = nums; //數(shù)組第一個(gè)元素的地址也是數(shù)組的首地址 p = &nums[0];上面兩句是等價(jià)的。
如下幾個(gè)操作,用指針操作數(shù)組:
下面寫一段代碼,用指針訪問(wèn)數(shù)組的元素:
//定義一個(gè)整形數(shù)組,并初始化 int nums[5] = {4, 5, 3, 2, 7};//定義一個(gè)指針變量 p,將數(shù)組 nums 的首地址賦值給 p,也可以用p = &nums[0]賦值 int *p = nums, i; //i 作為循環(huán)變量//p 指向數(shù)組第一個(gè)元素(數(shù)組首地址),我們可以直接用間接尋址符,獲取第一個(gè)元素的內(nèi)容 printf("nums[0] = %d\n", *p); //輸出結(jié)果為 nums[0] = 4//我們可以通過(guò)“p + 整數(shù)”來(lái)移動(dòng)指針,要先移動(dòng)地址,所以 p + 1 要擴(kuò)起來(lái) printf("nums[1] = %d\n", *(p + 1)); //輸出結(jié)果為 nums[1] = 5//由上面推導(dǎo)出*(p + i) = nums[i],所以我們可以通過(guò) for 循環(huán)變量元素 for(i = 0; i < 5; i++){printf("nums[%d] = %d", i, *(p + i)); }注:數(shù)組名不等價(jià)于指針變量,指針變量可以進(jìn)行 p++和&操作,而這些操作對(duì)于數(shù)組名是非法的。數(shù)組名在編譯時(shí)是確定的,在程序運(yùn)行期間算一個(gè)常量。
3.2、字符指針與字符數(shù)組
在 C 語(yǔ)言中本身沒(méi)有提供字符串?dāng)?shù)據(jù)類型,但是可以通過(guò)字符數(shù)組和字符指針的方式存儲(chǔ)字符串。
(1)字符數(shù)組方式
這個(gè)在前面應(yīng)該學(xué)習(xí)過(guò),這里就不贅述了。
char word[] = "zack"; printf("%s", word);(2)字符指針?lè)绞?/strong>
指針?lè)绞讲僮髯址蛿?shù)組操作字符串類似,可以把定義的指針看做是字符數(shù)組的數(shù)組名。在內(nèi)存中存儲(chǔ)大致如下,這里為了方便換了個(gè)字符串:
//除了定義一個(gè)字符數(shù)組外,還可以直接定義一個(gè)字符指針存儲(chǔ)字符串 char *sentence = "Do not go gentle into that good night!";//此時(shí)可以做字符串的操作 //輸出 printf("%s", sentence);//通過(guò)下標(biāo)取字符 printf("%c", sentence[0]);//獲取字符串長(zhǎng)度,其中 strlen 是 string.h 庫(kù)中的方法 printf("%d", strlen(sentence));注:字符指針?lè)绞絽^(qū)別于字符數(shù)組方式,字符數(shù)組不能通過(guò)數(shù)組名自增操作,但是字符指針是指針,可以自增操作。自增自減少會(huì)實(shí)現(xiàn)什么效果大家可以自己嘗試運(yùn)行一下
下面做個(gè)小練習(xí),利用字符指針將字符數(shù)組 sentence 中的內(nèi)容復(fù)制到字符數(shù)組 word 中:
//定義字符數(shù)組 sentence 和 word,給 sentence 賦初值 char sentence[] = "Do not go gentle into that good night!", word[100];//定義字符指針,指向 word char *ch = word; int i;//循環(huán)賦值 for(i = 0; sentence[i] != '\0'; i++){*(ch + i) = sentence[i]; }//在當(dāng) i 等于 sentence 的長(zhǎng)度(sentence 的長(zhǎng)度不包含'\0')時(shí), //i 繼續(xù)自增,此時(shí)判斷 sentence[0] != '\0'不符合,跳出循環(huán),則 i 比 sentence 長(zhǎng)度大 1 *(ch + i) = '\0';//輸出字符串,因?yàn)?ch 指向 word,所以輸出結(jié)果是一樣的 printf("ch = %s, word = %s", ch, word);注:指針變量必須初始化一個(gè)有效值才能使用
3.3、多級(jí)指針及指針數(shù)組
(1)多級(jí)指針
指針變量作為一個(gè)變量也有自己的存儲(chǔ)地址,而指向指針變量的存儲(chǔ)地址就被稱為指針的指針,即二級(jí)指針。依次疊加,就形成了多級(jí)指針。我們先看看二級(jí)指針,它們關(guān)系如下:
其中 p 為一級(jí)指針,pp 為二級(jí)指針。二級(jí)指針定義形式如下:
和指針變量的定義類似,由于*是右結(jié)合的,所以*pp 相當(dāng)于*(*p)。在本次定義中,二級(jí)指針的變量名為 pp,而不是**p。多級(jí)指針的定義就是定義時(shí)使用多個(gè)“*”號(hào)。下面用一個(gè)小程序給大家舉例:
//定義普通變量和指針變量 int *pi, i = 10; //定義二級(jí)指針變量 int **ppi;//給指針變量賦初值 pi = &i;//給二級(jí)指針變量賦初值 ppi = π//我們可以直接用二級(jí)指針做普通指針的操作 //獲取 i 的內(nèi)容 printf("i = %d", **ppi); //獲取 i 的地址 printf("i 的地址為%d", *ppi);注:在初始化二級(jí)指針 ppi 時(shí),不能直接 ppi = &&i,因?yàn)?amp;i 獲取的是一個(gè)具體的數(shù)值,而具體數(shù)字是沒(méi)有指針的。
(2)指針數(shù)組
指針變量和普通變量一樣,也能組成數(shù)組,指針數(shù)組的具體定義如下:
數(shù)據(jù)類型 *數(shù)組名[指針數(shù)組長(zhǎng)度];下面舉一個(gè)簡(jiǎn)單的例子熟悉指針數(shù)組:
//定義一個(gè)數(shù)組 int nums[5] = {2, 3, 4, 5, 2}, i;//定義一個(gè)指針數(shù)組 int *p[5];//定義一個(gè)二級(jí)指針 int **pp;//循環(huán)給指針數(shù)組賦值 for(i = 0; i < 5; i++){p[i] = &nums[i]; }//將指針數(shù)組的首地址賦值給 pp,數(shù)組 p 的數(shù)組名作為 p 的首地址,也作為 p 中第一個(gè)元素的地址。 //數(shù)組存放的內(nèi)容為普通變量,則數(shù)組名為變量的指針;數(shù)組存放的內(nèi)容為指針,則數(shù)組名為指針的指針。 pp = p;//利用二級(jí)指針 pp 輸出數(shù)組元素 for(i = 0; i < 5; i++){//pp == &p[0] == &&nums[0],nums[0] == *p[0] == **ppprintf("%d", **pp);//指針變量+整數(shù)的操作,即移動(dòng)指針至下一個(gè)單元pp++; }3.4、指針與多維數(shù)組
講多維數(shù)組是個(gè)麻煩的事,因?yàn)槎嗑S數(shù)組和二維數(shù)組沒(méi)有本質(zhì)的區(qū)別,但是復(fù)雜度倒是高了許多。這里我主要還是用二維數(shù)組來(lái)舉例,但是還是會(huì)給大家分析多維數(shù)組和指針的關(guān)系。
(1)多維數(shù)組的地址
先用一個(gè)簡(jiǎn)單的數(shù)組來(lái)舉例:
int nums[2][2] = {{1, 2},{2, 3} };我們可以從兩個(gè)維度來(lái)分析:
我們知道數(shù)組名即為數(shù)組首地址,上面的二維數(shù)組有兩個(gè)維度。首先我們把按照上面 1 來(lái)理解,那么 nums 就是一個(gè)數(shù)組,則nums 就作為這個(gè)數(shù)組的首地址。第二個(gè)維度還是取 nums[0],我們把 nums[0]作為一個(gè)名稱,其中有兩個(gè)元素。我們可以嘗試以下語(yǔ)句:
printf("%d", nums[0]);此語(yǔ)句的輸出結(jié)果為一個(gè)指針,在實(shí)驗(yàn)過(guò)后,發(fā)現(xiàn)就是 nums[0][0]的地址。即數(shù)組第一個(gè)元素的地址。
如果再多一個(gè)維度,我們可以把二維數(shù)組看做一種數(shù)據(jù)類型 y,而三維數(shù)組就是一個(gè)變量為 y 的一維數(shù)組。而數(shù)組的地址我們要先確定是在哪個(gè)維度,再將數(shù)組某些維度看成一個(gè)整體,作為名稱,此名稱就是該維度的地址(這里有些繞)。
例:
//假設(shè)已初始化,二維數(shù)組數(shù)據(jù)類型設(shè)為 x,一維數(shù)組數(shù)據(jù)類型設(shè)為 y int nums[2][2][2];//此數(shù)組首地址為該數(shù)組名稱 printf("此數(shù)組首地址為%d", nums);//此數(shù)組可以看做存儲(chǔ)了兩個(gè) x 類型元素的一維數(shù)組,則 nums[0] = x1 的地址為 printf("第二個(gè)維度的首地址為%d", nums[0]);//而 x1 可以看做存儲(chǔ)了兩個(gè) y 類型元素的一維數(shù)組,則 y1 = x1[0] = nums[0][0] printf("第三個(gè)維度的首地址為%d", nums[0][0]);三維數(shù)組實(shí)際存儲(chǔ)形式如下:
實(shí)際存儲(chǔ)內(nèi)容的為最內(nèi)層維度,且為連續(xù)的。對(duì)于 a 來(lái)說(shuō),其個(gè)跨度為 4 個(gè)單元;對(duì) a[0]來(lái)說(shuō),其跨度為 2 個(gè)單元;對(duì) a[0][0]來(lái)說(shuō),跨度為一個(gè)單元。有上面還可以得出:
上面的等式只是數(shù)值上相等,性質(zhì)不同。
(2)多維數(shù)組的指針
在學(xué)習(xí)指針與數(shù)組的時(shí)候,我們可以如下表示一個(gè)數(shù)組:
int nums[5] = {2, 4, 5, 6, 7}; int *p = nums;在前面講指針數(shù)組時(shí),所有指針數(shù)組元素都指向一個(gè)數(shù)字,那么我們現(xiàn)在可以嘗試用指針數(shù)組的每個(gè)元素指向一個(gè)數(shù)組:
//定義一個(gè)二維數(shù)組 int nums[2][2] = {{1, 2},{2, 3} };//此時(shí) nums[0]、和 nums[1]各為一個(gè)數(shù)組 int *p[2] = {nums[0], nums[1]};//我們可以用指針數(shù)組 p 操作一個(gè)二維數(shù)組//p 為數(shù)組 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0] printf("nums[0][0] = %d", **p);//指針 + 整數(shù)形式,p+1 移動(dòng)到 nums 的地址,*(p +1) = nums[1],則**(p + 1) = nums[1][0] printf("nums[1][0] = %d", **(p + 1));//先*p = nums[0],再*p + 1 = &nums[0][1],最后獲取內(nèi)容*(*p + 1)即為 nums[0][1] printf("nums[0][1] = %d", *(*p + 1));這里可能不能理解為什么*p + 1 = &nums[0][1],而不是 nums[1]。*p 獲得的是一個(gè)一維數(shù)組,而 int 數(shù)組 + 1 的跨度只有 4 個(gè)字節(jié),也就是一個(gè)單元。前面 p 是一維數(shù)組的指針,其跨度為一個(gè)數(shù)組。所以*p + 1 = &nums[0][1],而 p + 1 = nums[1]。
四、指針與函數(shù)
前面學(xué)習(xí)函數(shù)學(xué)到,函數(shù)參數(shù)可以為 int、char、float 等,但是在操作時(shí),這些參數(shù)只作為形參,所有操作都只在函數(shù)體內(nèi)有效(除對(duì)指針的操作外),那么今天來(lái)學(xué)習(xí)一下指針作為函數(shù)參數(shù)。
4.1、函數(shù)參數(shù)為指針
我們直接做一個(gè)練習(xí),定義一個(gè)函數(shù),用來(lái)交換兩個(gè)變量的內(nèi)容。
void swap(int *x, int *y); void main(){int x = 20, y = 10;swap(&x, &y);printf("x = %d, y = %d", x ,y); } void swap(int *x, int *y){int t;t = *x;*x = *y;*y = t; }代碼非常簡(jiǎn)單,我也就不細(xì)講了。這里傳入的參數(shù)為指針,所以調(diào)用 swap 方法后 x,y 的內(nèi)容發(fā)生了交換。如果直接傳入 x,y,那么交換只在 swap 中有效,在 main 中并沒(méi)有交換。
4.2、函數(shù)的返回值為指針
返回值為指針的函數(shù)聲明如下:
數(shù)據(jù)類型 *函數(shù)名(參數(shù)列表){函數(shù)體 } //例如: int s; int *sum(int x, int y){s = x + y;return &s; }在函數(shù)調(diào)用前要聲明需要對(duì)函數(shù)聲明(有點(diǎn)編譯器不需要)
int s; void mian(){int *r = sum(10, 9);printf("10 + 9 + %d", *r); } int *sum(int x, int y){s = x + y;return &s; }除了上面的操作,更實(shí)用的是返回一個(gè)指向數(shù)組的指針,這樣就實(shí)現(xiàn)了返回值為數(shù)組。
4.3、指向函數(shù)的指針
C 語(yǔ)言中,函數(shù)不能嵌套定義,也不能將函數(shù)作為參數(shù)傳遞。但是函數(shù)有個(gè)特性,即函數(shù)名為該函數(shù)的入口地址。我們可以定義一個(gè)指針指向該地址,將指針作為參數(shù)傳遞。
函數(shù)指針定義如下:
數(shù)據(jù)類型 (*函數(shù)指針名)();函數(shù)指針在進(jìn)行“*”操作時(shí),可以理解為執(zhí)行該函數(shù)。函數(shù)指針不同與數(shù)據(jù)指針,不能進(jìn)行+整數(shù)操作。
下面舉個(gè)例子,來(lái)使用函數(shù)指針:
#include <string.h> /** * 定義一個(gè)方法,傳入兩個(gè)字符串和一個(gè)函數(shù)指針 p,用 p 對(duì)兩個(gè)字符串進(jìn)行操作 */ void check(char *x, char *y, int (*p)()); void main(){//string.h 庫(kù)中的函數(shù),使用之前需要聲明該函數(shù)。字符串比較函數(shù)int strcmp();char x[] = "Zack";char y[] = "Rudy";//定義一個(gè)函數(shù)指針int (*p)() = strcmp;check(x, y, p); } void check(char *x, char *y, int (*p)()){if(!(*p)(x, y)){printf("相等");}else{printf("不相等");} }利用函數(shù)指針調(diào)用方法具體操作如下:
(*p)(x, y);指針除了這些地方,還在結(jié)構(gòu)體中用處巨大。今天就先講到這里~·
總結(jié)
- 上一篇: Protel99SE多张原理图的设计步骤
- 下一篇: 明小子注入工具+啊D注入工具+御剑后台扫