說起進制,大家都能想到二進制、10進制、16進制、8進制等等,但是在互聯網應用開發中,卻很少用到這些換算。在物聯網短指令應用中,卻十分常見。
理解字節本質和二進制
無論是互聯網應用還是物聯網應用,在網絡傳輸層傳送的其實都是二進制數據。因為現代通信設備對信號處理都是用的數字電路,數字電路的輸入輸出只有兩種狀態,那就是高電平或低電平,也就是對應二進制數據的1和0。按照一定的時序和時鍾基准,就可以代表了不同的信息。理解二進制數據的形成對物聯網開發的深入是很有必要的。
上圖中綠色線代表數據電平,紅色線代表用於鑒別二進制每一個數據位的時間基准。
在前面我們已經說到網絡傳輸中是按照字節傳輸的,而每個字節通常是按照8位二進制組成的,那么上圖說明了一個字節的數據在數字電路中電平變化情況,也是在網絡傳輸中各個通信設備間傳輸的電平變化情況。綠色線是數據線上的電平,可以看到有兩個凸起的高電平,高電平代表1,低電平代表0,那么用二進制表示,是不是就是“01010”呢,事實上是不對的。上圖的一個字節的二進制值應該是“10011000”,為什么會識別多識別出來幾個位呢?原因是還有一個時間間隔的基准參照,就是時鍾線上的電平變化。
每次從低電平變化為高電平,再從高電平變化為低電平,這個過程產生的電平波形是個凸起的方波,我們稱之為高脈沖(因像脈搏跳動一樣而得名,也有用低脈沖的,原理相同)。時鍾線上連續不斷產生着固定時間間隔和脈沖寬度的高脈沖,而數字電路在每個時鍾高脈沖到來的時候,去識別一下數據線上的電平高低,如果是高就代表當前數據為是1,否則為0。就這樣每一個時鍾高脈沖都去識別一次,將結果依次記錄下來,就組成了二進制數據。上圖中數據線上的第二個高電平被識別成二進制“11”,就是因為在數據線高電平期間經歷了兩個時鍾高脈沖,所以是兩個1,而不是一個1,數據線上的低電平被識別成多個0的原理也是一樣的。
一個字節由8個二進制位組成,通常標准是高位在前,最低位序號是0,最高位序號是7,所以上圖中時鍾高脈沖上面的數字是描述一個字節中的8個位的序號,也就是順序。傳完一個字節緊接着傳下一個字節,原理相同。
為什么叫二進制?我們知道通常生活中使用的數字計數是10進制的,因逢10進1而得名。那二進制也是因為逢2進1而得名的。一個字節的8位二進制實際就是一個計數標記,由8個位組合在一起表示。因為每個位只能有0和1兩種變化,所以要計數到2的時候就得進位了。看下面一組數據:
0000 0000 //這個字節的值是0,也就是10進制的0,中間空格為書寫描述方便,實際不存在
0000 0001 //這個字節的值是1,也就是10進制的1
0000 0010 //這個字節的值是2,也就是10進制的2,需進位
0000 0011 //這個字節的值是3,也就是10進制的3
0000 0100 //這個字節的值是4,也就是10進制的4,需進位
0000 0101 //這個字節的值是5,也就是10進制的5
......
1111 1111 //這個字節的值是255,也就是10進制的255
通過上面一組數據,就能看出逢2進1的關系了,也知道為什么一個字節的值是在0-255之間了。那么8個位中,只有一個位為1,其他位為0的時候,分別在不同位置時,他們的值是怎樣的關系呢,看下面一組數據:
0000 0001 //這個字節的值是1
0000 0010 //這個字節的值是2
0000 0100 //這個字節的值是4
0000 1000 //這個字節的值是8
0001 0000 //這個字節的值是16
0010 0000 //這個字節的值是32
0100 0000 //這個字節的值是64
1000 0000 //這個字節的值是128
從上面這組數據可以看出,值為1的位,每前進1位,其值就會是之前的2倍。這與10進制的1、10、100等每前進1位值就是之前的10倍是一樣的規律,這樣更好理解二進制位變化的關系了。那么我們在代碼中,定義一個整數變量,賦值之后,它具體的二進制是什么樣的呢,再看下面例子:
// C語言代碼
unsigned char tmp = 65;
定義了一個無符號8位整數(單字節)變量 tmp,並同時給賦了初值為10進制整數65,運行時這個變量的初值的二進制(也是內存中實際保存的形式)是“0100 0001”,因為這個二進制對應的10進制值就是65。而不是保存6和5這兩個數字的ASCii值(54,53)到內存中的。
那么單字節最大值是255,大於255的值是怎么表示的呢,各個語言都有不同的整數數據類型,他們對應的字節數量是不一樣的,看下面的例子:
1111 1111 //255,無符號單字節整數最大值
1111 1111 1111 1111 //65535,無符號雙字節整數最大值
1111 1111 1111 1111 1111 1111 1111 1111 //4294967295,無符號四字節整數最大值
根據上面這組數據,在你使用的開發語言的數據類型中找到該數據類型是多少個字節的,就知道他可以最大記錄的數值是多少了。
對於整數變量是這樣記錄的,但是字符這類的是如何記錄的呢,這就不得不提ASCii碼(美國信息交換標准代碼)。ASCii碼將常見的英文字母、阿拉伯數字、英文標點符號等可見字符和常用的非可見控制符號都定義了對應的二進制值,每個符號占用一個字節,具體可網絡搜索“ASCii碼”有完整介紹,這里就不用詳細介紹了。定義字符變量后,實際的變量二進制值是什么,看下面的例子:
// C語言代碼
unsigned char strA = 'A'; //變量二進制值:0100 0001;10進制值:65
unsigned char str1 = '1'; //變量二進制值:0011 0001;10進制值:49
上例中變量str1的值為什么是49而不是1,是因為這個‘1’是字符1,也就相當於是我們寫字描述數量用的標識符號1,而不是計數時候的整數值本身,所以要使用字符‘1’的ASCii碼值49來表示出來,對應的二進制為“0011 0001”。還有個差別,這個變量的值實際是整數49,但是在系統調試輸出的時候,會顯示出字符‘1’的,也就是可見的。而真正的那個整數1是需要轉換后才可以看到他的值的,否則不可見或看到的是錯誤的或亂碼。大寫字母A的ASCii碼值是65,所以變量strA實際記錄的也是整數65的二進制值。
理解十六機制
理解了字節的本質是二進制組成的,也知道了與十進制的對應關系,已經可以與日常計數換算了,那么十六進制又是什么鬼,為什么還要弄出來一個十六進制呢?
首先我們看二進制的寫法,一個字節要寫8次,顯然很不方便,也不好讀,口算成十進制數有很大難度。那么十六進制標准書寫是兩個字符表示一個字節,等寬制的,便於編輯瀏覽。更重要的是與二進制的換算剛好將8位分成兩部分,每4位對應一個字符,兩個字符拼在一起代表了完整的8位。下面我們通過數據看一下對應關系:
0000 1010 //16進制為0A,10進制為10
0000 1111 //16進制為0F,10進制為15
0001 0000 //16進制為10,10進制為16
1000 0010 //16進制為82,10進制為130
1111 1111 //16進制為FF,10進制為255
十六進制,顧名思義應是逢16進1才對,可是阿拉伯數字只有0-9,那么逢16進1至少要能表示到15才可以。因此十六進制在0-9的基礎上又增加了A-F這6個字母,分別代表10-15。對應10進制值,00表示0,09表示9,0A表示10,0F表示15,10表示16(因為滿16就進位了)。上圖中把一個字節的8個位四四分開,分別對應前后兩個16進制字符,就很快可以換算出來。
第一組前四位都是0,所以十六進制第一個字符寫0;后四位是1010,是十進制的10,用十六進制應該是A,所以第二個字符應該是A。這樣合起來的十六進制值就是0A。
第二組前四位都是0,所以十六進制第一個字符寫0;后四位是1111,是十進制的15,用十六進制應該是F,所以第二個字符應該是F。這樣合起來的十六進制值就是0F。
第三組前四位是0001,十六進制值應該是1,所以十六進制第一個字符寫1;后四位都是0,用十六進制應該是0,所以第二個字符應該是0。這樣合起來的十六進制值就是10。
第四組前四位是1000,十六進制值應該是8,所以十六進制第一個字符寫8;后四位是0010,用十六進制應該是2,所以第二個字符應該是2。這樣合起來的十六進制值就是82。
第四組前四位是1111,十六進制值應該是F,所以十六進制第一個字符寫F;后四位是1111,用十六進制應該是F,所以第二個字符應該是F。這樣合起來的十六進制值就是FF,也就是十進制的255,單字節的最大值。
十六進制在不同的編程語言里面,標識符(用於標注聲明這是16進制的數字)有所不同,在C語言里用“0x”標識,如下面代碼:
// C語言代碼
unsigned char a = 0xB3; //變量a等於十進制的179
unsigned char b = 0x0C; //變量b等於十進制的12
關於十六進制字面量的表示方法請查閱各語言的官方手冊,這里不再 一一列舉。
關於數據值的溝通
在我們最初的物聯網開發團隊中,嵌入式團隊與服務端團隊溝通時,經常出現這樣的發問“你那邊是發送的16進制的數據還是10進制的數據啊”,或類似“你是以16進制形式發送的還是以10進制形式發送的啊”、“你用16進制發的,我也得轉成16進制啊,能不能你改成10進制發送啊”等等。
為什么會有上面的問題,上面的問題應該這樣問嗎?后來我發現從互聯網開發轉過來的程序員多數會有這個問題,這個問題是不應該這樣問的,究其原因主要有一下幾種:
1、嵌入式開發人員涉及到數據的底層操作和編輯,多數都是習慣用16進制描述數據,數據編輯器也多數都是16進制的,很少有10進制的。所以溝通的時候,16進制的數值描述首先從嵌入式人員說出來了。
2、由於上面原因,嵌入式的文檔中描述協議標志的時候,也是用16進制進行定義的,這方便數據編輯(后面會討論到),所以在討論文檔的時候也會出現16進制的描述。
3、互聯網開發人員多數習慣了10進制的數值描述,對進制轉換和字節的本質因長期不涉及相對底層的開發,就搞不清之間的關系了。
4、最重要的是問的人對數據傳輸的字節底層含義已經淡忘了,所以一時不能適應了。
其實在溝通中,發送方只要告訴接收方發送的是哪個進制的多少值就可以了。接收方用16進制讀取還是10進制讀取決定與自己,因為數據本身就是那個二進制沒變。你讀的進制不同,值的面量就不同了,根據需要自己選擇換算就可以了。例如下面的代碼:
// C語言代碼
unsigned char a = 0x0B; //16進制賦值
unsigned char b = 11; //10進制賦值
if(a == b){
printf('OK');
}else{
printf('NO');
}
上面代碼執行后,控制台輸出的是“OK”,因為十六進制的 0x0B 和十進制的 11 的二進制是一樣的。
本節完,待續......