C語言進階:指針的進階(2)
數組指針
由前面的例子,不難得出,數組指針是指向數組的指針,是指針而非數組。
數組指針的定義
char ch = 'w'; char* pch = &ch;//字符地址存放在字符指針中 int a = 10; int* pint = &a;//整型地址存放在整型指針中 float f = 0.0; float* pf = &f;//浮點型地址存放在浮點型指針中
什麼變量的地址存放在什麼指針中。指針指向變量的類型,決定瞭指針的類型。顧名思義,數組指針指向的是數組。
遞推可得,數組的地址存放在數組指針中。且數組指針的類型為數組的類型再加個* 。
下面那種定義方法是對的呢?
int arr[10] = { 0 }; //1. int* pa = arr; //2. &arr;//整個數組的地址 int* parr = &arr; //3. int* parr[10] = &arr; //4. int(*parr)[10] = &arr;
取出的是首元素的地址,而非整個數組的地址
整型指針應存放整型變量的地址,數組的地址無法存入整型指針中。
[]的優先級比*高,故parr先與[]結合成數組名,所以parr是個指針數組。
數組指針的類型由數組類型決定,先找出數組的類型int[10](
去掉名就是類型)。且不能讓[]先與parr結合,所以用()先將parr和*結合,即成int(*parr)[10]。
C語言規定[]必須再最後面,所以不可寫成
int[10](*parr)。
int* parr[10];//指針數組 int(*parr)[10];//數組指針
我們前面強調過,去掉名字就是類型。所以int[10]是整型數組的類型,int*[10]是指針數組的類型,int(*)[10]是數組指針的類型。
&數組名和數組名
之前介紹過不止一遍,所以這次隻說重點。
指針類型決定瞭指針±整數的步長。
//首元素地址+1 printf("%p\n", arr);//0073FCA4 printf("%p\n", arr + 1);//0073FCA8 //整個數組地址+1 printf("%p\n", &arr);//0073FCA4 printf("%p\n", &arr + 1);//0073FCCC
1.首元素地址就是整型指針+1,自然隻能向後訪問4shou個字節
2.整個數組地址+1,即int(*)[10]型指針+1,向後訪問瞭 i n t × 10 int×10 int×10即40個字節。
sizeof(arr)
也代表整個數組,現在去理解為什麼sizeof裡數組名代表的是整個數組呢?數組這種結構保存瞭數組的大小,sizeof求所占空間的長度,那自然要嚴謹一些瞭。
數組指針的使用
遍歷數組,使用數組或是指針作形參接收就行瞭。且所謂的用數組接收僅是理解層面,本質上都是指針。
void Print1(int arr[], int sz) { for (int i = 0; i < sz; i++) { //printf("%d ", arr[i]); printf("%d ", *(arr + i)); } } void Print2(int* arr, int sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); //printf("%d ", *(arr + i)); } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); Print1(arr, sz); Print2(arr, sz); return 0; }
反面用例
數組作實參,用數組或指針接收即可。數組指針使用對瞭很好用,但如果隨便用可能會很別扭。下面先介紹強行使用數組指針的用法。
//錯誤示范 void Print3(int(*pa)[10], int sz) { for (int i = 0; i < sz; i++) { //printf("%d ", pa[i]); printf("%d ", *(pa + i)); } }
將整個數組地址傳過去,則用數組指針接收,然後呢,直接對pa解引用嗎?
結果顯然是錯誤的,從結果中也可以看出打印的是十進制下的地址,+1跳過40個字節。
這裡筆者在學習的時候產生瞭個疑問,傳過去數組的地址,為什麼解一層引用後還是地址呢?
&arr解引用*後相當於找到首元素的地址,可以理解為&和*相互抵消隻剩下arr不就是首元素的地址嘛~
void Print4(int(*pa)[10], int sz) { for (int i = 0; i < sz; i++) { printf("%d ", *(*(pa)+j)); } }
倘若我們把一維數組看作是二維數組第一行。由於二維數組在內存中是連續存放的,我們隻打印二維數組的第一行,便可以避免上面的錯誤。
style=“zoom:80%;” />
*(pa)
相當於數組指針所指向數組的數組名。數組指針指向整個數組,將其看作二維數組並解引用得到一行的首元素,從而遍歷訪問。
正面用例
從上面的例子也可以看出,用數組指針訪問二維數組時,效果便不錯。
//二維數組傳參,用二維數組接收 void Print1(int arr[3][5], int r, int c) { for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { //printf("%d ", arr[i][j]); printf("%d ", *(*(arr + i) + j)); } printf("\n"); } }
上面的例子,是正常二維數組傳參,二維數組接收的情況。下面我們用數組指針接收。
//二維數組傳參,用數組指針接收 void Print2(int(*pa)[5], int r, int c) { for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { //1. printf("%d ", pa[i][j]); //2. printf("%d ", *(*(pa + i) + j)); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 }; Print2(arr, 3, 5);//二維數組首元素是首行 return 0; }
- 把二維數組想象成一個擁有三個元素的一維數組(每個元素也為一維數組),即一維數組的一維數組。
- 由於其每個元素是有5個元素的一維數組,數組指針定義為int(*p)[5],指向首行這個“一維數組”。(傳參穿的是數組名)
- 第一層循環用於“跳行”,即每次跳過5個元素。第二層循環遍歷每行“一維數組”。
用二維數組和數組指針接收的都是首行地址。
數組指針的類型int(*)[5],和二維數組首元素地址的類型相同。
故可得,二維數組首元素地址和數組指針是等價的,即數組指針pa就是數組名。
二維數組首元素為其首行,相當於一個一維數組,該一維數組的地址類型為int(*)[5]
。且實參為二維數組名,降級為指向首行的指針,所以它是數組指針,類型為int(*)[5]。
數組指針指向二維數組,才是使用數組指針的正確示范。
Example
下列示例分別是什麼?
//1. int arr[5]; //2. int *pa1[5]; //3. int (*pa2)[10]; //4. int (*pa3[10])[5];
1.整型數組
2.存放整型指針的數組
*靠左靠右無所謂,pa1先和[]結合為數組,剩下int*為數組元素類型。
3.指向整型數組的指針
(*pa2),*先和pa2結合為指針,剩下int[10],指向的是元素個數為10的整型數組。
4.存放數組指針的數組
pa3先和[10]結合為數組,剩下int(*)[5]是指向數組的指針為數組的元素。所以是個元素個數為10的數組指針數組。
逆向思考,有整型數組arr[5]和指向該數組的類型為int(*)[5]的數組指針,還有數組指針數組pa3[10]用於存放該數組指針。
類型辨別方法
1.若名稱先和[]結合為數組,隻去掉數組名就是數組類型,去掉[n]和數組名便是其元素的類型。
2.若名稱先和*結合為指針,隻去掉指針名就是指針類型,去掉*和指針名便是指向的變量的類型。
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!