c語言之指針-深入分析

1.指針的概述
int a=123456789;                
char * pch=(char *)&a;
short * ps=(short *)&a;
unsigned short *ups=(unsigned short *)&a;
int *pi=&a;

printf("%d\n",*pch);
printf("%d\n",*ps);
printf("%d\n",*ups);
printf("%d\n",*pi);
輸出結果:
21
-13035
52501
123456789
(1)指針有"兩個值":
指針本身也就是所指對象的地址值&a(一個地址),指針本身也是一個變量,指針所指的對象a,通常用*加指針變量來找到所指的值。
如果把指針pi比作一個房間,a就是房間裏面我們想要找的人,這個時候*相當於鑰匙,*pi來打開這扇門,找到a,同樣用&來找到a的地址,也就是pi。
(2)指針本身來還具有一些其他屬性:
指針的類型和指針所指向的類型,指針的類型必須要和所指向的類型對應一致。例如int*只能指向int型數據(可以強制轉換)。
int a=123456789,char *pch想要指向a就必須將a的類型強制轉換爲char,short同理。
而我們知道指針pch是存儲a的地址,&a已經取得了a的地址,所以&a強轉爲char *直接就和pch對應了(pch指向a,pch的值是&a)。下面ps同理。
(3)指針所對應的內存區域
指針雖然只存儲一個地址,但是他的類型規定了爲該地址劃分多少連續空間,所以指針的值和類型共同構成了指針所指向的內存區域。
當然指針int *pi本身也佔有一個內存區域,&pi就是他的地址,int*就是他的類型(總是和環境對應的字節數),所以每創建一個指針也會爲其開闢一片空間。
(4)較爲複雜的指針類型
我們知道指針的類型要和所指向的對象類型要一致,因爲類型和內存中開闢的空間大小有關。
除了基本類型(開闢固定大小空間)以外,還有幾種常見的類型,通常我們也用指針對其進行指向。
int a[5]這是一個int數組,開闢了五個int大小的內存,所以數組變量a的類型爲int[5],對應的指針類型爲int(*)[5](注意*在()中)。
int fun(int a)這是一個返回int的函數,指針同樣也可以指向這個函數(因爲函數本身開闢了空間),對應類型int(*)(int),例如int(*p)(int)。
上面兩種常見的指針類型我們分別稱作數組指針(指向數組的指針)和函數指針(指向函數的指針),可得指針可通稱爲指向被指向對象類型的指針。
*在()中是因爲*和[]還有()運算符優先級的問題,(*)代表了定義的對象本身是一個指針類型。
(5)關於輸出結果
這個就要和數據在內存中的表示有關係了,在32位環境下系統尋址範圍爲2^32(4G),指針所佔空間就是4字節(16進製表示爲0-0xFFFFFFFF)。
而int a=123456789是十進制表示的,但是計算機內存中都是用二進制來表示數據。a轉換爲2進制0000 ‭0111 0101 1011 1100 1101 0001 0101‬(計算器)。
int是帶符號的整型,內存中大小4Byte,char是帶符號的字節型,內存中大小1Byte(相關內容可參看計算機組成原理)。
而windows操作系統是一個小端系統,低字節數據放入低內存地址存儲,暫不過多贅述。所以內存中a的真實排布爲0001 0101 1100 1101 0101 1011 0000 0111。
將int強轉爲char,會截取內存中最低一個字節8bit(0001 0101),所以*pch的值爲0001 0101也就是21。
int轉爲short,會截取內存中低兩個字節16bit(0001 0101 1100 1101),小端表示數據爲1100 1101 0001 0101,最高位爲1,而內存中以補碼顯示帶符號的數據。
補碼求源碼->先-1(1100 1101 0001 0100)->在整體取反(0011 0010 1110 1011)->13035->填上符號位(-13035)
關於補碼,大小端存儲模式和尋址大小等問題,都在組成原理中有解釋。
int轉unsigned short,unsigned short是無符號短整型,小端表示數據爲1100 1101 0001 0101,內存中都是原碼不存在符號位,所以就是52501。

2.指針的值和指針的偏移    
int a=10;
int b=20;
int *p = (int *)0x0019ff2c;

*p=100;
printf("a = %d\n",a);

char *pc=(char *)p;
short *ps=(short *)p;
int *pi=(int *)p;
double *pd=(double *)p;
printf("%p : %p\n",pc,pc+1);
printf("%p : %p\n",ps,ps+1);
printf("%p : %p\n",pi,pi+1);
printf("%p : %p\n",pd,pd+1);

輸出結果:
a = 100
0019FF2C : 0019FF2D
0019FF2C : 0019FF2E
0019FF2C : 0019FF30
0019FF2C : 0019FF34
(1)指針的偏移:
我們先通過debug找到變量a的地址是0x0019ff2c(16進制),將0x0019ff2c強制轉換成指針的類型(int *),這樣p的值就是0x0019ff2c,類型也一致。
找到了一片內存空間,然後通過*p=100來間接修改了a的值。前面說過a有一片獨立的內存區域,這片空間取決於它的數據類型,int就是4Bytes。
這種情況下,指針偏移一個單位,那麼內存空間也就偏移指針所指向的數據類型的內存空間,如int *偏移一個單位,那麼內存空間偏移4字節。
針對這種情況,指針就採取了一個機制,指針的偏移,指針變量同樣也可以加減,指針加1,指針向高內存偏移一個單位,減1就向低內存偏移一個單位4bytes。
(2)對於結果:
char *pc=(char *)p這種每次截取了一個字節,pc+1內存偏移一個字節,在運用中通過這樣就能把一個int數據的每一個字節都取出來。

3.當指針指向數組引發的數組指針問題
int ar[10] = {0};
int (*s)[10] = &ar;
int br[][10] = {1,2,3,4,5,6,7,8,9,10};
int (*pArr)[10] = br;

printf("%p : %p\n", s,s+1);
printf("%p : %p\n", pArr,pArr+1);
輸出結果:
0019FF08 : 0019FF30
0019FEDC : 0019FF04
(1)一維數組指針指向一維數組
指針能夠指向一個數組,那麼指針的類型也必須是數組類型,所以int ar[10]必須要用int (*)[10]來指向,這是指針的偏移一個單位值是40Bytes。
形如int (*s)[10]這樣的指針我們就通常稱爲一個數組指針(本質是指針)。數組指針和數組數據之間的值同樣會互相影響,和一般指針相同。(指針指向數組)
(2)一維數組指針操作二維數組
上述int br[][10]也相當於br[1][10],是一個二維數組,第一維(行)可省略,二維數組的行標被弱化爲指針
我們同樣可以用數組指針來控制它(並非是指向二維數組,只能說指向二維數組的第二維),所以這個和操作的一維數組意義不同。
一維數組數組指針指向二維數組,其實一維數組指針指向二維數組的第二維,指針的值存儲的是數組名的值(不加&),這正是因爲數組和指針的特殊關係造成。
(3)指針控制數組
當指針指向數組的時候,我們通常用指針的偏移來對數組的數據進行控制。因爲s的類型是int (*)[10],所以s+1就偏移了int*10個bytes。
同理可推s指向ar,*s就對應ar的值,而ar本身是ar[0]的地址,所以(*s)[0]就是ar[0],ar[0]是一個int型,同樣地,*s也就是指向int的指針。
這個時候*s+1相當於向下偏移了int個字節,所以*(*s+1)相當於取出了ar[1]的值。看起來可能有點麻煩。但是理解其原理很有必要。
一維指針也可以控制二維數組,這個時候操作和指針指向一維數組的操作類似。(pArr+1)就是偏移了一個第二維長度(40Bytes)。
*(pArr+0)值就是br[0],指向br[0][0],所以我們可以用*((*pArr+0)+1)來得到br[0][1]的值並對其操作。
(4)關於指針和數組的思考
由一維數組指針和一維數組以及二維數組的關係我們可以看出來,指針在操作控制數組的時候,編譯器弱化了指針*,數組[]和地址&的概念。
通過一維數組指針操作二維數組反而方便,從而我們應用中常常可以利用這個特性來做一些很怪異的操作,代碼更加靈活,也會引發更多的問題和思考。
故可以用int*來指向一個int對象,卻可以操作一個int數組。也可以用一個n維數組指針來指向一個n維數組,(n-1)維數組指針同樣操作一個n維數組。

 

學習c/c++的小夥伴,可以加QQ羣735899509,一起學習討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章