有以下程序:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
*p = 10;
p++;
*p = 20;
printf("十進制 %d,%d\n",arr[0],arr[1]);
printf("十六進制 %08x,%08x\n",arr[0],arr[1]);
return 0;
}
我們暫時先不要去考慮其答案,而是先來探究中間這個“p++”在這裏是什麼意思?
在該程序中,我們定義了一個整型數組,然後定義了一個整形指針變量,其指向的是該數組的首元素地址,所以之後對其解引用“*p=10”,自然會使arr[0]=10,這是沒有問題的。之後重點來了,這裏的“p++”到底給p加了個什麼東西?我們有以下猜想:
1. 加了整個數組的長度?
2. 加了一個字節的長度?
3. 加了四個字節的長度?
首先我們很容易把 1否決,因爲這是不可能的,如果一下子給P加整個數組的長度,p就會從數組的最前端直接跳到末尾,這種結果對我們來說是毫無意義的。
那麼 2 就有點意思了,加一個字節的長度會發生什麼呢?
我們知道,整形數組中,每個元素需要用四個字節來保存,我們用下面的圖像來簡略表示arr[0]= 10和arr[1] = 2
其中,我們用紅色字體假設其地址,每個格子所佔空間爲四個字節。接下來我們把這個圖片放大四倍,即每個格子所佔空間變爲一個字節。如圖:
之後我們便需要把10和2分別放入四個格子中,這其實很簡單。
例如10,由於是四個格子,每個格子一個字節,即要用32位表示10 ,但每個格子寫8個數字太麻煩了,所以我們用十六進制 表示10,這樣剛好每兩個十六進制數字放一個格子,我們知道10的十六進制是00 00 00 0a . 同理得出 2 的十六進制 00 00 00 02 .
最後重要的來了,是不是直接把這八對十六進制數字按次序放進去呢?當然不是!因爲存在大小端的問題!低地址放大數據是大端,低地址放小數據是小端,而我們的PC正好是小端,所以要在低地址處放入小的數據。那麼0x0000000a和0x00000002應該怎麼放呢?如下圖:
也許有人不明白,舉個例子:十進制的10007中1和7 誰大?你可能會說 7 大,但你不能只看數字而不看位數啊,7是7,可這裏的1是10000啊,所以如果也要把10007按小端儲存,就應該把 7 放到最前面 ,1放到最後面。所以你現在明白爲甚麼上面的十六進制是爲什麼這麼儲存的了吧?
然後我們繼續,讓一級指針 p 加一個字節,那麼解引用後賦予的值應該在下圖綠框的地方,
即我們要把20放入綠框中,有了上面的經驗,步驟就很清晰了,首先,20的十六進制是00 00 00 14,然後按照小端低地址放小數據的規則,有以下結果:
看到這裏,想必大家已經知道,“加一個字節”這個猜想是絕對站不住腳的,“p++”絕對不可能整出這麼複雜的玩意兒出來,只加一個字節,只會使指針錯位。不過,既然算到了這裏,我們就來看看這真正只加一個字節會對結果產生什麼影響,接下來的程序如下:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
*p = 10;
p = (int *)((int)p+1); //該語句是讓 p 只加一個字節
*p = 20;
printf("%08x,%08x\n",arr[0],arr[1]); //輸出arr[0] 、arr[1] 的八位十六進制
return 0;
}
其輸出結果爲0000140a,00000000
別驚訝爲甚麼是反的…..因爲你儲存是按照小端儲存,輸出是正常的啊….
該結果跟我們的計算結果是一樣的,有的同學還想知道其十進制的值,只要把上面輸出語句裏的“%08x”改爲“%d”即可,其輸出結果是5130,0
這裏就可以更直觀地看出其不靠譜了,10和2怎麼可能變成這麼大的數字?
那麼最後,我們再回過頭看本篇文章開頭的那道程序,結果我就在這裏公佈了
十進制 10,20
十六進制0000000a,00000014
看到這兩個十六進制數字,是不是感到莫名親切?
總結:指針與數字間的加法,需要進行調整,其調整權重爲指針所指向的類型長度,如:上面的例子,其權重就爲sizeof(int)
總的計算式爲:指針+(數字 * 權重)。
指針與數字間的減法,與指針加法類似,在此不贅述。其總的計算式爲:指針 -(數字 * 權重)
接下來,我們討論指針與指針之間的運算。
首先,我們需要知道的是,每一個運算都有其意義,例如:指針和一個數字進行加減運算,其意義是改變了指針指向的地址,使其嚴格指向下一個數據。而我們現在需要考慮指針之間的運算是什麼含義?
我們先看 “指針+指針” ,這個運算有意義嗎?顯然是沒有的,我們拿時間來作比喻,下午三點加下午四點得出來的值是什麼?你肯定會是七,但這個值有意義嗎?就算你說這個七是七點的意思,但時鐘裏每一個時刻都是獨立的,兩個時刻相加,你只是把他們的值加起來,就其本身而言並無意義。所以指針之間不存在加法運算。
然後我們看 “指針-指針”,還是拿時間來舉例子,下午七點,減去下午三點,得出來的值是什麼含義? 答案是四。你能說這代表的是四點這個時刻嗎?並不是,這個四代表的是,三點和七點之間間隔着四個小時。
到這裏指針之間減法的意義就呼之欲出了,兩個指針相減得出的應該是兩者之間元素的個數。
指針之間相減其計算式爲:(指針 - 指針)/權重
下面是關於指針運算的程序,僅作爲練習題,其結果已寫明,請自行對照。
指針 + 數字:
int main()
{
int *p = (int *)2000;
printf("%d\n",p+4);//2016
printf("%d\n",(short *)p+4);//2008
printf("%d\n",(double *)p+4);//2032
printf("%d\n",(float **)p+4);//2016
printf("%d\n",(unsigned short *)p+4);//2008
printf("%d\n",(long *)p+4);//2016
printf("%d\n",(char *)p+4);//2004
printf("%d\n",(unsigned long long)p+4);//2004
return 0;
}
指針 - 數字:
int main()
{
int *p = (int *)0x2010;
printf("%x\n",p-2);//2008
printf("%x\n",(short *)p-2);//200c
printf("%x\n",(unsigned long *)p-2);//2008
printf("%x\n",(long long **)p-2);//2008
printf("%x\n",(float *)p-2);//2008
printf("%x\n",(double *)p-2);//2000
printf("%x\n",(char *)p-2);//200e
printf("%x\n",(unsigned long )p-2);//200e
return 0;
}
指針 - 指針:
int main()
{
int arr[10] = {1};
int *p = &arr[1];
int *q = &arr[9];
printf("%d\n",p-q);//-8
printf("%d\n",q-p);//8
printf("%d\n",(short *)q-(short *)p);//16
printf("%d\n",(double *)q-(double *)p);//4
printf("%d\n",(int ***)q-(int ***)p);//8
printf("%d\n",(char **)q-(char **)p);//8
printf("%d\n",(long)q-(long)p);//32
return 0;
}
其結果皆爲標準答案,若不符請仔細思考,請特別注意*的存在與否以及各類型的字節數!