多维数组与指针之间的关系详解
先介紹一下簡(jiǎn)單的一維數(shù)組:
列如:
int a[3] = {0,1,2};[3]和類型int則明確表示編譯器應(yīng)該為這個(gè)棧分配多大的內(nèi)存,也就是三個(gè)int大小!
在內(nèi)存中示意圖是:
在CPU看來內(nèi)存是一組連續(xù)的地址空間,所以當(dāng)我們對(duì)一維數(shù)組進(jìn)行操作時(shí)只需要知道數(shù)組首地址,就可以通過地址偏移加減運(yùn)算方式來求得每個(gè)元素位于內(nèi)存中的文件映射出來的數(shù)據(jù)段虛擬地址!
不過要注意不要越界,其實(shí)你在自己的地址空間內(nèi)訪問超出數(shù)組大小的空間也不會(huì)有問題,因?yàn)槟闶窃谧约旱牡刂废逻M(jìn)行訪問的,不會(huì)被內(nèi)核卡擦掉,只不過那一段內(nèi)存可能被對(duì)齊了,是未知不被使用的數(shù)據(jù)!
詳細(xì)內(nèi)存對(duì)齊參見:詳解C語言內(nèi)存對(duì)齊
使用方法也非常簡(jiǎn)單:
- int a[3] = { 0, 1, 2 };
- printf("%d", a[0]); //打印第0個(gè)數(shù)據(jù)
打印結(jié)果:
使用指針方式:
注意數(shù)組即本身就是一個(gè)地址,而指針可以直接操作地址,所以指針可以使用數(shù)組的方式來表示:
- int a[3] = { 0, 1, 2 };
- int *p = a;
- printf("%d", p[0]); //打印第0個(gè)數(shù)據(jù)
編譯器會(huì)自動(dòng)根據(jù)表達(dá)式所使用的運(yùn)算符來自動(dòng)匯編解引用代碼!
打印結(jié)果:
也可以顯示的使用指針解引用:
- int a[3] = { 0, 1, 2 };
- int *p = a;
- printf("%d", *p+1); //打印第1個(gè)數(shù)據(jù)
這里p已經(jīng)指向了a數(shù)組的首地址,也就是a[0],想打印第一個(gè)元素的值,只需要對(duì)其*解引用并+1讓其進(jìn)行地址偏移一個(gè)類型單位(int,編譯器會(huì)根據(jù)表達(dá)式在結(jié)合類型自動(dòng)進(jìn)行匯編上的地址偏移量add)!
二維數(shù)組:
int a[3][3] = {{0,1,2},{3,4,5}};????//定義一個(gè)二維數(shù)組上面的表達(dá)方式即:定義一個(gè)有3列且每列有3行數(shù)據(jù)的一個(gè)二維數(shù)組!
上面只是抽象的表達(dá)方式,其實(shí)底層就是一個(gè)一維數(shù)組:
長度是每行的集合,只是C語言上對(duì)其更加抽象的區(qū)分開了,根據(jù)第一個(gè)[]操作符里的值將其分成多少個(gè)段!根據(jù)[]后的[]確定每段內(nèi)存能存下多少字節(jié),根據(jù)類型來確定該內(nèi)存用來存儲(chǔ)什么樣的類型數(shù)據(jù)運(yùn)算時(shí)調(diào)用alu(整數(shù)運(yùn)算器)還是fpu(浮點(diǎn)數(shù)運(yùn)算器)
浮點(diǎn)數(shù)是由單獨(dú)的存儲(chǔ)方式的,所以需要嚴(yán)格的區(qū)分開!
而且在底層是沒有類型這一區(qū)分的全部都是二進(jìn)制數(shù),所以在編譯階段編譯器就會(huì)檢查類型之間的讀寫,所以類型限制是由編譯器來維護(hù)的!
使用方法:
- int a[3][3] = { { 0, 1, 2 }, {3,4,5} };
- printf("%d", a[0][1]); //打印第0行第1個(gè)數(shù)據(jù)
打印結(jié)果:
下面來介紹一下使用指針的方法和問題:
首先先來看一下:
下面代碼為什么會(huì)報(bào)錯(cuò)?
- int a[3][8] = {{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8}};
- int **p = a;
原因很簡(jiǎn)單,二級(jí)指針只能用來指向int*指針,而int a是一個(gè)二維數(shù)組,兩個(gè)類型一開始指向的實(shí)際類型就不對(duì),在其次,雙方占用的內(nèi)存大小也不同!
列如
int a[3][8]占用的是int*3*8個(gè)字節(jié)大小
而*p僅占用4個(gè)字節(jié)(取決于編譯器位數(shù))
那問題又來了,為什么一維數(shù)組就可以?
其原因如下:
在C/C++編譯器當(dāng)中一維數(shù)組隱式是一個(gè)指針,換句話來說,數(shù)組就是指針,數(shù)組本身就是一個(gè)地址,無需二次尋址,指針和數(shù)組區(qū)別在于數(shù)組不用解引用,而且數(shù)組名也是一個(gè)常量,無法直接賦值!
最經(jīng)典的列子就是當(dāng)你將數(shù)組作為參數(shù)時(shí),編譯器會(huì)自動(dòng)將數(shù)組轉(zhuǎn)化為指針,其原因是為了剩內(nèi)存!
而二維數(shù)組則被隱式的聲明成:int *a[8];
所以我們?nèi)绻胫赶蛞粋€(gè)二維數(shù)組時(shí)候就要聲明成int (*p)[8] = a;?//?一個(gè)指向有8個(gè)整型數(shù)組的指針;
如果不相信的話,我們來修改一下代碼看看:
int a[3][8] = {{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8}}; int (*p)[5] = a;????//這里將int (*p)[4]改成int (*p)[5]看看會(huì)報(bào)什么錯(cuò)誤報(bào)如下錯(cuò)誤:
可以看到:int a[3][8]被隱式的轉(zhuǎn)換成:int(*)[8]了!
修改一下:
int (*p)[8] = a; //一個(gè)指向有8個(gè)整型數(shù)組的指針;解引用方法:
最簡(jiǎn)單的就是我們可以直接將該指針當(dāng)做數(shù)組來使用,因?yàn)?#xff1a;二維數(shù)組實(shí)則上并沒有并轉(zhuǎn)換成int (*)[8]只是隱式的類型轉(zhuǎn)換,實(shí)際內(nèi)存還是位于棧中!(*p)指向的是:一個(gè)隱式的int *a,而int *a指向a[3]這個(gè)第一維數(shù)組的首元素也就是首地址a[0],要知道數(shù)組地址是連續(xù)的可以通過解引用隱式的*a+1得到下一個(gè)元素的地址!而后面的[8]則表示每個(gè)一維數(shù)組中有多少個(gè)元素!
也就是說說int a[3][8]被隱式的轉(zhuǎn)換成int *a[8],*a指向原本的a[3]的首地址,而后面的[8]則是告訴*a每個(gè)元素的偏移量是多少!
則也就是說[8]為8個(gè)int!
其實(shí)更為明確的表示方法就是 int a[3][8] = 3個(gè)int[8]
其實(shí)我們也不需要對(duì)其進(jìn)行解引用,因?yàn)槭褂肹]括號(hào)編譯器會(huì)把該指針作為數(shù)組一樣使用,而數(shù)組自己就是一個(gè)地址,所以編譯器會(huì)自動(dòng)將該指針轉(zhuǎn)化為地址!
printf("%d", p[1][1]);????//打印第一維第一個(gè)數(shù)據(jù)上面這張方法是最為簡(jiǎn)單的,
還有一種方法:
printf("%d", *(*(p+1)+1));????//打印第一維第一個(gè)數(shù)據(jù)下面來詳細(xì)的分解一下上面的解引用過程
首先第一步需要對(duì)p進(jìn)行解引用,這里不在當(dāng)做數(shù)組使用所以需要顯示的對(duì)其進(jìn)行解引用,上面說過*p指向隱式的*a,這里對(duì)其解引用實(shí)則上是對(duì)找到了*a的地址并對(duì)其進(jìn)行+1
*(p+1)這里加上括號(hào)是因?yàn)?取值運(yùn)算符優(yōu)先級(jí)要高于+號(hào)運(yùn)算符,注意乘法*不高于+號(hào)運(yùn)算符,而取值*會(huì)高于+號(hào)運(yùn)算符,編譯器會(huì)根據(jù)表達(dá)式來確定*號(hào)的用途。
下面再在來看p+1,上面說過(*p)指向的是隱式的*a地址,而*a指向數(shù)組的首地址也就是a[0],這里p+1也就是讓*a+1,加上括號(hào)()讓其優(yōu)先對(duì)地址進(jìn)行+1在解引用,否則會(huì)直接對(duì)*a解引用然后在對(duì)該元素值+1!即操作*a棧地址里存儲(chǔ)的地址+1而非真正的數(shù)組地址,如果不解引用的話那就是p本身地址+1了!
補(bǔ)充一個(gè)小知識(shí):
指針也是有自己的地址的,指針存在于棧中,一般指針的棧內(nèi)存存儲(chǔ)的是堆或棧地址!
然后又在*(p+1)的外面加了一個(gè)括號(hào)(*(p+1)),最后并讓其+1再次解引用:*(*(p+1)+1)
下面來詳細(xì)解釋一下:
第一,當(dāng)我們通過*(p+1)找到了隱式*a的地址,注意只是找到了隱式*a的地址而非數(shù)組的地址,需要再次對(duì)*a解引用找到*a棧內(nèi)存存儲(chǔ)的數(shù)組地址:
**p? ? 這樣的寫法才是真正以指針的形式找到二維數(shù)組的寫法!
不信我們?cè)囈幌?#xff1a;
printf("%d", **p);打印結(jié)果為:1
而**p+1就是對(duì)a指向的數(shù)組地址+1,要知道二維實(shí)則上也是一維數(shù)組,都是地址都是線性排序的所有**p+1,就是指向第二個(gè)元素,不需要加括號(hào)是因?yàn)?*優(yōu)先級(jí)高于+,按照這個(gè)優(yōu)先級(jí)來算表達(dá)式,會(huì)先對(duì)p解引用找到隱式的*a,在對(duì)*a解引用找到數(shù)組地址+1則下一個(gè)元素的地址:
printf("%d", **p+1);打印結(jié)果:2
通過上面的介紹,就應(yīng)該很容易理解這段代碼了:
*(*(p+1)+1)首先對(duì)*(p+1)解引用找到也就是隱式的*a并對(duì)其地址進(jìn)行解引用,然后在對(duì)其+1(這里+1加的是int*字節(jié)大小的偏移地址)也就是找到指向a[1]的*a偏移地址,在對(duì)其進(jìn)行+1,也就是找到數(shù)組里的元素,然后在對(duì)其進(jìn)行解引用,在解引用之前要加上括號(hào),上面也說了,優(yōu)先級(jí)的原因,否則會(huì)找到a[1]首元素然后對(duì)該值+1
所以正確的指針引用寫法是:
*(*(p+1)+1)下面說說三維數(shù)組應(yīng)該怎樣使用:
列如:
int nA[2][2][2];對(duì)于這樣的三維數(shù)組并不難理解:
int nA[2][2][2];
實(shí)則上就是
在每行多增加了一個(gè)行寬
列如:
int nA[2][2] = { { 1, 2 }, { 3, 4 }}; 更改為三維數(shù)組之后:
int nA[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; // 三維數(shù)組 三維數(shù)組可以被認(rèn)為是二維數(shù)組,而二維數(shù)組也可以被認(rèn)為是一維數(shù)組,因?yàn)樵谟?jì)算機(jī)當(dāng)中數(shù)組的地址是連續(xù)的,只有行沒有列,維數(shù)組只是一種抽象的表達(dá)方式!
三維則是給每行增加額外的行寬
更明確的表達(dá)方式就是:int a[2][2][2] = 2個(gè)int[2][2]
更加明確的表達(dá)方式其實(shí)就是:int a[2][2][2] = 有列,每個(gè)列上有兩行,每行可以放2個(gè)數(shù)據(jù)!
注意這里不是畫畫,沒有高度,所以在更底層的表達(dá)方式三維實(shí)則上是給每行增加行寬!
使用方法:
- int nA[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; // 三維數(shù)組
- int(*p)[2][2] = nA;
- printf("%d\n", p[0][1][1]); //打印第0列第一行第1個(gè)行寬
注意三維的初始化必須用{}括起來!
即表示每行寬
打印結(jié)果:
可以看到打印出第打印第0列第一行第1個(gè)行寬第1個(gè)元素?cái)?shù)據(jù):4
堆棧下標(biāo)是從0開始的所以索引是1!
下面介紹如何使用指針的形式來訪問:
- int nA[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; // 三維數(shù)組
- int(*p)[2][2] = nA;
- printf("%d\n", *(*(*p)+1)); //打印第0列第0行第1個(gè)行寬
打印結(jié)果:
下面來解釋一下上面的指針解引用過程:
*(*(*p)+1)*p首先解引用是對(duì)p指向的*nA指針解引用找到*nA指針,在*解引用是找到*nA指向的指向的nA[2]的首地址解引用,注意這個(gè)時(shí)候必須再次解引用,因?yàn)樾袑捯呀?jīng)被分成了兩個(gè),nA[2][2]也已經(jīng)被隱式的聲明成一個(gè)指針**nA指向該數(shù)組的首地址也就是nA[2][2]的首地址,我們要對(duì)其解引用確定要對(duì)哪個(gè)地址進(jìn)行訪問***p 這種解引用方式則是對(duì)nA元素第0行第0列第0個(gè)元素進(jìn)行訪問,如果+1則是對(duì)第0行第1列第0個(gè)元素訪問***p+1,如果想訪問其中的每個(gè)元素需要進(jìn)行括號(hào)優(yōu)先級(jí)運(yùn)算,上面也說過:
(*p)解引用*nA
*(*p)解引用*nA指向的數(shù)組元素首地址
*(*(*p))?上面說過nA[2][2]已經(jīng)被隱式的聲明成了一個(gè)指針指向每個(gè)行寬,所以這步操作是對(duì)該指針進(jìn)行解引用則每行的首地址
*(*(*p)+1)? ? 對(duì)指針進(jìn)行加減運(yùn)算,讓指針向下個(gè)地址偏移一個(gè)指針變量單位也就是一個(gè)int的大小,指向下一個(gè)元素
所以打印的是:
第0行第0列第1個(gè)元素:2
如果想打印第0行第1列第0個(gè)元素只需要對(duì)*p+1即可
*(*(*p+1))
?
其指針概念較多,容易混淆,下面是幾種指針的聲明方式:
1、 一個(gè)整型數(shù);int a;2、 一個(gè)指向整型數(shù)的指針;int *a;3、 一個(gè)指向指針的指針,它指向的指針是指向一個(gè)整型數(shù);int **a;4、 一個(gè)有10個(gè)整型數(shù)的數(shù)組;int a[10];5、 一個(gè)有10個(gè)指針的數(shù)組,該指針是指向一個(gè)整型數(shù)的;int *a[10];6、 一個(gè)指向有10個(gè)整型數(shù)組的指針;int (*a)[10];7、 一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù);int (*a)(int);8、 一個(gè)指向數(shù)組的指針,該數(shù)組有10個(gè)指針,每個(gè)指針指向一個(gè)整型數(shù);int *(*a)[10];9、 一個(gè)有10個(gè)指針的數(shù)組,給指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù);int (*a[10])(int);10、 一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)指向函數(shù)的指針,返回的函數(shù)指針指向有一個(gè)整型參數(shù)且返回一個(gè)整型數(shù)的函數(shù);int (*(*a)(int))(int);
其實(shí)指針和數(shù)組并沒有本質(zhì)上的區(qū)別,區(qū)別在于數(shù)組在初始化之后就不能作為右值進(jìn)行運(yùn)算修改數(shù)組大小或指向其它數(shù)組地址,所以數(shù)組為什么被稱為數(shù)組就是地址?因?yàn)閿?shù)組在聲明之后就是一個(gè)常量,其地址就是整個(gè)數(shù)組的起始地址,而指針則可以隨意指向,當(dāng)然除了被const修飾符修飾的指針!
而且數(shù)組名是不能參與運(yùn)算的,必須通過下標(biāo)顯示指明要參與運(yùn)算的元素!
那么又來了一個(gè)問題,上面說的數(shù)組名就是數(shù)組的首地址那為何還要用[]來指明下標(biāo)才能運(yùn)算?
答:因?yàn)樵贑/C++編譯器規(guī)定數(shù)組名雖然是首地址,但是只能被作為右值運(yùn)算,如果想要被作為左值參與運(yùn)算必須顯示指定下標(biāo)確定操作哪個(gè)元素,而數(shù)組名則對(duì)應(yīng)整個(gè)數(shù)組的首地址,如果對(duì)數(shù)組名操作不指明對(duì)哪個(gè)元素操作,即對(duì)整個(gè)數(shù)組操作,那么對(duì)于編譯器來說如果這個(gè)數(shù)組大于CPU位數(shù)那么會(huì)造成硬件中斷!
關(guān)于CPU尋址詳解: 深度理解“CPU內(nèi)部尋址方式”
最后在補(bǔ)充一點(diǎn)為什么說要經(jīng)常使用指針?
答:指針節(jié)省內(nèi)存,使用指針并通過malloc分配內(nèi)存可以節(jié)省編譯后內(nèi)存,并且棧也是有限的!
[轉(zhuǎn)](https://blog.csdn.net/bjbz_cxy/article/details/79962349)總結(jié)
以上是生活随笔為你收集整理的多维数组与指针之间的关系详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mat,Iplimage,vector,
- 下一篇: 处有未经处理的异常:0xC0000005