指針對于很多c語言初學(xué)者來說可能難以理解,一不小心可能被指針的指向關(guān)系繞進(jìn)去,在這里就對指針做一些總結(jié),寫一下自己的理解。 在程序中,我們聲明一個變量(int a = 1),將數(shù)據(jù)1存到變量a中,計算機內(nèi)部會將這個數(shù)據(jù)存到內(nèi)存(RAM)中,那么,數(shù)據(jù)存到某個地方,就會涉及地址。就像你買的快遞,快遞到了就要存到某個驛站里面放著,你的快遞就是一個數(shù)據(jù),驛站就是一個變量,這個驛站就要有地址,不然全國這么多驛站你怎么知道你的快遞在哪個驛站。 到這里,地址的概念應(yīng)該有了吧。 現(xiàn)在想想地址(比如0x0000 0001)不也是一個數(shù)據(jù)嗎,那么不也可以用一個變量存地址這個數(shù)據(jù)?是的,可以,這個變量就是指針,指針?biāo)褪谴鎯α硪粋€變量的內(nèi)存地址的一種數(shù)據(jù)類型,即指針的內(nèi)容就是另一個變量的內(nèi)存地址。 指針本身也是一個變量,所以指針變量也有自己的地址,只是它有點特殊,它存放的是另一個變量的地址而已,理解這句話就行。 前面講到過指針?biāo)且环N數(shù)據(jù)類型,為了方便,我們就規(guī)定在這種類型后面加*號表示該類型指針,有char型指針(char *)、double型指針(double *)和int型指針(int *)等等。 試著敲一下下面一段代碼,可以加深對指針的認(rèn)識: 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是打印地址(指針地址),是十六進(jìn)制的形式 C/C++ 中規(guī)定了 * 操作符來從對應(yīng)指針類型存放的地址中拿出相應(yīng)數(shù)據(jù),再定義一個變量int b = *p,指針p存了a的地址,*p就是拿出a的值,b的值就變成了1,*操作也被稱為解引用。
指針的運算是特別容易搞錯的,千萬不能以為和普通類型(比如int型數(shù)據(jù))的運算一樣。 指針的加減運算:
看輸出的結(jié)果就很容易看出規(guī)律,p指針指向a[0],特別注意p+1指針變成指向a[1],所以*(p+1) = a[1] = 2,而不是*(p+1) = a[0] + 1 = 2,當(dāng)然這里兩個答案湊巧一樣,但是把數(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)大一學(xué)c語言的時候,學(xué)到二級指針都已經(jīng)把我給繞暈了,如果當(dāng)時你給我寫個int ********p出來,我估計直接崩潰到放棄。 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位的,而指針用來存地址的。 最后,大家要明白一個概念,其實并沒有什么多級指針這種東西,多級指針就是個指針,稱為多級指針是為了我們方便表達(dá)而取的邏輯名稱。 二維數(shù)組其實和二級指針有著相似的理解方法: 比如a[3][2],把它理解成一個一維數(shù)組來看待,這個一維數(shù)組里面有三個元素,只是這個一維數(shù)組有點特殊,它的每個元素又是一個一維數(shù)組而已。 懂了上面這段話,二維數(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ù)組其它元素類似都可以輸出
1. 數(shù)組指針:指針在后,說明它就是個指針,所以數(shù)組指針指向的是數(shù)組,相當(dāng)于一次聲明了一個指針。從前面就已經(jīng)知道,二維數(shù)組a[3][2]中,a實質(zhì)上就是一個數(shù)組指針。 公式: 指向的那個數(shù)組的元素類型 (*指針名字)[指向的數(shù)組的元素個數(shù)] 2. 指針數(shù)組:數(shù)組在后,說明它就是個數(shù)組。字符數(shù)組是什么?就是存放字符的數(shù)組,那么指針數(shù)組就是存放指針類型的數(shù)組,相當(dāng)于一次聲明了多個指針。 公式: 數(shù)組元素的類型 數(shù)組名字[數(shù)組元素個數(shù)] char *a[3] = {'red', 'green', 'blue'}; char **pp = a; //定義二級指針pp, a本質(zhì)上相當(dāng)于二級指針 printf('%s\n', pp[0]); // 打印 red printf('%s\n', pp[1]); // 打印 green printf('%s\n', pp[2]); // 打印 blue 關(guān)于指針想寫的內(nèi)容還有很多,其實這只是開了個頭,比如:野指針、函數(shù)指針、函數(shù)參數(shù)傳遞方式、const 修飾指針、動態(tài)內(nèi)存分配: malloc 和 free、堆, 棧、內(nèi)存泄露......,以后再慢慢補齊。 指針在鏈表使用的比較多,多寫一些鏈表的操作會對指針理解很有幫助,鏈表節(jié)點的增加、刪除、修改、查找,單向鏈表、雙向鏈表、雙向循環(huán)鏈表、內(nèi)核鏈表等等。 |
|