指針使得 C 語言能夠更高效地實現對計算機底層硬件的操作,而計算機硬件的操作很大程度上依賴地址,指針便提供了一種對地址操作的方法,在一定意義上,指針是c語言的精髓,所以一定要耐心看完。
指針對于很多c語言初學者來說可能難以理解,一不小心可能被指針的指向關系繞進去,在這里就對指針做一些總結,寫一下自己的理解。
一. 指針的介紹 ?
在程序中,我們聲明一個變量(int a = 1),將數據1存到變量a中,計算機內部會將這個數據存到內存(RAM)中,那么,數據存到某個地方,就會涉及地址。就像你買的快遞,快遞到了就要存到某個驛站里面放著,你的快遞就是一個數據,驛站就是一個變量,這個驛站就要有地址,不然全國這么多驛站你怎么知道你的快遞在哪個驛站。
到這里,地址的概念應該有了吧。
現在想想地址(比如0x0000 0001)不也是一個數據嗎,那么不也可以用一個變量存地址這個數據?是的,可以,這個變量就是指針,指針它就是存儲另一個變量的內存地址的一種數據類型,即指針的內容就是另一個變量的內存地址。
指針本身也是一個變量,所以指針變量也有自己的地址,只是它有點特殊,它存放的是另一個變量的地址而已,理解這句話就行。
前面講到過指針它是一種數據類型,為了方便,我們就規定在這種類型后面加*號表示該類型指針,有char型指針(char *)、double型指針(double *)和int型指針(int *)等等。
試著敲一下下面一段代碼,可以加深對指針的認識:
int a = 1; // 定義一個int型變量 int?*p?=?&a;?? //?定義一個int型指針p,&a表示對a取地址,指針p的內容是a的地址 //?int??*p;???p = &a;????//?第二行也可以這樣寫,意思一樣 printf("%p ",?&a);???? //?打印a的地址 printf("%p ",?p);????? //?打印指針p指向的地址 // %p是打印地址(指針地址),是十六進制的形式
C/C++ 中規定了 * 操作符來從對應指針類型存放的地址中拿出相應數據,再定義一個變量int b =?*p,指針p存了a的地址,*p就是拿出a的值,b的值就變成了1,*操作也被稱為解引用。
二. 指針的相關操作(運算) ?
算數運算:+、-、++、--、
指針的運算是特別容易搞錯的,千萬不能以為和普通類型(比如int型數據)的運算一樣。
指針的加減運算:
指針+1/指針-1,加/減的是整個指針類型的長度,與其說指針的加減法,我認為不如說成指針的偏移更合適,接下來看為什么是偏移,舉個非常明顯的例子:
char?a[5]?=?{1,?2,?3,?4,?5};??//?定義一個char型數組,這里的a實質上是一個指針,指向這個數組的首元素a[0]的指針 char *p = a; printf("%d ",?*p);????????//?輸出1?-->?a[0] printf("%d ",?*(p?+?1));??//?輸出2?-->?a[1] ......
看輸出的結果就很容易看出規律,p指針指向a[0],特別注意p+1指針變成指向a[1],所以*(p+1)? = a[1] = 2,而不是*(p+1)? = a[0]?+ 1 = 2,當然這里兩個答案湊巧一樣,但是把數組的內容換一下就不會是一樣了,如果是改成(*p)?+ 1,那么就是(*p)?+ 1 = a[0]?+ 1?= 2,同理可以改成p+2、p+3......
還有試著定義其它類型的數組(比如int型:int a[5] = {1, 2, 3, 4, 5};),看看是不是這個規律,就可以知道指針加減的是這個指針類型的長度,也就是指針的偏移,還可以嘗試定義結構體數組,將會有更深的理解。
減法就不用多說了,理解了指針p+1/p-1,那么指針p++/p--其實是一樣的,都是偏移。
三. 多級指針 ?
說起多級指針這個東西,曾經大一學c語言的時候,學到二級指針都已經把我給繞暈了,如果當時你給我寫個int ********p出來,我估計直接崩潰到放棄。
我們先來說說二級指針吧!前面有講到,指針也是一種數據類型,是一種變量,也有自己的地址,所以既然有地址,而指針就是存放另一個變量的地址的呀,那為什么不能再用一個指針存放這個指針的地址呢,對吧!所以就有了二級指針,就是指向指針的指針。
ok!來點生活上的東西,快遞柜大家都用過吧,快遞小哥給你發一個取件碼你就能拿到快遞。
?
這里的每一個柜子就是一塊內存,取件碼就是地址,柜子里的快遞就是存儲在內存的內容/數據。
假如快遞小哥把你的快遞放到"058柜子",給你發取件碼,那么你輸入取件碼就可以取到快遞。
如果快遞小哥逗你一下,故意給你發"057柜子"的取件碼,然后在"057柜子"放一張紙條,上面寫:快遞在058柜子,這時候你肯定是按照紙條從"058柜子"里就可以拿到快遞。
這里的"057柜子"就是指針,指針里面存放另一個變量(058柜子)的地址。
如果快遞小哥給你發"056柜子"的取件碼,在"056柜子"里放一張紙條寫:快遞在"057柜子"里,又在"057柜子"里放一張紙條寫:快遞在"058柜子"里。
這里的"056柜子"就是二級指針,"057柜子"就是指針,"058柜子"就是指針存放的另一個變量。
現在明白了二級指針吧,那么,N級指針也就那樣,也就是指向指針的指針的指針的指針的指針........,是不是非常簡單!
int a = 1; int *p = &a; int **pp = &p; // 二級指針pp存放指針p的地址,即二級指針pp指向指針p int?***ppp?=?&pp;? //?三級指針ppp存放二級指針pp的地址,即三級指針ppp指向二級指針pp ......
總之,如果一個內存如果存放的是另一個變量的地址,那么就叫指針。一塊內存要么存放實際內容/數據,要么存放的是另一個變量的地址,確實是剛剛所說的非常簡單。
【總結兩點】:
?1. 指針本身也是一個變量,也有自己的地址,需要內存存儲。?
?2. 指針存放的是所指向的變量的地址,這個所指向的變量也可以是一個指針。
【特別注意】:面試可能被問到指針的大小
?1. 指針的大小跟指針是什么類型的沒有任何關系。
?2. 在32為系統系統中,所有的指針大小都是4個字節,原因是32系統上所有變量的地址都是32位的,而指針用來存地址的。
最后,大家要明白一個概念,其實并沒有什么多級指針這種東西,多級指針就是個指針,稱為多級指針是為了我們方便表達而取的邏輯名稱。
四. 多維數組 ?
二維數組其實和二級指針有著相似的理解方法:
比如a[3][2],把它理解成一個一維數組來看待,這個一維數組里面有三個元素,只是這個一維數組有點特殊,它的每個元素又是一個一維數組而已。
懂了上面這段話,二維數組就很好理解。 ? 前面我們已經知道一維數組a[3]中,a實質上是一個指針,指向這個數組首元素a[0]:
int a[3] = {1, 2, 3}; //?a[0]??-->??*a printf("%d ",?*a);???????//?打印?1??-->??a[0]?的值 //?a[1]??-->??*(a?+?1) printf("%d ",?*(a?+?1));?//?打印?2??-->??a[1]?的值 //?a[2]??-->??*(a?+?2) printf("%d ",?*(a?+?2));?//?打印?3??-->??a[2]?的值? 那么,二維數組a[3][2]當成一維數組看是不是可以得出:
int?a[3][2]?=?{{1,?2},?{3, 4},?{5,?6}}; //?a[0][0]??-->??(*a)[0] printf("%d ",?(*a)[0]);???????//?打印?1??-->??a[0][0]?的值 //?a[1][0]??-->??(*(a?+?1))[0] printf("%d ",?(*(a?+?1))[0]);?//?打印?3??-->??a[1][0]?的值 //?a[2][0]??-->??(*(a?+?2))[0] printf("%d ",?(*(a?+?2))[0]);?//?打印?5??-->??a[2][0]?的值 //?a[2][1]??-->??(*(a?+?2))[1] printf("%d ",?(*(a?+?2))[1]);?//?打印?6??-->??a[2][1]?的值 //?.....?二維數組其它元素類似都可以輸出? 結論一:a[m][n]? 等價于?(*(a + m)[n]??-->就是一個數組指針(后面會提到) ? 基于前面兩種指針和數組的變換,可以繼續得出:
int?a[3][2]?=?{{1, 2}, {3, 4}, {5, 6}}; //?a[0][0]??-->??(*a)[0]??-->??*(*a?+?0)? -->??把?*a?當成整體 printf("%d ",?*(*a));??????? //?打印?1??-->??a[0][0]?的值 //?a[1][0]??-->??(*(a?+?1))[0]??-->??*(*(a?+?1)?+?0) printf("%d ",?*(*(a?+?1)));?? //?打印?3??-->??a[1][0]?的值 //?a[2][0]??-->??(*(a?+?2))[0]??-->??*(*(a?+?2)?+?0) printf("%d ",?*(*(a?+?2)));?? //?打印?5??-->??a[2][0]?的值 //?a[2][1]??-->??(*(a?+?2))[1]??-->??*(*(a?+?2)?+?1) printf("%d ",?*(*(a?+?2)?+?1));?//?打印?6??-->??a[2][1]?的值 // ..... 二維數組其它元素類似都可以輸出? 結論二:a[m][n]? ?等價于? ?*(*(a + m) + n) ? ? 五. 數組指針與指針數組 ?
1. 數組指針:指針在后,說明它就是個指針,所以數組指針指向的是數組,相當于一次聲明了一個指針。從前面就已經知道,二維數組a[3][2]中,a實質上就是一個數組指針。
公式:
指向的那個數組的元素類型? (*指針名字)[指向的數組的元素個數]
2. 指針數組:數組在后,說明它就是個數組。字符數組是什么?就是存放字符的數組,那么指針數組就是存放指針類型的數組,相當于一次聲明了多個指針。
公式:
數組元素的類型? 數組名字[數組元素個數]
char?*a[3]?=?{"red",?"green",?"blue"}; char?**pp?=?a;????//定義二級指針pp,?a本質上相當于二級指針 printf("%s ",?pp[0]);????//?打印 red printf("%s ", pp[1]); // 打印 green printf("%s ",?pp[2]);????//?打印?blue? 直觀上區分數組指針和指針數組的方法: 由于數組指針的 [] 比 * 的優先級高,所以數組指針的指針加括號,所以看看指針有沒有用圓括號括起來,就可以區分開。 ? ?
六. 其它??
關于指針想寫的內容還有很多,其實這只是開了個頭,比如:野指針、函數指針、函數參數傳遞方式、const 修飾指針、動態內存分配: malloc 和 free、堆, 棧、內存泄露......,以后再慢慢補齊。
指針在鏈表使用的比較多,多寫一些鏈表的操作會對指針理解很有幫助,鏈表節點的增加、刪除、修改、查找,單向鏈表、雙向鏈表、雙向循環鏈表、內核鏈表等等。
編輯:黃飛
?
評論