二級指針 (多級指針)
指針變量作為一個變量也有自己的存儲地址,而指向指針變量的存儲地址就被稱為指針的指針,即二級指針。依次疊加,就形成了多級指針。指針可以指向一份普通類型的數據,例如 int、double、char 等,也可以指向一份指針類型的數據,例如 int *、double *、char * 等。如果一個指針指向的是另外一個指針,我們就稱它為二級指針,或者指向指針的指針。,我們先看看二級指針,它們關系如下:
int a =100;//一個普通變量 int *p1 = &a;//一個一級指針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;//五級指針 //實際開發中會經常使用一級指針和二級指針,幾乎用不到高級指針。
想要獲取指針指向的數據時,一級指針加一個*,二級指針加兩個*,三級指針加三個*關系如下:
#includeint main() { int a = 100; int *p1 = &a; int **p2 = &p1; int ***p3 = &p2; printf("%d, %d, %d, %d ", a, *p1, **p2, ***p3);//他們的值都是一樣的 printf("&p2 = %#X, p3 = %#X ", &p2, p3);//所指向的地址也是一樣的 printf("&p1 = %#X, p2 = %#X, *p3 = %#X ", &p1, p2, *p3); printf(" &a = %#X, p1 = %#X, *p2 = %#X, **p3 = %#X ", &a, p1, *p2, **p3); return 0; } //以三級指針 p3 為例來分析上面的代碼。***p3等價于*(*(*p3))。*p3 得到的是 p2 的值, 也即 p1 的地址;*(*p3) 得到的是 p1 的值,也即 a 的地址;經過三次“取值”操作后,*(*(*p3)) 得到的才是 a 的值。
指針數組、指向函數的指針、指向二維數組的指針
指針數組:
指針變量和普通變量一樣,也能組成數組,如果一個數組中的所有元素保存的都是指針,那么我們就稱它為指針數組。指針數組的定義形式一般為:
數據類型 * 名字 [數組長度]; 這里注意 [ ]的優先級比 * 來得高 int *a [10]; 這里說明a是一個數組,包含了10個元素,每個元素的類型為int *。
除了每個元素的數據類型不同,指針數組和普通數組在其他方面都是一樣的,下面是一個簡單的例子:
#includeint 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 ", *an[0], *an[1], *an[2]); printf("%d %d %d ", **(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 時要加兩個 *。
指針數組還可以和字符串數組結合使用:
#includeint main(){ char *str[3] = { //定義一個字符串數組 長度為3 "c.biancheng.net", "C語言中文網", "C Language" }; printf("%s %s %s ", 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 ", 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會使得指針指向數組的上一行。
按照上面的定義,我們來看看代碼:
#includeint main(void){ int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; int (*p)[4] = a; printf ( "%d ", 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) #includeint 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指針大法這些才是入門!繼續加油咯~
-
存儲
+關注
關注
13文章
4266瀏覽量
85686 -
函數
+關注
關注
3文章
4308瀏覽量
62445 -
指針
+關注
關注
1文章
480瀏覽量
70512 -
數組
+關注
關注
1文章
416瀏覽量
25913
原文標題:【零基礎學C語言】知識總結十:二級指針、指針數組和指向函數的指針
文章出處:【微信號:cyuyanxuexi,微信公眾號:C語言編程學習基地】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論