徹底理解C++指針


目錄

目錄 1

1. 概念 1

1.1. 雙指針 1

1.2. 指針數組 1

1.3. 數組指針 1

1.4. 常見指針定義解讀 1

2. 區別 2

3. 兼容性 2

4. 為何列數須相等? 2

5. “1”的含義 3

6. 回歸本質 3

7. “*”“[]” 7

 

1. 概念

1.1. 雙指針

指向一個指針的指針。

1.2. 指針數組

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

1.3. 數組指針

指向一個數組的指針。

1.4. 常見指針定義解讀

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 = &p

2. 區別

 

 

行數

列數

說明

int** p1;

雙指針

不固定

不固定

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

int* p2[3];

指針數組

固定

不固定

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

int (*p3)[3];

數組指針

不固定

固定

共3列,多少行不確定。

3. 兼容性

int** p1;

int* p2[3];

int (*p3)[3];

int p4[2][3];

int p5[3];

 

// 兼容性

p1 = p2;

p3 = p4;

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

 

p1 = p2; // 兩者列數均不確定,可兼容

 

列數相等”或“列數不確定”是兼容的提前條件,如上述的p3p4p5三者的列數均相同。

4. 為何列數須相等?

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

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

5. 1”的含義

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

定義

 

所指向類型

1”的含義

int* p;

p+1

p指向int類型

sizeof(int)

(*p)+1

“*p”不是指針

即為表面意義的數字1

(&p)+1

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

sizeof(int*)

int** p;

p+1

p指向“int*”類型

sizeof(int*)

(*p)+1

“*p”指向int類型

sizeof(int)

(**p)+1

“**p”不是指針

即為表面意義的數字1

int m[5];

m+1

m是個地址,指向int類型

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

&m+1

&m是個地址,指向int[5]類型

sizeof(m)

int*** p;

p+1

p指向“int**”類型

sizeof(int**)

(*p)+1

*p”指向“int*”類型

sizeof(int*)

(**p)+1

**p”指向int類型

sizeof(int)

(***p)+1

“***p”已不是指針

即為表面意義的數字1

int mm[2][3];

mm+1

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

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

6. 回歸本質

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

 

徹底理解指針,最關節是理解內存是啥。內存有兩個基本屬性:一是地址,二是數據。對於“int *p;”,p是地址,“*p”是數據。對於“加減”操作,要區分是對地址,還是數據的“加減”操作,對於“int* p;”,則“p+1”是地址的加減操作,而“(*p)+1”則是數據的加減操作。

 

在x86_64環境,指針大小為8字節,int類型為4字節。注意下圖中的虛線框,它們要么是未初始化的內存,或者對它們的訪問是越界訪問。總之一句話:虛線框的內存地址是確定的,但存儲在這些地址上的數據是不能保證的。

 

 

上圖對應的C++代碼:

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

#include <stdio.h>

#include <iostream>

 

int main()

{

    using namespace std;

    

    int a;

    int b;

    cout << "&a=" << &a << ", &b=" << &b << endl << endl;

    

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

    int* p = m;

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

    

    cout << "&m[0]=" << &m[0] << endl

         << "&m[1]=" << &m[1] << endl

         << "&m[2]=" << &m[2] << endl

         << "&m[3]=" << &m[3] << endl

         << "&m[4]=" << &m[4] << endl

         << "&m[5]=" << &m[5] << endl

         << "&m[6]=" << &m[6] << endl

         << "&m[7]=" << &m[7] << endl;

         

    

    cout << endl

         << "pp=" << pp << endl

         << "pp+1=" << pp+1 << endl

         << "pp+2=" << pp+2 << endl;

         

    cout << endl

         << "*pp=" << *pp << endl

         << "*(pp+1)=" << *(pp+1) << endl

         << "*(pp+2)=" << *(pp+2) << endl;

         

    cout << endl

         << "&p=" << &p << endl

         << "p=" << p << endl

         << "p+1=" << p+1 << endl

         << "p+2=" << p+2 << endl;

    

    cout << endl

         << "*p=" << *p << endl

         << "*(p+1)=" << *(p+1) << endl

         << "*(p+2)=" << *(p+2) << endl;

         

pp = &p;

    cout << endl << "pp = &p;" << endl;

    

    cout << endl

         << "pp=" << pp << endl

         << "pp+1=" << pp+1 << endl

         << "pp+2=" << pp+2 << endl;

         

    cout << endl

         << "*pp=" << *pp << endl

         << "(*pp)+1=" << (*pp)+1 << endl

         << "(*pp)+2=" << (*pp)+2 << endl;

         

    cout << endl

         << "*pp=" << *pp << endl

         << "*(pp+1)=" << *(pp+1) << endl

         << "*(pp+2)=" << *(pp+2) << endl;

         

    return 0;

}

 

上圖對應的C++代碼運行結果(由在線編譯器http://coliru.stacked-crooked.com/編譯運行):

&a=0x7fff55631998, &b=0x7fff55631994

 

&m[0]=0x7fff55631c30

&m[1]=0x7fff55631c34

&m[2]=0x7fff55631c38

&m[3]=0x7fff55631c3c

&m[4]=0x7fff55631c40

&m[5]=0x7fff55631c44

&m[6]=0x7fff55631c48

&m[7]=0x7fff55631c4c

 

pp=0x7fff55631c30

pp+1=0x7fff55631c38

pp+2=0x7fff55631c40

 

*pp=0x200000001

*(pp+1)=0x400000003

*(pp+2)=0x600000005

 

&p=0x7fff55631988

p=0x7fff55631c30

p+1=0x7fff55631c34

p+2=0x7fff55631c38

 

*p=1

*(p+1)=2

*(p+2)=3

 

pp = &p;

 

pp=0x7fff55631988

pp+1=0x7fff55631990 // 這個地址值是確定的,但上面的數據是啥則不好說

pp+2=0x7fff55631998

 

*pp=0x7fff55631c30

(*pp)+1=0x7fff55631c34

(*pp)+2=0x7fff55631c38

 

*pp=0x7fff55631c30

*(pp+1)=0x0

*(pp+2)=0x0

7. “*”[]

假設有: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 mm[][4] = { {0x01,0x02,0x03,0xA1}, {0x04,0x05,0x06,0xA2}, {0x07,0x08,0x09,0xA3} };

int (*qq)[4] = mm;

 

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

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

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

 

cout << "((*(mm+2))+3)=" << ((*(mm+2))+3) << endl;

cout << "((*(yy+2))+3)=" << ((*(yy+2))+3) << endl;

 

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

((*(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]”的值。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM