c語言指針數組講解(零基礎學C語言知識總結十)
2023-04-21 11:25:40 4
二級指針 (多級指針)指針變量作為一個變量也有自己的存儲地址,而指向指針變量的存儲地址就被稱為指針的指針,即二級指針。依次疊加,就形成了多級指針。指針可以指向一份普通類型的數據,例如 int、double、char 等,也可以指向一份指針類型的數據,例如 int *、double *、char * 等。如果一個指針指向的是另外一個指針,我們就稱它為二級指針,或者指向指針的指針。,我們先看看二級指針,它們關係如下:
int a =100;//一個普通變量
int *p1 = //一個一級指針p1指向a變量的地址
int **p2 = &p1;//一個二級指針p2指向p1指針的地址
// p2 -> p1 -> a
// &p1 &a 100
/*規律:
一級指針 指向變量的地址
二級指針 指向一級指針的地址
三級指針 指向二級指針的地址
依次類推....
指針變量也是一種變量,也會佔用存儲空間,也可以使用&獲取它的地址。C語言不限制指針的級數,每增加一級指針,在定義指針變量時就得增加一個星號*。p1 是一級指針,指向普通類型的數據,定義時有一個*;p2 是二級指針,指向一級指針 p1,定義時有兩個*。
多級指針的話就是:
int ***p3 = &p2;//三級指針
int ****p4 = &p3;//四級指針
int *****p5 = &p4;//五級指針
//實際開發中會經常使用一級指針和二級指針,幾乎用不到高級指針。
想要獲取指針指向的數據時,一級指針加一個*,二級指針加兩個*,三級指針加三個*關係如下:
#include
int main {
int a = 100;
int *p1 =
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;
}
//以三級指針 p3 為例來分析上面的代碼。***p3等價於*(*(*p3))。*p3 得到的是 p2 的值,
也即 p1 的地址;*(*p3) 得到的是 p1 的值,也即 a 的地址;經過三次「取值」操作後,*(*(*p3))
得到的才是 a 的值。
指針數組、指向函數的指針、指向二維數組的指針指針數組:指針變量和普通變量一樣,也能組成數組,如果一個數組中的所有元素保存的都是指針,那麼我們就稱它為指針數組。指針數組的定義形式一般為:
數據類型 * 名字 [數組長度];
這裡注意 [ ]的優先級比 * 來得高
int *a [10];
這裡說明a是一個數組,包含了10個元素,每個元素的類型為int *。
除了每個元素的數據類型不同,指針數組和普通數組在其他方面都是一樣的,下面是一個簡單的例子:
#include
int main(void) {
int a = 1;
int b = 2;
int c = 3;
//定義一個指針的數組
int *an[3] = { &a,&b,&c };//由於裡邊每一個元素都是指針,所以利用取地址符&,指向abc三個變量
//這裡定義一個指向指針數組的指針,由於數組已經是指針了,所以要用到二級指針
int **p = an;//由於數組本身就是表示一個地址所以不用取地址符&
printf("%d %d %d\n", *an[0], *an[1], *an[2]);
printf("%d %d %d\n", **(p 0) , **(p 1), **(p 2));
return 0;
}
//arr 是一個指針數組,它包含了 3 個元素,每個元素都是一個指針,在定義 arr 的同時,
我們使用變量 a、b、c 的地址對它進行了初始化,這和普通數組是多麼地類似。
parr 是指向數組 arr 的指針,確切地說是指向 arr 第 0 個元素的指針,
它的定義形式應該理解為int *(*parr),括號中的*表示 parr 是一個指針,
括號外面的int *表示 parr 指向的數據的類型。arr 第 0 個元素的類型為 int *,
所以在定義 parr 時要加兩個 *。
指針數組還可以和字符串數組結合使用:
#include
int main{
char *str[3] = { //定義一個字符串數組 長度為3
"c.biancheng.net",
"C語言中文網",
"C Language"
};
printf("%s\n%s\n%s\n", str[0], str[1], str[2]);//依次輸出每個字符串
return 0;
}
指向函數的指針:一個函數總是佔用一段連續的內存區域,函數名在表達式中有時也會被轉換為該函數所在內存區域的首地址,這和數組名非常類似。我們可以把函數的這個首地址(或稱入口地址)賦予一個指針變量,使指針變量指向函數所在的內存區域,然後通過指針變量就可以找到並調用該函數。這種指針就是函數指針。
數據類型 *指針名 (數據類型 參數);
數據為函數返回值類型,指針名稱,括號裡邊為函數參數列表。參數列表中可以同時給出參數的類型和名稱,
也可以只給出參數的類型,省略參數的名稱,這一點和函數原型非常類似。
注意( )的優先級高於*,第一個括號不能省略,如果寫作returnType *pointerName(param list);就成了函數原型,它表明函數的返回值類型為returnType *
用指針來實現對函數的調用:
#include
//返回兩個數中較大的一個
int max(int a, int b){
return a>b ? a : b;
}
int main(void){
int x, y, maxval;
//定義指向函數指針*pmax
int (*pmax)(int, int) = max; //也可以寫作int (*pmax)(int a, int b)
//要注意的是定義必須和函數形式一致
printf("Input two numbers:");
scanf("%d %d", &x, &y);
maxval = (*pmax)(x, y);//將函數調用並指針賦值
printf("Max value: %d\n", maxval);
return 0;
}
// maxval 對函數進行了調用。pmax 是一個函數指針,在前面加 * 就表示對它指向的函數進行調用。
注意( )的優先級高於*,第一個括號不能省略。
指向二維數組的指針:
(復盤一下二維數組的知識)二維數組在概念上是二維的,有行和列,但在內存中所有的數組元素都是連續排列的,它們之間沒有「縫隙」。以下面的二維數組 a 為例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
a就是像一個矩陣:
0 1 2 3
4 5 6 7
8 9 10 11
但在內存中,a 的分布是一維線性的,整個數組佔用一塊連續的內存:
【0】【1】【2】【3】【4】【5】【6】【7】【8】【9】【10】【11】
C語言中的二維數組是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最後存放 a[2] 行;每行中的 4 個元素也是依次存放。數組 a 為 int 類型,每個元素佔用 4 個字節,整個數組共佔用 4×(3×4) = 48 個字節。
C語言把一個二維數組分解成多個一維數組來處理。對於數組 a,它可以分解成三個一維數組,即 a[0]、a[1]、a[2]。每一個一維數組又包含了 4 個元素,例如 a[0] 包含`a[0][0] 、a[0][1]、a[0][2]、a[0][3]。
為了更好的理解指針和二維數組的關係,我們先來定義一個指向 a 的指針變量 p:
int (*p)[4] = a;
//括號中的*表明 p 是一個指針,它指向一個數組,數組的類型為int [4],
這正是 a 所包含的每個一維數組的類型。
[ ]的優先級高於*,( )是必須要加的,如果赤裸裸地寫作int *p[4],那麼應該理解為int *(p[4]),p 就成了一個指針數組,而不是二維數組指針。
對指針進行加法(減法)運算時,它前進(後退)的步長與它指向的數據類型有關,p 指向的數據類型是int [4],那麼p 1就前進 4×4 = 16 個字節,p-1就後退 16 個字節,那麼這正好是數組 a 所包含的每個一維數組的長度。也就是說,p 1會使得指針指向二維數組的下一行,p-1會使得指針指向數組的上一行。
按照上面的定義,我們來看看代碼:
#include
int main(void){
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a;
printf ( "%d\n", sizeof(*(p 1)) );//這裡輸出是16
return 0;
}
*(p 1) 1表示第 1 行第 1 個元素的地址:*(p 1)單獨使用時表示的是第 1 行數據,放在表達式中會被轉換為第 1 行數據的首地址,也就是第 1 行第 0 個元素的地址,因為使用整行數據沒有實際的含義,編譯器遇到這種情況都會轉換為指向該行第 0 個元素的指針;就像一維數組的名字,在定義時或者和 sizeof、& 一起使用時才表示整個數組,出現在表達式中就會被轉換為指向數組第 0 個元素的指針。
*(*(p 1) 1)表示第 1 行第 1 個元素的值。很明顯,增加一個 * 表示取地址上的數據:**
規律:
a i == p i
a[i] == p[i] == *(a i) == *(p i)
a[i][j] == p[i][j] == *(a[i] j) == *(p[i] j) == *(*(a i) j) == *(*(p i) j)
#include
int main(void) {
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
int i, j;
int(*p)[4] = a;//定義一個指向二維數組的指針p
for (i = 0; i < 3; i ) {
for (j = 0; j < 4; j ) {
printf("%d ", *(*(p i) j));//利用二級指針就可以訪問到i行j列的元素
} //*(p i):一維數組
printf("\n"); //*(*(p i) j) 二維數組
}
return 0;
}
/*輸出:
1 2 3 4
5 6 7 8
9 10 11 12
數組名 a 在表達式中也會被轉換為和 p 等價的指針!
指針數組和二維數組指針在定義時非常相似,但是括號的位置不同所表示的意思也就天壤之別:
int *(p1[5]); //指針數組,可以去掉括號直接寫作 int *p1[5];
int (*p2)[5]; //二維數組指針,不能去掉括號
指針數組和二維數組指針有著本質上的區別:
指針數組是一個數組,只是每個元素保存的都是指針,以上面的 p1 為例,在32位環境下它佔用 4×5 = 20 個字節的內存。二維數組指針是一個指針,它指向一個二維數組,以上面的 p2 為例,它佔用 4 個字節的內存。
至於多維數組和二維數組沒有本質的區別,但是複雜度倒是高了許多。一般不常用。
結束語:
程序在運行過程中需要的是數據和指令的地址,變量名、函數名、字符串名和數組名在本質上是一樣的,它們都是地址的助記符:在編寫代碼的過程中,我們認為變量名表示的是數據本身,而函數名、字符串名和數組名表示的是代碼塊或數據塊的首地址;程序被編譯和連結後,這些名字都會消失,取而代之的是它們對應的地址。指針就是存放地址的一種變量。
常見的的指針:
1、 指針變量可以進行四則運算。指針變量的加減運算並不是簡單的加上或減去一個整數,而是跟指針指向的數據類型與地址有關。
2、給指針變量賦值時,要將一份數據的地址賦給它,不能直接賦給一個整數,例如int *p = 1000;是沒有意義的,使用過程中一般會導致程序崩潰。
3、使用指針變量之前一定要初始化,否則就不能確定指針指向哪裡,如果它指向的內存沒有使用權限,程序就崩潰了。對於暫時沒有指向的指針,直接賦值NULL讓它變為空指針。
4、數組也是有類型的,數組名的本意是表示一組類型相同的數據。在定義數組時,或者和 sizeof、& 運算符一起使用時數組名才表示整個數組,表達式中的數組名會被轉換為一個指向數組的指針。
指針的用法暫時就這些,C指針大法這些才是入門!繼續加油咯~
作者:Mr_Li_
對啦對啦!另外的話為了幫助大家,輕鬆,高效學習C語言/C ,我給大家分享我收集的資源,從最零基礎開始的教程到C語言項目案例,幫助大家在學習C語言的道路上披荊斬棘!可以來我粉絲群領取哦~
編程學習書籍分享:
編程學習視頻分享:
整理分享(多年學習的源碼、項目實戰視頻、項目筆記,基礎入門教程)最重要的是你可以在群裡面交流提問編程問題哦!
對於C/C 感興趣可以關注小編在後臺私信我:【編程交流】一起來學習哦!可以領取一些C/C 的項目學習視頻資料哦!已經設置好了關鍵詞自動回復,自動領取就好了!
,