数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用
一、數組指針和指針數組
1.數組指針(行指針)
首先要知道數組指針是指向數組的指針。所以數組指針本質是個指針,只不過指向一個數組而已。格式為:T (*ptr)[]。
注意:"[]" 優先級高于 " * " ,本質得是指針,所以" * "得優先與ptr在一起,所以必須用()。
在繼續講述數組指針知識之前先來講講普通一維數組和普通指針。
(1)一維數組和普通指針
arr兩個元素之間的地址相差為4個字節(因為元素是int類型,一個int占4個字節)。
對于p[1]就相當于 * (p+1)
對于arr[1]就相當于 *(arr+1)
即將指針向后偏移一個元素單元(在這里即是4個字節)。然后對指針向后偏移過后的地址解引用。注意:這里指針偏移,指針沒有改變指向,而是用表達式p+1代表偏移后的地址。
類似,如果我們想要得到arr[2],arr[3]的值的話,可以用一下語句:
上面語句都能正常使用。
再來說說p+1偏移多少字節量的問題。
p+1偏移多少字節量和指針p類型有關,如果p是int*類型,那么p+1就向代表后偏移4個字節量的位置,如果p是char類型,那么p+1就代表向后偏移1個字節量的位置。如下代碼:
發現這個時候p+n代表的是p向后偏移n*sizeof(char)的字節量。arr[0]地址是20183592,arr[1]地址是20183596。arr[0]這個元素占有四個字節大小的空間,只不過數據放在了第一個字節。(這個跟大小端也有點關系,小端是低地址存放低字節,大端是高地址存放低字節,平時我們用的電腦操作系統這些都是小端,網絡字節序列是大端,換句話說如果是大端的話,那么1存放在(p+3)里面,也就是存放在四個字節的后一個字節空間)。
普通數組和指針的關系說了,那么再回來說說數組指針。
(2)數組指針和二維數組
下面講述數組指針將拿int (*ptr)[]舉例。
int(*ptr)[5];
這個語句是定義一個指針,這個指針指向一個int類型的整形數組,注意是指向整個int類型的整形數組,換句話說p+1的時候,p要跨過5個整形數據的長度。
運行結果:
ptr在代碼中的使用也發現了,其實ptr就是一個二級指針,只不過int(* ptr)[5]表明了這個二級指針第一次解引用后的一級指針代表多少空間。也就是 *ptr代表的是int[5]整個空間,即 *ptr是一個指向整個五個int類型空間的指針。相當于 int *p;int arr[5];p = arr; (這里的等價于 *ptr),所以 *ptr可以像普通一維數組和一級指針那樣訪問數組。*ptr就是普通的一級指針,可以和(1)中的那樣訪問數組元素。比如(1)中的p[2]和 * ( p+2)同樣的效果,所以( * ptr)[2]和 * ( *ptr+2)是一樣的效果,即用 *ptr代替本就是一級指針的p而已。
如下代碼及運行結果:
注意*ptr和普通一級指針操一樣,沒什么區別。int(ptr)[5]中的[5]對ptr解依次引用后的一級指針ptr沒有限制。[5]主要是對二級指針ptr起作用的。
上面都是對ptr解一次引用后的操作,其實解一次引用后,和普通一級指針操作一樣。下面看二級指針ptr的操作。
代碼:
二級指針如果我們用[][]形式訪問元素,那么和二位數組是一樣。而且arr+1我們知道是跳到下一行數組。那么ptr+1也是跳到下一行數組,ptr和arr區別就是,ptr沒有指定有多少行,指定了一行有幾個即[5]指定了一行有五個,其實[5]的作用就在ptr這里,ptr+n就是向后偏移了n * sizeof(int*5)個字節。
運行結果:
(注意:實際上二維數組的的空間是一直連續的,這個圖只是為了突出二維數組的形態)。
再來看下面的訪問方式:
int main() {int(*ptr)[5];int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };ptr = a;printf("%d\n", *(ptr[0]+1));//==*(*(ptr+0)+1)==ptr[0][1]printf("%d\n", *(ptr[1] + 1));//==*(*(ptr+1)+1)==ptr[1][1]printf("%d\n", *(ptr + 1)[2]);//**(ptr+1+2)==*ptr[3]超出范圍printf("%d\n", (*(ptr + 1))[2]);//ptr[1][2]return 0; }運行結果:
上面都可以看作是對二級指針的操作。ptr和二級指針的區別就在于[5]上面的5。所以遇到數組指針把數組指針就看成多了個[n]信息的二級指針就好了。
T *ptr[n]意思是ptr+1偏移n個sizeof(T)字節。就比二級指針多了這一個信息。而ptr只能用 int ( * )[n]類型賦值。
比如
int(*ptr) [5]
int arr[4][4];
ptr = arr;//error
int(*ptr) [5]
int arr[4][5];
ptr = arr;//OK
int(*ptr) [5]
int arr[5];
ptr = &arr;//OK
也就是說用不論是讓ptr指向一維數組(ptr是二級指針,所以一維數組首地址得&取地址)還是二維數組,必須滿足一行元素是5個,即列數和數組指針的[n]中的n值相對應。
2.指針數組
首先要知道指針數組是存放指針的數組。所以指針數組本質是個數組,只不過存放的是指針而已。格式為:T * arr[]。
注意:" [] " 優先級高于 " * "所以不需要(),當然如果T* (arr[])這樣寫更清晰,只是沒必要。
下面講述指針數組將拿int* arr[]舉例。
指針數組比數組指針理解起來簡單多了。因為本質就是一個數組,里面存的是指針變量。
(注意:本代碼沒有考慮內存釋放問題)
運行結果:
發現a元素存的地址值和p存的地址值一樣。說明我們用p指針初始化指針數組a的時候,是字節拷貝的方式(可以看成是類和對象問題里的淺拷貝),把p里面存放的地址值一個一個字節的拷貝到指針數組的元素。
(1)注意arr[0]、arr[1]、arr[2]最開始保存的內存地址。
打印出來發現是CCCCCCC,搜了一下,說VS編譯器(微軟的做法)開辟棧幀時,會使用CCCCCCC填充棧幀空間。也就是arr[0]、arr[1]、arr[2]都是在棧幀上的,換句話說,指針數組存放在棧幀上,編譯器給指針數組開辟棧幀了,本來這些空間都會存放一個堆地址,堆空間里放的時是int類型的數據(僅對我們的代碼而言),但是由于一開始我們沒有初始化指針數組,所以里面元素值都是CCCCCCC。
(2)注意以下寫法
arr[0] = (int*)40;
*arr[0] = 40;
這兩個寫法是不可取的。
arr[0] = (int *)40相當于把40直接強轉為int *類型,40是右值。不可能隨便給個值40,然后把40強轉為地址值40,那這還亂了套了,我們只能把已有的變量的地址值強轉為不同類型。
一開始arr[0]存放的地址值是CCCCC,這根本不是地址值,是開辟棧幀時自動填充的值,*arr[0] = 40相當于把這個棧空間里的值CCCCCCC當成地址值,解引用,然后把值放進去,這樣肯定不行。
二、函數指針和指針函數
1.函數指針
首先要知道函數指針是指向函數的指針。所以函數指針本質是一個指針。要求*肯定要與函數名相結合,可以和數組指針指針函數對比一下,思路一樣的。
先簡單熟悉兩個函數指針類型
上面兩種函數指針的使用方法和調用方法演示:
代碼:
給函數指針賦值的兩種方法:
(1)F1 = fun;
(2)F1 = &fun;
注意,只需要用函數名就可以給函數指針賦值,如果是重載函數,那么函數指針知道是哪個函數名轉換為函數指針給它賦的值。(因為函數指針會根據定義的時候的形參列表自己選)。
對函數名取不取地址無關緊要,因為不取地址的話,編譯器會自動將函數名轉換為地址格式賦值給函數指針。加上取地址符就是我們顯示轉化了。
函數指針調用函數的兩種方式(以上面代碼中的F1為例):
(1)F1(“實參”,“實參”);
(2)(*F1)(“實參”,“實參”);
F1里面放的是函數地址,第一種方式,編譯器會自己訪問函數指針空間的函數地址值找到函數,對其調用。
第二種方式就是我們直接對函數指針解引用,直接調用函數,省去了編譯器的隱式轉換。
講完這兩種基本的函數指針和上面的指針數組,下面給出一些代碼,區分一下數組指針,指針數組,函數指針。
void (*F)(int); //函數指針,指向的函數原型為void fun(int)void* (*F)(int,int); //函數指針,指向的函數原型為void* fun(int,int)void (*(*F)(int,int))(int); //函數指針,指向的函數原型是void(*F)(int) fun(int,int) //即函數參數類型為(int,int)函數返回類型是一個函數指針 //返回類型為void(*F)(int),這是一個指向函數原型為void fun(int)的函數指針void* (*(*F)(int))(int,int); //函數指針,指向的函數原型為void* (*F)(int,int) fun(int) //這個函數的返回類型為void* (*F)(int,int)函數指針 //函數指針void* (*F)(int,int)指向的函數原型為void* fun(int,int)int* ptr[10]; //指針數組,存放類型為int*類型元素的指針數組int*(*ptr)[10]; //數組指針,指針指向的數組為指針數組,指針數組列數為10,且數組存放的元素類型int*類型int* (*F[10])(int); //函數指針數組,一維數組,可以存放10個元素,存放的元素類型為函數指針,函數指針指向的函數原型為int* fun(int)(已經有點快要胡言亂語了…)
如果能捋清楚,可以挑戰一下自己:寫一個函數指針數組,函數指針指向的函數返回類型又是一個函數指針。(形參隨意,最后一個函數指針指向的函數返回類型為void*)
可以先寫一個普通函數指針:void (*F)(int);
在上面基礎上寫一個指向的函數的返回類型為函數指針的函數指針:void * ( *(*F)(int))(int,int);
在上面基礎上完成數組:
void * ( * (*F[10])(int))(int,int);
2.指針函數
首先要知道指針函數是返回類型為指針的函數。所以指針函數本質是一個函數。
int *fun(int,int);
這是一個指針函數。()優先級高于 *,所以這是一個函數,這個很好認,平常寫函數寫這么多,返回值類型為指針的函數肯定能看出來。
三、常量指針和指針常量
1.常量指針
首先要知道常量指針是指向常量的指針。所以常量指針本質是一個指針。意思是指針指向的是常量,常量內容不能被修改。所以也就是指針指向的東西的內容不能改變。但是指針的指向允許被修改。
格式為T const * p。
2.指針常量
首先要知道指針常量是指針本身是常量。換句話說,不能修改指針的內容(即指針存儲的地址),也就是不能修改指針的指向。但是指針指向的東西的內容允許被修改。
格式為:T * const p或者const T * const p。
總的來說,指針常量就是指針本身是常量,不能修改指針的指向。
四、常量引用
常量引用
常量引用即引用常量的引用,所以不能修改引用的東西的內容。
格式為const T&
此外我們都知道,引用是不能修改的,即一開始是誰的別名就一直是誰的別名,沒辦法修改。這在引用那一篇博客里談過。即引用底層用指針實現:
int a = 10;
int& b = a;
即在底層實現為:int * const b = &a;
大總結: 總的來說,數組指針、函數指針、常量指針、常量引用代表的意思分別是:
①指向數組的指針。
②指向函數的指針。
③指向常量的指針。
④引用常量的引用。
指針數組、指針函數、指針常量代表的意思分別是:
①存放指針的數組。
②返回類型為指針的函數。
③指針本身為常量的指針。
總結
以上是生活随笔為你收集整理的数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 类的成员函数与内联以及静态成员
- 下一篇: 运算符重载(加减运算符、前置加加(减减)