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