第六章 二進位、八進位、十六進位
6.1 為什麼需要八進位和十六進位?
6.2 二、八、十六進位數轉換到十進位數
6.2.1 位元轉換為十進位數
6.2.2 八位元轉換為十進位數
6.2.3 八位元的表達方法
6.2.4 八位元在轉義符中的使用
6.2.5 十六進位數轉換成十進位數
6.2.6 十六進位數的表達方法
6.2.7 十六進位數在轉義符中的使用
6.3 十進位數轉換到二、八、十六進位數
6.3.1 10進位數轉換為2進位數
6.3.2 10進位數轉換為8、16進位數
6.4 二、十六進位數互相轉換
6.5 原碼、反碼、補碼
6.6 通過調試查看變數的值
6.7 本章小結
這是一節“前不著村後不著店”的課。不同進位之間的轉換純粹是數學上的計算。不過,你不必擔心會有麼複雜,無非是乘或除的計算。
生活中其實很多地方的計數方法都多少有點不同進位的影子。
比如我們最常用的10進位,其實起源於人有10個指頭。如果我們的祖先始終沒有擺脫手腳不分的境況,我想我們現在一定是在使用20進位。
至於二進位……沒有襪子稱為0隻襪子,有一隻襪子稱為1隻襪子,但若有兩襪子,則我們常說的是:1雙襪子。
生活中還有:七進位,比如星期。十六進位,比如小時或“一打”,六十進位,比如分鐘或角度……
6.1 為什麼需要八進位和十六進位?
編程中,我們常用的還是10進位……必竟C/C++是進階語言。
比如:
int a = 100,b = 99;
不過,由於資料在電腦中的表示,最終以二進位的形式存在,所以有時候使用二進位,可以更直觀地解決問題。
但,位元太長了。比如int 類型佔用4個位元組,32位。比如100,用int類型的位元表達將是:
0000 0000 0000 0000 0110 0100
面對這麼長的數進行思考或操作,沒有人會喜歡。因此,C,C++ 沒有提供在代碼直接寫位元的方法。
用16進位或8進位可以解決這個問題。因為,進位越大,數的表達長度也就越短。不過,為什麼偏偏是16或8進位,而不其它的,諸如9或20進位呢?
2、8、16,分別是2的1次方,3次方,4次方。這一點使得三種進位之間可以非常直接地互相轉換。8進位或16進位縮短了位元,但保持了位元的表達特點。在下面的關於進位轉換的課程中,你可以發現這一點。
6.2 二、八、十六進位數轉換到十進位數6.2.1 位元轉換為十進位數
位元第0位的權值是2的0次方,第1位的權值是2的1次方……
所以,設有一個位元:0110 0100,轉換為10進位為:
下面是豎式:
0110 0100 換算成 十進位
第0位 0 * 20 = 0
第1位 0 * 21 = 0
第2位 1 * 22 = 4
第3位 0 * 23 = 0
第4位 0 * 24 = 0
第5位 1 * 25 = 32
第6位 1 * 26 = 64
第7位 0 * 27 = 0 +
---------------------------
100
用橫式計算為:
0 * 20 + 0 * 21 + 1 * 22 + 1 * 23 + 0 * 24 + 1 * 25 + 1 * 26 + 0 * 27 = 100
0乘以多少都是0,所以我們也可以直接跳過值為0的位:
1 * 22 + 1 * 23 + 1 * 25 + 1 * 26 = 100
6.2.2 八位元轉換為十進位數
八進位就是逢8進1。
八位元採用 0~7這八數來表達一個數。
八位元第0位的權值為8的0次方,第1位權值為8的1次方,第2位權值為8的2次方……
所以,設有一個八位元:1507,轉換為十進位為:
用豎式表示:
1507換算成十進位。
第0位 7 * 80 = 7
第1位 0 * 81 = 0
第2位 5 * 82 = 320
第3位 1 * 83 = 512 +
--------------------------
839
同樣,我們也可以用橫式直接計算:
7 * 80 + 0 * 81 + 5 * 82 + 1 * 83 = 839
結果是,八位元 1507 轉換成十進位數為 839
6.2.3 八位元的表達方法
C,C++語言中,如何表達一個八位元呢?如果這個數是 876,我們可以斷定它不是八位元,因為八位元中不可能出7以上的阿拉伯數字。但如果這個數是123、是567,或12345670,那麼它是八位元還是10進位數,都有可能。
所以,C,C++規定,一個數如果要指明它採用八進位,必須在它前面加上一個0,如:123是十進位,但0123則表示採用八進位。這就是八位元在C、C++中的表達方法。
由於C和C++都沒有提供位元的表達方法,所以,這裡所學的八進位是我們學習的,CtC++語言的數值表達的第二種進位法。
現在,對於同樣一個數,比如是100,我們在代碼中可以用平常的10進位表達,例如在變數初始化時:
int a = 100;
我們也可以這樣寫:
int a = 0144; //0144是八進位的100;一個10進位數如何轉成8進位,我們後面會學到。
千萬記住,用八進位表達時,你不能少了最前的那個0。否則電腦會通通當成10進位。不過,有一個地方使用八位元時,卻不能使用加0,那就是我們前面學的用於表達字元的“轉義符”表達法。
6.2.4 八位元在轉義符中的使用
我們學過用一個轉義符'/'加上一個特殊字母來表示某個字元的方法,如:'/n'表示換行(line),而'/t'表示Tab字元,'/''則表示單引號。今天我們又學習了一種使用轉義符的方法:轉義符'/'後面接一個八位元,用於表示ASCII碼等於該值的字元。
比如,查一下第5章中的ASCII碼錶,我們找到問號字元(?)的ASCII值是63,那麼我們可以把它轉換為八進值:77,然後用 '/77'來表示'?'。由於是八進位,所以本應寫成 '/077',但因為C,C++規定不允許使用斜杠加10進位數來表示字元,所以這裡的0可以不寫。
事實上我們很少在實際編程中非要用轉義符加八位元來表示一個字元,所以,6.2.4小節的內容,大家僅僅瞭解就行。
6.2.5 十六進位數轉換成十進位數
2進位,用兩個阿拉伯數字:0、1;
8進位,用八個阿拉伯數字:0、1、2、3、4、5、6、7;
10進位,用十個阿拉伯數字:0到9;
16進位,用十六個阿拉伯數字……等等,阿拉伯人或說是印度人,只發明了10個數字啊?
16進位就是逢16進1,但我們只有0~9這十個數字,所以我們用A,B,C,D,E,F這五個字母來分別表示10,11,12,13,14,15。字母不區分大小寫。
十六進位數的第0位的權值為16的0次方,第1位的權值為16的1次方,第2位的權值為16的2次方……
所以,在第N(N從0開始)位上,如果是是數 X (X 大於等於0,並且X小於等於 15,即:F)表示的大小為 X * 16的N次方。
假設有一個十六進數 2AF5, 那麼如何換算成10進位呢?
用豎式計算:
2AF5換算成10進位:
第0位: 5 * 160 = 5
第1位: F * 161 = 240
第2位: A * 162 = 2560
第3位: 2 * 163 = 8192 +
-------------------------------------
10997
直接計算就是:
5 * 160 + F * 161 + A * 162 +2 * 163 = 10997
(別忘了,在上面的計算中,A表示10,而F表示15)
現在可以看出,所有進位換算成10進位,關鍵在於各自的權值不同。
假設有人問你,十進數 1234 為什麼是 一千二百三十四?你盡可以給他這麼一個算式:
1234 = 1 * 103 + 2 * 102 + 3 * 101 + 4 * 100
6.2.6 十六進位數的表達方法
如果不使用特殊的書寫形式,16進位數也會和10進位相混。隨便一個數:9876,就看不出它是16進位或10進位。
C,C++規定,16進位數必須以 0x開頭。比如 0x1表示一個16進位數。而1則表示一個十進位。另外如:0xff,0xFF,0X102A,等等。其中的x也也不區分大小寫。(注意:0x中的0是數字0,而不是字母O)
以下是一些用法樣本:
int a = 0x100F;
int b = 0x70 + a;
至此,我們學完了所有進位:10進位,8進位,16進位數的表達方式。最後一點很重要,C/C++中,10進位數有正負之分,比如12表示正12,而-12表示負12,;但8進位和16進位只能用達無符號的正整數,如果你在代碼中裡:-078,或者寫:-0xF2,C,C++並不把它當成一個負數。
6.2.7 十六進位數在轉義符中的使用
轉義符也可以接一個16進位數來表示一個字元。如在6.2.4小節中說的 '?' 字元,可以有以下表達方式:
'?' //直接輸入字元
'/77' //用八進位,此時可以省略開頭的0
'/0x3F' //用十六進位
同樣,這一小節只用於瞭解。除了Null 字元用八位元 '/0' 表示以外,我們很少用後兩種方法表示一個字元。
6.3 十進位數轉換到二、八、十六進位數6.3.1 10進位數轉換為2進位數
給你一個十進位,比如:6,如果將它轉換成位元呢?
10進位數轉換成位元,這是一個連續除2的過程:
把要轉換的數,除以2,得到商和餘數,
將商繼續除以2,直到商為0。最後將所有餘數倒序排列,得到數就是轉換結果。
聽起來有些糊塗?我們結合例子來說明。比如要轉換6為位元。
“把要轉換的數,除以2,得到商和餘數”。
那麼:
要轉換的數是6, 6 ÷ 2,得到商是3,餘數是0。 (不要告訴我你不會計算6÷3!)
“將商繼續除以2,直到商為0……”
現在商是3,還不是0,所以繼續除以2。
那就: 3 ÷ 2, 得到商是1,餘數是1。
“將商繼續除以2,直到商為0……”
現在商是1,還不是0,所以繼續除以2。
那就: 1 ÷ 2, 得到商是0,餘數是1 (拿筆紙算一下,1÷2是不是商0餘1!)
“將商繼續除以2,直到商為0……最後將所有餘數倒序排列”
好極!現在商已經是0。
我們三次計算依次得到餘數分別是:0、1、1,將所有餘數倒序排列,那就是:110了!
6轉換成二進位,結果是110。
把上面的一段改成用表格來表示,則為:
| 被除數 |
計算過程 |
商 |
餘數 |
| 6 |
6/2 |
3 |
0 |
| 3 |
3/2 |
1 |
1 |
| 1 |
1/2 |
0 |
1 |
(在電腦中,÷用 / 來表示)
如果是在考試時,我們要畫這樣表還是有點費時間,所更常見的換算過程是使用的連除:
(圖:1)
請大家對照圖,表,及文字說明,並且自已拿筆計算一遍如何將6轉換為位元。
說了半天,我們的轉換結果對嗎?位元110是6嗎?你已經學會如何將位元轉換成10進位數了,所以請現在就計算一下110換成10進位是否就是6。
6.3.2 10進位數轉換為8、16進位數
非常開心,10進位數轉換成8進位的方法,和轉換為2進位的方法類似,惟一變化:除數由2變成8。
來看一個例子,如何將十進位數120轉換成八位元。
用表格表示:
| 被除數 |
計算過程 |
商 |
餘數 |
| 120 |
120/8 |
15 |
0 |
| 15 |
15/8 |
1 |
7 |
| 1 |
1/8 |
0 |
1 |
120轉換為8進位,結果為:170。
非常非常開心,10進位數轉換成16進位的方法,和轉換為2進位的方法類似,惟一變化:除數由2變成16。
同樣是120,轉換成16進位則為:
| 被除數 |
計算過程 |
商 |
餘數 |
| 120 |
120/16 |
7 |
8 |
| 7 |
7/16 |
0 |
7 |
120轉換為16進位,結果為:78。
請拿筆紙,採用(圖:1)的形式,演算上面兩個表的過程。
6.4 二、十六進位數互相轉換
二進位和十六進位的互相轉換比較重要。不過這二者的轉換卻不用計算,每個C,C++程式員都能做到看見位元,直接就能轉換為十六進位數,反之亦然。
我們也一樣,只要學完這一小節,就能做到。
首先我們來看一個位元:1111,它是多少呢?
你可能還要這樣計算:1 * 20 + 1 * 21 + 1 * 22 + 1 * 23 = 1 * 1 + 1 * 2 + 1 * 4 + 1 * 8 = 15。
然而,由於1111才4位,所以我們必須直接記住它每一位的權值,並且是從高位往低位記,:8、4、2、1。即,最高位的權值為23 = 8,然後依次是 22 = 4,21=2, 20 = 1。
記住8421,對於任意一個4位的位元,我們都可以很快算出它對應的10進位值。
下面列出四位位元 xxxx 所有可能的值(中間略過部分)
僅4位的2進位數 快速計算方法 十進位值 十六進值
1111 = 8 + 4 + 2 + 1 = 15 F
1110 = 8 + 4 + 2 + 0 = 14 E
1101 = 8 + 4 + 0 + 1 = 13 D
1100 = 8 + 4 + 0 + 0 = 12 C
1011 = 8 + 4 + 0 + 1 = 11 B
1010 = 8 + 0 + 2 + 0 = 10 A
1001 = 8 + 0 + 0 + 1 = 10 9
....
0001 = 0 + 0 + 0 + 1 = 1 1
0000 = 0 + 0 + 0 + 0 = 0 0
位元要轉換為十六進位,就是以4位一段,分別轉換為十六進位。
如(上行為二制數,下面為對應的十六進位):
1111 1101 , 1010 0101 , 1001 1011
F D , A 5 , 9 B
反過來,當我們看到 FD時,如何迅速將它轉換為位元呢?
先轉換F:
看到F,我們需知道它是15(可能你還不熟悉A~F這五個數),然後15如何用8421湊呢?應該是8 + 4 + 2 + 1,所以四位全為1 :1111。
接著轉換 D:
看到D,知道它是13,13如何用8421湊呢?應該是:8 + 2 + 1,即:1011。
所以,FD轉換為位元,為: 1111 1011
由於十六進位轉換成二進位相當直接,所以,我們需要將一個十進位數轉換成2進位數時,也可以先轉換成16進位,然後再轉換成2進位。
比如,十進位數 1234轉換成二制數,如果要一直除以2,直接得到2進位數,需要計算較多次數。所以我們可以先除以16,得到16進位數:
| 被除數 |
計算過程 |
商 |
餘數 |
| 1234 |
1234/16 |
77 |
2 |
| 77 |
77/16 |
4 |
13 (D) |
| 4 |
4/16 |
0 |
4 |
結果16進位為: 0x4D2
然後我們可直接寫出0x4D2的二進位形式: 0100 1011 0010。
其中對映關係為:
0100 -- 4
1011 -- D
0010 -- 2
同樣,如果一個位元很長,我們需要將它轉換成10進位數時,除了前面學過的方法是,我們還可以先將這個二進位轉換成16進位,然後再轉換為10進位。
下面舉例一個int類型的位元:
01101101 11100101 10101111 00011011
我們按四位一群組轉換為16進位: 6D E5 AF 1B
6.5 原碼、反碼、補碼
結束了各種進位的轉換,我們來談談另一個話題:原碼、反碼、補碼。
我們已經知道電腦中,所有資料最終都是使用位元表達。
我們也已經學會如何將一個10進位數如何轉換為位元。
不過,我們仍然沒有學習一個負數如何用二進位表達。
比如,假設有一 int 類型的數,值為5,那麼,我們知道它在電腦中表示為:
00000000 00000000 00000000 00000101
5轉換成二制是101,不過int類型的數佔用4位元組(32位),所以前面填了一堆0。
現在想知道,-5在電腦中如何表示?
在電腦中,負數以其正值的補碼形式表達。
什麼叫補碼呢?這得從原碼,反碼說起。
原碼:一個整數,按照絕對值大小轉換成的位元,稱為原碼。
比如 00000000 00000000 00000000 00000101 是 5的 原碼。
反碼:將位元按位取反,所得的新位元稱為原位元的反碼。
取反操作指:原為1,得0;原為0,得1。(1變0; 0變1)
比如:將00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。
稱:11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反碼。
反碼是相互的,所以也可稱:
11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互為反碼。
補碼:反碼加1稱為補碼。
也就是說,要得到一個數的補碼,先得到反碼,然後將反碼加上1,所得數稱為補碼。
比如:00000000 00000000 00000000 00000101 的反碼是:11111111 11111111 11111111 11111010。
那麼,補碼為:
11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
所以,-5 在電腦中表達為:11111111 11111111 11111111 11111011。轉換為十六進位:0xFFFFFFFB。
再舉一例,我們來看整數-1在電腦中如何表示。
假設這也是一個int類型,那麼:
1、先取1的原碼:00000000 00000000 00000000 00000001
2、得反碼: 11111111 11111111 11111111 11111110
3、得補碼: 11111111 11111111 11111111 11111111
可見,-1在電腦裡用二進位表達就是全1。16進位為:0xFFFFFF。
一切都是紙上說的……說-1在電腦裡表達為0xFFFFFF,我能不能親眼看一看呢?當然可以。利用C++ Builder的調試功能,我們可以看到每個變數的16進位值。
6.6 通過調試查看變數的值
下面我們來動手完成一個小小的實驗,通過調試,觀察變數的值。
我們在代碼中聲明兩個int 變數,並分別初始化為5和-5。然後我們通過CB提供的調試手段,可以查看到程式運行時,這兩個變數的十進位值和十六進位值。
首先建立一個控制台工程。加入以下黑體部分(就一行):
//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
int aaaa = 5, bbbbb = -5;
return 0;
}
//---------------------------------------------------------------------------
沒有我們熟悉的的那一行:
getchar();
所以,如果全速運行這個程式,將只是DOS視窗一閃而過。不過今天我們將通過設定斷點,來使用程式在我們需要的地兒停下來。
設定斷點:最常用的調試方法之一,使用程式在運行時,暫停在某一代碼位置,
在CB裡,設定斷點的方法是在某一行代碼上按F5或在行首欄內單擊滑鼠。
如:
在中,我們在return 0;這一行上設定斷點。斷點所在行將被CB以紅色顯示。
接著,運行程式(F9),程式將在斷點處停下來。
(請注意兩張圖的不同,前面的圖是運行之前,後面這張是運行中,左邊的箭頭表示運行運行到哪一行)
當程式停在斷點的時,我們可以觀察當前程式碼片段內,可見的變數。觀察變數的方法很多種,這裡我們學習使用Debug Inspector (調試期檢視),來全面觀察一個變數。
以下是調出觀察某一變數的 Debug Inspector 視窗的方法:
先確保代碼視窗是使用中視窗。(用滑鼠點一下代碼視窗)
按下Ctrl鍵,然後將滑鼠挪到變數 aaaa 上面,你會發現代碼中的aaaa變藍,並且出現底線,效果如網頁中的超連結,而滑鼠也變成了小手狀:
點擊滑鼠,將出現變數aaaa的檢視視窗:
(筆者使用的作業系統為WindowsXP,視窗的外觀與Win9X有所不同)
從該視窗,我可以看到:
aaaa :變數名
int :變數的資料類型
0012FF88:變數的記憶體位址,請參看5.2 變數與記憶體位址;地址總是使用十六進位表達
5 : 這是變數的值,即aaaa = 5;
0x00000005 :同樣是變數的值,但採用16進位表示。因為是int類型,所以佔用4位元組。
首先先關閉前面的用於觀察變數aaaa的Debug Inspector視窗。
現在,我們用同樣的方法來觀察變數bbbb,它的值為-5,負數在電腦中使用補碼錶示。
正如我們所想,-5的補碼為:0xFFFFFFFB。
再按一次F9,程式將從斷點繼續運行,然後結束。
6.7 本章小結
很難學的一章?
來看看我們主要學了什麼:
1)我們學會了如何將二、八、十六進位數轉換為十進位數。
三種轉換方法是一樣的,都是使用乘法。
2)我們學會了如何將十進位數轉換為二、八、十六進位數。
方法也都一樣,採用除法。
3)我們學會了如何快速的地互換位元和十六進位數。
要訣就在於對位元按四位一組地轉換成十六進位數。
在學習十六進位數後,我們會在很多地方採用十六進位數來替代位元。
4)我們學習了原碼、反碼、補碼。
把原碼的0變1,1變0,就得到反碼。要得到補碼,則先得反碼,然後加1。
以前我們只知道正整數在電腦裡是如何表達,現在我們還知道負數在電腦裡使用其絕對值的補碼錶達。
比如,-5在電腦中如何表達?回答是:5的補碼。
5)最後我們在上機實驗中,這會了如何設定斷點,如何調出Debug Inspector視窗觀察變數。
以後我們會學到更多的調試方法。