二級指針、一維數(shù)組與指針,二維數(shù)組與指針
二級指針
指針可以指向一個普通類型的數(shù)據(jù),例如 int、double、char 等,也可以指向一個指針類型的數(shù)據(jù),例如 int *、double *、char * 等。
如果一個指針指向的是另外一個指針,我們就稱它為二級指針,或者指向指針的指針。
假設(shè)有一個 int 類型的變量 age,page是指向 age 的指針變量,ppage 又是指向 page 的指針變量,它們的關(guān)系如下圖所示:

將這種關(guān)系轉(zhuǎn)換為C語言代碼:
int age =28;int *page = &age;int **ppage = &page;
指針變量也是一種變量,也會占用存儲空間,也可以使用&獲取它的地址。C語言不限制指針的級數(shù),每增加一級指針,在定義指針變量時就得增加一個星號*。page 是一級指針,指向普通類型的數(shù)據(jù),定義時有一個*;ppage 是二級指針,指向一級指針 page,定義時有兩個*。
如果我們希望再定義一個三級指針 p3age,讓它指向 ppage,那么可以這樣寫:
int ***p3age = &ppage;四級指針也是類似的道理:
int ****p4age = &p3age;實(shí)際開發(fā)中會經(jīng)常使用一級指針和二級指針,幾乎用不到高級指針。
想要獲取指針指向的數(shù)據(jù)時,一級指針加一個*,二級指針加兩個*,三級指針加三個*,以此類推,請看代碼:
#includeint main(){int a =100;int *p1 = &a;int **p2 = &p1;int ***p3 = &p2;printf("%d, %d, %d, %d\n", a, *p1, **p2, ***p3);printf("&p2 = %#X, p3 = %#X\n", &p2, p3);printf("&p1 = %#X, p2 = %#X, *p3 = %#X\n", &p1, p2, *p3);printf(" &a = %#X, p1 = %#X, *p2 = %#X, **p3 = %#X\n", &a, p1, *p2, **p3);return 0;}
一維數(shù)組指針
數(shù)組(Array)是一系列具有相同類型的數(shù)據(jù)的集合,每一份數(shù)據(jù)叫做一個數(shù)組元素(Element)。數(shù)組中的所有元素在內(nèi)存中是連續(xù)排列的,整個數(shù)組占用的是一塊內(nèi)存。以int arr[] = { 99, 15, 100, 888, 252 };為例,該數(shù)組在內(nèi)存中的分布如下圖所示:

定義數(shù)組時,要給出數(shù)組名和數(shù)組長度,數(shù)組名可以認(rèn)為是一個指針,它指向數(shù)組的第 0 個元素。在C語言中,我們將第 0 個元素的地址稱為數(shù)組的首地址。以上面的數(shù)組為例,下圖是 arr 的指向:

數(shù)組下標(biāo)為啥從0開始?
數(shù)組下標(biāo)實(shí)際上是每個元素的地址相對于第一個元素地址的偏移量


訪問數(shù)組元素除了可以通過下標(biāo)法之外,還可以通過指針法訪問。
下標(biāo)法
for(int i = 0;i < 6;i++)
{
? ?printf("%d ",arr[i]);
? ?//printf("%d ",i[arr]);
}指針法
for(int i = 0;i < 6;i++)
{
? ?printf("%d ",*(arr + i));
}這就是為什么在某些地方大家會看到 i[arr] 這種訪問數(shù)組元素的方法的原因,實(shí)際上下標(biāo)法就是通過指針法來實(shí)現(xiàn)的,只不過編譯器幫助我們做了這個操作,簡化了操作難度。
指向數(shù)組的指針
我們也可以定義一個指向數(shù)組的指針,例如:
int arr[] = { 99, 15, 100, 888, 252 };
int *p = arr;arr 本身可以看做是一個指針,可以直接賦值給指針變量 p。arr 是數(shù)組第 0 個元素的地址,所以int *p = arr;也可以寫作int *p = &arr[0];。也就是說,arr、p、&arr[0] 這三種寫法都是等價的,它們都指向數(shù)組第 0 個元素,或者說指向數(shù)組的開頭。
如果一個指針指向了數(shù)組,我們就稱它為數(shù)組指針(Array Pointer)。
數(shù)組指針指向的是數(shù)組中的一個具體元素,而不是整個數(shù)組,所以數(shù)組指針的類型和數(shù)組元素的類型有關(guān),上面的例子中,p 指向的數(shù)組元素是 int 類型,所以 p 的類型必須也是int *。
反過來想,p 并不知道它指向的是一個數(shù)組,p 只知道它指向的是一個整數(shù),究竟如何使用 p 取決于程序員的編碼。
使用指針訪問數(shù)組元素和使用函數(shù)名沒有任何區(qū)別,值得注意的是我們不同通過指針獲得數(shù)組的大小,但是通過數(shù)組名卻可以。
printf("%d\n",sizeof(arr)); //數(shù)組所占字節(jié)數(shù) 20 Byte
printf("%d\n",sizeof(p)); //指針?biāo)甲止?jié)數(shù) 4 Byte也就是說,根據(jù)數(shù)組指針不能逆推出整個數(shù)組元素的個數(shù),以及數(shù)組從哪里開始、到哪里結(jié)束等信息。不像字符串,數(shù)組本身也沒有特定的結(jié)束標(biāo)志,如果不知道數(shù)組的長度,那么就無法遍歷整個數(shù)組。
關(guān)于數(shù)組指針的謎題
假設(shè) p 是指向數(shù)組 arr 中第 n 個元素的指針,那么 *p++、*++p、(*p)++ 分別是什么意思呢?
*p++ 等價于 *(p++),表示先取得第 n 個元素的值,再將 p 指向下一個元素。
*++p 等價于 *(++p),會先進(jìn)行 ++p 運(yùn)算,使得 p 的值增加,指向下一個元素,整體上相當(dāng)于 *(p+1),所以會獲得第 n+1 個數(shù)組元素的值。
(*p)++ 就非常簡單了,會先取得第 n 個元素的值,再對該元素的值加 1。假設(shè) p 指向第 0 ?個元素,并且第 0 個元素的值為 99,執(zhí)行完該語句后,第 0 ?個元素的值就會變?yōu)?100。
數(shù)組名和數(shù)組指針的區(qū)別
雖然說數(shù)組名可以當(dāng)做指針使用,但實(shí)際上數(shù)組名并不等價于指針。
數(shù)組名代表的是整個數(shù)組,具有確定數(shù)量的元素
指針是一個標(biāo)量,不能確定指向的是否是一個數(shù)組
數(shù)組可以在某些情況下會自動轉(zhuǎn)換為指針,當(dāng)數(shù)組名在表達(dá)式中使用時,編譯器會把數(shù)組名轉(zhuǎn)換為一個指針常量,是數(shù)組中的第一個元素的地址,類型就是數(shù)組元素的地址類型(通過sizeof也可以看出來)
二維數(shù)組指針
二維數(shù)組可以理解為每一個元素都是一個一維數(shù)組的數(shù)組,這樣就可以很好的理解二維數(shù)組與指針了。
下面定義了一個2行3列的二維數(shù)組,并畫出了對應(yīng)的內(nèi)存模型。

我們可以使用arr[0]獲得第0個一維數(shù)組,然后再加上一個小標(biāo)就可以獲取到對應(yīng)的元素,如arr[0][0]獲取了第0行第0列的元素。
