
<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 = &p。
行數
列數
說明
int** p1;
雙指針
不固定
列數和行數都不确定,而且每行可以列數不等。
int* p2[3];
指針數組
固定
共3行,每行多少列不确定,而且每行可以列數不等。
int (*p3)[3];
數組指針
共3列,多少行不确定。
int p4[2][3];
int p5[3];
// 相容性
p1 = p2;
p3 = p4;
p3 = &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
(&p)+1
“&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])
&m+1
&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
指針的加減操作,實際是對位址的操作,而解引用“*”是取所在位址的資料,數組下标操作“[]”也是取所在位址的資料。
徹底了解指針,最關節是了解記憶體是啥。記憶體有兩個基本屬性:一是位址,二是資料。對于“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
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;
&p=" &p
pp = &p;
return 0;
}
&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=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]”的值。