這東西實際編程時一直無視的,范圍小了就換個大點的表示形式,但是總覺得基礎知識還是掌握得好,免得到時候用移位運算或類型轉換或筆試題時要花時間想。
C語言的基本類型有char、int、float、double,另外還有說明符long、short、signed和unsigned。
首先要注意在不同操作系統中類型大小不一樣,下面的情況只是考慮其中一種情況。
int和char均默認為signed,二進制的最高一位來表示符號,0為正1為負。
假如short int是16位,由於第1位表示正負,所以只剩15位表示實際數值,范圍為-2^15到2^15-1
舉例,按照原碼表示:
0000 0000 0000 0101表示5
1000 0000 0000 0101表示-5
反碼就是符號位不變,數值位取反,比如5就表示為0111 1111 1111 1010
但是這樣問題來了,1000 0000 0000 0000跟0000 0000 0000 0000表示的都是0,這樣0就有2種編碼方式。
所以C語言采取了補碼表示,1000 0000 0000 0000表示的是-2^15而非0。
補碼:1、對於正數,補碼與原碼相同;2、對於負數,數值位的絕對值取反后在最低位加1。
PS:負數轉整數也是取反后最低位加1(不是減1)
因此,C語言中是用表示-5的是1111 1111 1111 1011
那么對負整數逐次進行自加運算得到結果如下
-4 1111 1111 1111 1100
-3 1111 1111 1111 1101
-2 1111 1111 1111 1110
-1 1111 1111 1111 1111
於是-1再自加后所有位數全部變為0,0的表示形式就變成了0000 0000 0000 0000,表示0的只有一種形式。
C語言支持移位運算,即將數據看成二進制數,對其向左或向右移動若干位。
邏輯移位:移出去的位丟棄,空位補0
算術移位:移出去的位丟棄,空位補符號位(只有當有符號數做右移運算時才是算術移位)
0000 0101向左移2位(即5<<2)后變成 0001 01??,?處補0,所以結果是0001 0100,為20.
1111 1011向左移2位(即-5<<2)后變成1110 11??,?處補0,所以結果是1110 1100,按照補碼規則,為正數0001 0011的相反數加1,即-(19+1)=-20
所以左移很簡單,可以i << j可以替代乘法運算i*2j,運算效率更高。
要注意的是,雖然一般不會用到,但是左移位數超過該數值類型最大位數時,編譯器會用位數求余,所以這就跟具體類型大小有關。
0010 1001向右移2位(即41>>2)后變成??00 1010,正數?處補0,所以結果是0000 1010,為10。
1101 0111向右移2位(即-41>>2)后變成了??11 0101,負數?處補1,所以結果是1111 0101,為正數0000 1010的相反數加1,即-(10+1)=-11
可以看出絕對值相同的正數和負數右移同樣位數后得出的結果並不一致,i>>j並不能等價於i/2j!-41/4的結果是-10!
最后談談unsigned轉signed,以char為例。
char在計算機內部是用一個字節的二進制來表示的,這里假定默認為signed,表示范圍為-128到127。
對於char c = 128; c的二進制表示為1000 0000,如果轉換成int輸出是-127。
char轉換成short int並不是說位數增加了,而是把它當成short int來解釋,因此c還是1000 0000,表示的是-127,而不會因為轉型為int就變成了0000 0000 1000 0000
看下面一段代碼,signed轉unsigned
int _tmain(int argc, _TCHAR* argv[]) { char c = 128; unsigned char cu = c; short int i = cu; cout << i; return 0; }
把c轉換成unsigned char后,再轉換成short int,那么輸出的就是128,轉型后還是1000 0000,但是按照unsigned的解釋,最高位不再是符號位,而是數值位,所以結果就是2^8=128。
好了,再看下面一段代碼,unsigned轉signed
int _tmain(int argc, _TCHAR* argv[]) { unsigned char cu = 255; char c = cu; short int i = c; cout << i; return 0; }
255的unsigned表示為 1111 1111,轉換為signed后,符號位1代表是負數,數值位轉換成十進制后是127,按照補碼的定義結果為-1。
以前用OpenCV處理圖像時,經常被繞住,因為IplImage*的ImageData是char表示的,而處理圖像時一般都轉化成了unsigned char,回顧了補碼的概念后那么下面這個對應就好理解了。
數值區間 | [0,127] | [-128,-1] |
char | x | x |
unsigned char | x | 256+x |