天天看點

徹底了解C/C++指針 目錄 1. 概念 2. 差別 3. 相容性 4. 為何列數須相等? 5. “1”的含義 6. 回歸本質 7. “*”和“[]”

徹底了解C/C++指針 目錄 1. 概念 2. 差別 3. 相容性 4. 為何列數須相等? 5. “1”的含義 6. 回歸本質 7. “*”和“[]”

<a href="#_Toc7850%20">目錄 1</a>

<a href="#_Toc27559%20">1. 概念 1</a>

<a href="#_Toc12664%20">1.1. 雙指針 1</a>

<a href="#_Toc21143%20">1.2. 指針數組 1</a>

<a href="#_Toc27824%20">1.3. 數組指針 1</a>

<a href="#_Toc26229%20">1.4. 常見指針定義解讀 1</a>

<a href="#_Toc8103%20">2. 差別 2</a>

<a href="#_Toc17537%20">3. 相容性 2</a>

<a href="#_Toc22742%20">4. 為何列數須相等? 2</a>

<a href="#_Toc30368%20">5. “1”的含義 3</a>

<a href="#_Toc8851%20">6. 回歸本質 3</a>

<a href="#_Toc3507%20">7. “*”和“[]” 7</a>

指向一個指針的指針。

由指針值組成的數組,也就是說數組的每個元素值的資料類型均為指針類型,如:int* p[2];

指向一個數組的指針。

int *p;

p為指向int值的指針,也可以說是指向一維數組的指針,假如有一個一維數組:int m[8],則可:p = m;

int *p[8];

p為一個一維數組,數組元素為int*類型,它和數組int p[8]都是同一類型,隻不過一個元素類型為int*,一個是int

int (*p)[8];

p為一個指向二維資料的指針,數組元素為int類型,假如有二維資料:int m[1][8],則可:p = m;

int (*p)();

p為一個指向函數的指針,假設有一個函數:int foo(),則可:p = foo;

下面兩個了?

int (**pa)[8];

int (**pb)();

不用怕,隻是多了個*,也就是指向指針的指針。假設有:int m[1][8]; int (*p)[8] = m;,則:pa = &amp;p。

行數

列數

說明

int** p1;

雙指針

不固定

列數和行數都不确定,而且每行可以列數不等。

int* p2[3];

指針數組

固定

共3行,每行多少列不确定,而且每行可以列數不等。

int (*p3)[3];

數組指針

共3列,多少行不确定。

int p4[2][3];

int p5[3];

// 相容性

p1 = p2;

p3 = p4;

p3 = &amp;p5; // p5的列數必須和p3的列數相同

p1 = p2; // 兩者列數均不确定,可相容

“列數相等”或“列數不确定”是相容的提前條件,如上述的p3、p4和p5三者的列數均相同。

指針支援加減操作,比如:

int m[3][3];

int (*pm)[3] = m + 1;

上述第二行的m是指二維數組“int m[3][3];”在記憶體中的首位址,如:0x7fff82521370。而這個“1”是指這個二維數組一行的大小,也就是“int m[3];”的大小。是以,pm的值為:0x7fff82521370 + 12 = 0x7fffd5afd94c。

如果列數不相等,則加減操作無法進行,是以需要“列數相等”。假設:

int** b1;

int** b2 = b1 + 1;

上述中的“1”實際是多少?這個就要看b1的類型是什麼?在這裡,b1是一個雙指針,也就是指向指針的指針。本質上就是一個指針,是以在32位平台上它的值是4,在64位平台上它的值是8。

對于“p+1”中的“1”,其含義依p所指向的類型而不同。

定義

所指向類型

“1”的含義

int* p;

p+1

p指向int類型

sizeof(int)

(*p)+1

“*p”不是指針

即為表面意義的數字1

(&amp;p)+1

“&amp;p”指向“int*”類型

sizeof(int*)

int** p;

p指向“int*”類型

“*p”指向int類型

(**p)+1

“**p”不是指針

int m[5];

m+1

m是個位址,指向int類型

sizeof(int)或sizeof(m[0])

&amp;m+1

&amp;m是個位址,指向int[5]類型

sizeof(m)

int*** p;

p指向“int**”類型

sizeof(int**)

“*p”指向“int*”類型

“**p”指向int類型

(***p)+1

“***p”已不是指針

int mm[2][3];

mm+1

mm是個位址,指向int[3]類型

sizeof(m[0]),即為4*3=12

指針的加減操作,實際是對位址的操作,而解引用“*”是取所在位址的資料,數組下标操作“[]”也是取所在位址的資料。

徹底了解C/C++指針 目錄 1. 概念 2. 差別 3. 相容性 4. 為何列數須相等? 5. “1”的含義 6. 回歸本質 7. “*”和“[]”

徹底了解指針,最關節是了解記憶體是啥。記憶體有兩個基本屬性:一是位址,二是資料。對于“int *p;”,p是位址,“*p”是資料。對于“加減”操作,要區分是對位址,還是資料的“加減”操作,對于“int* p;”,則“p+1”是位址的加減操作,而“(*p)+1”則是資料的加減操作。

在x86_64環境,指針大小為8位元組,int類型為4位元組。注意下圖中的虛線框,它們要麼是未初始化的記憶體,或者對它們的通路是越界通路。總之一句話:虛線框的記憶體位址是确定的,但存儲在這些位址上的資料是不能保證的。

徹底了解C/C++指針 目錄 1. 概念 2. 差別 3. 相容性 4. 為何列數須相等? 5. “1”的含義 6. 回歸本質 7. “*”和“[]”

上圖對應的C++代碼:

// g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp &amp;&amp; ./a.out

#include 

int main()

{

    using namespace std;

    int a;

    int b;

    cout 

    int m[] = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xA0 };

    int* p = m;

    int** pp = (int**)m;

         &amp;p=" &amp;p 

pp = &amp;p;

    return 0;

}

&amp;a=0x7fff55631998, &amp;b=0x7fff55631994

&amp;m[0]=0x7fff55631c30

&amp;m[1]=0x7fff55631c34

&amp;m[2]=0x7fff55631c38

&amp;m[3]=0x7fff55631c3c

&amp;m[4]=0x7fff55631c40

&amp;m[5]=0x7fff55631c44

&amp;m[6]=0x7fff55631c48

&amp;m[7]=0x7fff55631c4c

pp=0x7fff55631c30

pp+1=0x7fff55631c38

pp+2=0x7fff55631c40

*pp=0x200000001

*(pp+1)=0x400000003

*(pp+2)=0x600000005

&amp;p=0x7fff55631988

p=0x7fff55631c30

p+1=0x7fff55631c34

p+2=0x7fff55631c38

*p=1

*(p+1)=2

*(p+2)=3

pp=0x7fff55631988

pp+1=0x7fff55631990 // 這個位址值是确定的,但上面的資料是啥則不好說

pp+2=0x7fff55631998

*pp=0x7fff55631c30

(*pp)+1=0x7fff55631c34

(*pp)+2=0x7fff55631c38

*(pp+1)=0x0

*(pp+2)=0x0

假設有:int** pp;,則“**pp”和“pp[0][0]”作用相同,實際上都是:*((*p)+0)。對于二維數組“mm[行][列]”,“mm[2][3]”效果和“*((*(mm+2))+3)”相同。

如果這樣定義:

int mm[][4] = { {0x01,0x02,0x03,0xA1}, {0x04,0x05,0x06,0xA2}, {0x07,0x08,0x09,0xA3} };

int** qq = mm;

則編譯時會告警“cannot convert 'int (*)[4]' to 'int**' in initialization”,原因是違背了本文第三節“相容性”要求,正确的定義是:

int (*qq)[4] = mm;

這個時候,“*((*(mm+2))+3)”、“*((*(qq+2))+3)”和“mm[2][3]”效果相同。如果要強來:

int** yy = (int**)mm;

cout 

可以看到位址值完全是兩個不同的了:

((*(mm+2))+3)=0x7fff8f58c27c

((*(yy+2))+3)=0x500000010

因為位址值yy和mm是相同的,是以仍然可以通過取巧使用yy來取得“mm[2][3]”的資料:

1) “mm+2”中的“2”,實則是“sizeof(m[0])*2”

2) “(*(mm+2))+3”中的“3”,實則是“sizeof(m[0][0])*3”

3) “m[0]”大小為“4*4=16”,“m[0][0]”大小為“sizeof(int)=4”,不難計算出“(*(mm+2))+3”相對于“mm”,位址偏移了“32+12=44”

4) 是以隻需要将“yy”偏移“44”即可達到目的

5) 在x86_64上“(int**)((unsigned long)(yy+6)-4)”值和“(*(mm+2))+3”相同

6) 可以通過“*(int*)((unsigned long)(yy+6)-4)”取得“m[2][3]”的值。

繼續閱讀