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