您現在的位置是:首頁 > 音樂首頁音樂

位操作奇技淫巧之原理加實踐

由 Unity牆外的世界 發表于 音樂2021-10-05
簡介 數字轉二進位制格式voidnumber2binary(ElemType number, char* str){int count = sizeof(ElemType) * 8

負號在計算機裡是什麼

目錄

概述機器數真值機器數的原始碼、反碼、補碼三種形式原碼反碼補碼溢位補碼與模的關係位運算子與運算或運算異或運算取反運算左移操作右移操作位運算奇淫技巧工具函式程式碼判斷一個數的奇偶性【x & 1】判斷二進位制的第 n 位為 1 或 0【x & (1<將二進位制的第 n 位設定為 1【x | (1<將二進位制的第 n 位設定為 0【x & ~(1<使用一個變數儲存多個標誌位【flag1 | flag2 | flag3 …】交換兩個數的值找出不重複的數資料合併總結參考資料

概述

最近無聊想學習一下

位操作的藝術

,想體會一下更為底層的操作,為此進行了一些學習總結。由於會涉及到一些硬體相關的理論,因此前面部分會講一下理論原理,假如不想看原理部分,直接跳到

位運算子部分

機器數

機器數(computer number)是數字在計算機中的二進位制表示形式。

機器數有兩個基本特點:

符號數字化。

由於計算機內部的硬體只能表示兩種物理狀態(用 0 和 1 表示),因此資料的正負號在機器裡就用一位二進位制 0 或者 1 來區分。通常這個符號放在二進位制數的最高位,稱

為符號位,以 0 表示 '+' 號,以 1 表示 '-' 號

機器數的大小受機器字長的限制。機器內部裝置一次能表示的二進位制位叫

機器字長

,一臺機器的字長是固定的。字長8位叫一個位元組(Byte),機器字長一般都是位元組的整數倍,如字長8位、16位、32位、64位。比如在字長為8的計算機中,十進位制數+5,其機器數為00000101;十進位制數-5,其機器數為 10000101。

真值

計算機機器數真正的值稱為真值。因為機器數的最高位是符號位,所以我們在計算真值的時候要分割槽分開。比如上面講的機器數10000101,單純作為一個二進位制數,我們轉換為十進位制是133。但是其真值是不計算符號位的,其最高位的1表示“-”。所以10000101的真值為-5。

機器數的原始碼、反碼、補碼三種形式

前面我們講過機器數是在計算機中的二進位制表示形式,但是在計算機中,這種表現形式又分為原碼、反碼、補碼等三種最常用的形式。

ps:下面舉例都是字長為8。

原碼

原碼 = 符號位 + 真值

比如:

[+5](原碼) = 0000 0101

[-5](原碼) = 1000 0101

原碼錶示與真值對應直觀,它的數值部分就是該數的絕對值,而且與真值、十進位制的轉換十分方便。但是用原碼進行加減運算的時候,會出現以下問題:

使用原碼計算表示式:1 - 1 = 0

1 - 1

= 1 + (-1)

= [0000 0001](原) + [10000 00001](原)

= -2

注意:

於計算機是沒有減法器,只有加法器,減法運算可以轉換為加上那個數的負數。

使用原碼計算 1 - 1 表示式結果居然等於 -2。

在進行原碼加減法運算時,首先判斷兩個數的符號,同號相加,異號相減。在做減法前,先判斷兩個數絕對值的大小,然後用大數減去小數,最後再確定差的符號。

例子:

7 - 13 = 7 + (-13) = 6,由於兩個數的符號存在異號,所以這裡做減法運算。首先先判斷兩個數的絕對值: |7| < |13|,然後用大數減去小數:13 - 7 = 6,最後確定差的符號,由於 13 為原式中的被減數,且符號位為 1(-),因此最後的結果為 -6。

這樣一種形式進行加減運算時,負數的符號位不能與其數值部分一道參加運算,而必須利用單獨的線路確定符號位。很顯然,這樣設計電路就很複雜,這是不經濟實用的,為了解決這個問題,反碼產生了。

反碼

反碼:正數的反碼與其原碼相同;負數的反碼是對其原碼逐位取反,但符號位除外。

用反碼來計算 1 - 1

1 - 1

= 1 + (-1)

= [0000 0001](原) + [1000 0001](原)

= [0000 0001](反) + [1111 1110](反)

= [1111 1111](反)

= [1000 0000](原)

= -0

看上去結果好像是正確的,但結果是 -0,雖然對於 0 的符號沒有什麼實際意義。但是在計算機中,0 如果用原碼和反碼來表示會有兩種形式:

[+0] = [0000 0000](原) = [0000 0000](反)

[-0] = [1000 0000](原) = [1111 1111](反)

兩種編碼就算了吧,只要結果正確什麼都可以,然而。

使用反碼計算表示式:2 - 1

2 - 1

= 2 + (-1)

= [0000 0010](原) + [1000 0001](原)

= [0000 0010](反) + [1111 1110](反)

= [0000 0000](反)

= [0000 0000](原)

= +0

使用原碼的思想去計算 2 - 1 得到的結果居然是 0。其實在進行反碼加法運算的時候發生了進位,而由於字長為 9,進位就直接省略了,便造成了錯誤。下面採用反碼的計算機解決辦法如下:

反碼的符號位相加後,如果有進位出現,則要把它送回到最低位去相加(迴圈進位)

2 - 1

= 2 + (-1)

= [0000 0010](原) + [1000 0001](原)

= [0000 0010](反) + [1111 1110](反) + [0000 0001](迴圈進位)

= [0000 0001](反)

= [0000 0001](原)

= +1

採用反碼運算雖然較好的解決了原碼運算所遇到的問題,但由於迴圈進位需要二次算術相加,延長了計算時間,這同樣給電路帶來麻煩,這時候補碼就要登場了。

補碼

補碼:正數的補碼與原碼相同,負數的補碼等於其反碼的末位加 1

補碼運算要注意的問題:

補碼運算時,其符號位與數值部分一起參加運算。

補碼的符號位相加後,如果有進位出現,要把這個進位捨去(自然丟失)。

例子:

2 - 1

= 2 + (-1)

= [0000 0010](原) + [1000 0001](原)

= [0000 0010](反) + [1111 1110](反)

= [0000 0010](補) + [1111 1111](補)

= [0000 0001](補)

= [0000 0001](原)

= +1

補碼運算中符號位發生進位之後為什麼可以被捨棄?

對於兩位十進位制來說,下面的結果都是相同的:

28 - 1 = 27

28 + 99 = (1)27

可以看到,由於只能表示兩位十進位制,因此在 28 + 99 = 127 中,27 前面的 1 將無法顯示,因此就需要捨棄掉。

補碼運算相對來說說就簡單很多,符號位和數值部分都可以參與運算,且無需考慮進位的問題。還有就是 +0 和 -0 在補碼中只有一種形式:

[+0] = [0000 0000](原) = [0000 0000](反) = [0000 0000](補)

[-0] = [1000 0000](原) = [1111 1111](反) = [0000 0000](補)

補碼完美的解決了計算機中正數運算的問題。因此可以得出一個結論:

整型數在計算機中,使用補碼來表示。

但還有一個問題需要考慮:

算出 127 + 1 的值(在字長為 8 的機器中)

溢位

在程式中計算:127 + 1

publicstaticvoidmain(String[] args){

byte x = 127;

byte y = 1;

byte k = (byte) (x+y);

System。out。println(k); //-128

System。out。println(Byte。MIN_VALUE+“~”+Byte。MAX_VALUE); //-128~127

}

byte 在計算機正好是一個位元組,也就是8位二進位制序列。可以發現127 + 1 結果不是 128,反而是 -128,這就是結果發生了溢位。因為byte表示數的範圍是 -128-127,128 超出了這個範圍。用補碼計算如下:

1 + 127

= [0000 0001]原 + [0111 1111]原

= [0000 0001]補 + [0111 1111]補

= [1000 0000]補

可以發現這個數的符號位沒有發生進位,但是數值最高位發生了進位。在看前面的 2 - 1

2 -1

= 2 + (-1)

= [0000 0010]原 + [1000 0001]原

= [0000 0010]補 + [1111 1111]補

= [0000 0001]補 = [0000 0001]原

= +1

這個表示式符號位和數值最高位發生了進位,但是結果卻是正確的。總結如下:

符號位和數值最高位只有其中一個發生了進位了進位就為溢位

補碼與模的關係

作為程式猿,相信對於模這個概念都不陌生了吧。

那麼什麼是模呢?想象一下我們日常中使用的鐘表,它只可以顯示 0 ~ 12 點的時間。假設現在是 2 點鐘,想要將時間調到 10 點鐘的話,會有兩種調整方式:

逆時針將時針轉動 4 小時

順時針將時針轉動 8(12 - 4) 小時

從這個例子便能看出模的使用。

還記得上面有過這麼一個問題:

補碼運算中符號位發生進位之後為什麼可以被捨棄?

對於兩位十進位制來說,下面的結果都是相同的:

28 - 1 = 27

28 + 99 = (1)27

可以看到,這裡的 99 是透過 100 - 1 = 99 得來的,因此,100 就是這個例子中的模。從上面的例子中可以分析出 1 和 99 互為 100 的補數。

因此,在模的範圍內做減法,可以將

X - Y

的減法變更為

X + Y 的補數

的加法。那麼就得出一個結論:

在計算機中,負數的表達方式就是它絕對值的補數。

例如(使用 4 字長方便觀察):

[-3] = [1011](原) = [1100](反) = [1101](補)

4 字長最大能表示 15,因此 -3 的絕對值的補數為:16 - 3 = 13

[13] = [1101](原) = [1010](反) = [1011](補)

那麼問題來了,13 已經用於表示正數的 13 了,現在又要用來表示負數的 -3,這就產生了混淆。

為了解決這個問題,需要給這套規則劃分一個範圍,原來 4 字長能表示的數為 0 - 15,但現在需要拿出一半來表示負數,因此就會有下面這張圖:

位操作奇技淫巧之原理加實踐

image-20191129174314456

可以看到 4 字長的二進位制表示的正數與負數的範圍,即正數的範圍:[0 ~ 7],負數的範圍:[-8 ~ -1],負數和正數整好各佔一半。

從上面這個圖就可以很容易的找到

溢位臨界區和進位臨界區

了。例如:7 + 1 = 8 ,可以從上圖中看到, 7 順時針的下一位是 -8,而正確答案應該是 8,所以發生了

溢位

。再有:-1 + 1 = 0 ,從上圖中看到, -1 順時針的下一位為 0,且正確答案正好為 0,所以發生了

進位

因此,溢位臨界區如下圖所示:

位操作奇技淫巧之原理加實踐

溢位臨界區

進位臨界區如下圖所示:

位操作奇技淫巧之原理加實踐

進位臨界圖

位運算子

位操作奇技淫巧之原理加實踐

與運算

從下面的展示中可以看到,兩個數的二進位制位進行與運算,只要有 0,結果就為 0,全部為 1 則為 1。

00000111 (7)

& 00000101 (5)

————

00000101 (5)

或運算

從下面的展示中可以看到,兩個數的二進位制位進行與或運算,只要有 1,結果就為 1,無 1 則為 0。

00000111 (7)

| 00000101 (5)

————

00000111 (7)

異或運算

從下面的展示中可以看到,兩個數的二進位制位進行異或運算,只要相同則為 0,不同則為 1。

00000111 (7)

^ 00000101 (5)

————

00000010 (2)

取反運算

從下面的展示中可以看到,對一個數的二進位制位進行取反操作,有 1 則為 0,有 0 則為 1。

~ 00000101 (5)

————

11111010 (-6)

左移操作

從下面的展示中可以看到,對一個數的二進位制位進行左移三位的操作(1<<3),原來 1 對應二進位制位的第一個位置上的 1 向左移動到了三個位置。

00000001 (1)

>>(3) ————

00001000 (8)

右移操作

從下面的展示中可以看到,對一個數的二進位制位進行右移三位的操作(8>>3),原來 8 對應二進位制位的第四個位置上的 1 向右移動到了三個位置。

00001000 (8)

<<(3) ————

00001000 (1)

位運算奇淫技巧

工具函式程式碼

/* 用於根據型別生成對應位數的二進位制,例如 char 為 1 位元組,即 8 位,因此會顯示 8 位二進位制*/

typedefchar ElemType;

// 數字轉二進位制格式

voidnumber2binary(ElemType number, char* str)

{

int count = sizeof(ElemType) * 8;

int i;

for (i = count - 1; i >= 0; i——)

{

str[i] = (number & 1) ? ‘1’ : ‘0’;

number = number >> 1;

}

str[count] = ‘\0’;

}

// 格式化二進位制計算顯示

/*

例如:

00000111 (7)

^ 00000101 (5)

————

00000010 (2)

*/

voidbinary_calculate_format(ElemType a, ElemType b, ElemType c, char oper)

{

int count = sizeof(ElemType) * 8 + 1;

char strSplit[sizeof(ElemType) * 8 + 1];

int i;

for (i = 0; i < count-1; i++)

strSplit[i] = ‘-’;

char str[sizeof(ElemType) * 8 + 1];

number2binary(a, str);

printf(“ %s (%d)\n”, str, a);

number2binary(b, str);

printf(“%c %s (%d)\n”, oper, str, b);

printf(“ %s\n”, strSplit);

number2binary(c, str);

printf(“ %s (%d)\n”, str, c);

}

intmain()

{

// 使用方法

char str[sizeof(ElemType) * 8 + 1];

// number2binary 函式

number2binary(1, str);

printf(“%s\n”, str);

// binary_calculate_format 函式

binary_calculate_format(5, 6, 5|6, ‘|’);

return0;

}

/*

測試程式輸出:

00000001

00000101 (5)

| 00000110 (6)

————

00000111 (7)

*/

判斷一個數的奇偶性【x & 1】

ElemType x = 10;

if (x & 1)

printf(“%d 為奇數\n”, x);

else

printf(“%d 為偶數\n”, x);

binary_calculate_format(x, 1, x&1, ‘&’);

/*

測試程式輸出:

10 為偶數

00001010 (10)

& 00000001 (1)

————

00000000 (0)

*/

從輸出可以看到,其實就是判斷一個數的二進位制格式的最低位,如果等於 1 則為奇數,反之等於 0 。因為除了二進位制的最低位上的 1 表示數值 1 外,其它位上的 1 都表示 2 的 n 次冪,因此只需要判斷最低位是否為 1 便能確定該數的奇偶性。(其效果和 x % 2 相同)

判斷二進位制的第 n 位為 1 或 0【x & (1<

ElemType x = 10;

// 因為 1 正好處於二進位制的第一位,

// 所以如果需要將 1 移動到第三位,只需要移動兩次,因此這裡需要減一操作。

ElemType n = 3 - 1;

if (x & (1<

printf(“%d 的第 %d 位為 1\n”, x, n+1);

else

printf(“%d 的第 %d 位為 0\n”, x, n+1);

binary_calculate_format(x, 1<

/*

程式輸出:

10 的第 3 位為 0

00001010 (10)

& 00000100 (4)

————

00000000 (0)

*/

其實這個技巧和

判斷一個數的奇偶性

是差不多的,只不過在這個技巧中使用了左移操作,將 1 移動到了指定的位置,然後進行與運算,判斷指定位置的值為 1 或 0。

將二進位制的第 n 位設定為 1【x | (1<

ElemType x = 10;

ElemType n = 3 - 1;

binary_calculate_format(x, 1<

/*

程式輸出:

00001010 (10)

| 00000100 (4)

————

00001110 (14)

*/

這個技巧和上面的

判斷二進位制的第 n 位為 1 或 0

大體相同,只不過一個使用了與運算,一個使用了或運算,各自根據自己的需求,使用對應的位運算子。

在執行 1<

將二進位制的第 n 位設定為 0【x & ~(1<

ElemType x = 7;

ElemType n = 3 - 1;

binary_calculate_format(x, ~(1<

/*

程式輸出:

00000111 (7)

| 11111011 (-5)

————

00000011 (3)

*/

這個技巧和上面的

將二進位制的第 n 位設定為 1

大體相同,只不過一個使用了與運算,一個使用了或運算,各自根據自己的需求,使用對應的位運算子。

在執行 ~(1<

其實上面幾個技巧在應用層面上的開發是不怎麼用到的,之所以寫出來是體驗一下位操作的藝術。接下來的幾個就是實實在在在平時的開發中會用到的技巧。

使用一個變數儲存多個標誌位【flag1 | flag2 | flag3 …】

ElemType flag = 0;

ElemType flag1 = 1; // 0000 0001

ElemType flag2 = 2; // 0000 0010

ElemType flag3 = 4; // 0000 0100

// 設定操作

flag |= flag1;

char str[sizeof(ElemType) * 8 + 1];

number2binary(flag, str);

printf(“%s\n”, str); // 0000 0001

flag |= flag2;

number2binary(flag, str);

printf(“%s\n”, str); // 0000 0011

flag |= flag3;

number2binary(flag, str);

printf(“%s\n”, str); // 0000 0111

// 判斷操作

if (flag & flag1)

printf(“存在 flag1。\n”);

else

printf(“不存在 flag1。\n”);

// 取出操作

flag ^= flag1;

if (flag & flag1)

printf(“存在 flag1。\n”);

else

printf(“不存在 flag1。\n”);

/*

程式輸出:

00000001

00000011

00000111

存在 flag1。

不存在 flag1。

*/

是不是很熟悉啊?例如 c 語言以只讀且檔案不存在時建立檔案的方式開啟檔案:open(“demo。txt”, O_RDONLY|O_CREAT);。

其設定操作的原理就是使用或運算將二進位制不同位設為 1。

其判斷操作的原理就是使用與運算判斷指定位是否為 1。

其取出操作的原理就是使用異或運算將 flag1 取出,即使用了異或的性質:a ^ 0 = a,a ^ a = 0。

交換兩個數的值

ElemType a = 10;

ElemType b = 20;

printf(“交換前: a = %d, b = %d\n”, a, b);

printf(“執行 a = a ^ b:\n”);

binary_calculate_format(a, b, a^b, ‘^’);

a = a ^ b; // a ^ b = ab

printf(“執行 a = a ^ b:\n”);

binary_calculate_format(a, b, a^b, ‘^’);

b = a ^ b; // ab ^ b = a

printf(“執行 a = a ^ b:\n”);

binary_calculate_format(a, b, a^b, ‘^’);

a = a ^ b; // ab ^ a = b

printf(“交換後: a = %d, b = %d\n”, a, b);

/*

程式輸出:

交換前: a = 10, b = 20

執行 a = a ^ b:

00001010 (10)

^ 00010100 (20)

————

00011110 (30)

執行 a = a ^ b:

00011110 (30)

^ 00010100 (20)

————

00001010 (10)

執行 a = a ^ b:

00011110 (30)

^ 00001010 (10)

————

00010100 (20)

交換後: a = 20, b = 10

*/

其實這個技巧的核心原理就是使用了異或的性質:a ^ 0 = a,a ^ a = 0。具體過程上面程式碼已有註釋。

找出不重複的數

char arr[] = {1, 1, 2, 2, 3, 4, 4};

int i;

char ans = 0;

for (i = 0; i < (sizeof(arr)/sizeof(char)); i++)

ans ^= arr[i];

printf(“ans = %d\n”, ans);

/*

程式輸出:

ans = 3

*/

其實這個技巧的核心原理也是使用了異或的性質:a ^ 0 = a,a ^ a = 0。由於 arr 中的重複元素最多隻能重複一次,所以就可以根據異或的性質將重複的元素排除出來,最後就只剩沒有重複的元素了。

leetcode 上對應有這麼一道題目:只出現一次的數字

資料合併

ElemType a = 7;

ElemType b = 5;

char str[sizeof(ElemType) * 8 + 1];

char str1[sizeof(ElemType) * 8 + 1];

// 存放操作

ElemType c = a << 4; // 將 a 存放到高 4 位

number2binary(c, str);

number2binary(a, str1);

printf(“a = %d(%s), (a<<4) = %d(%s)\n”, a, str1, c, str);

c |= b; // 將 b 存放在低四位

printf(“********************\n”);

printf(“c |= b : \n”);

binary_calculate_format(c, b, c|b, ‘|’);

printf(“********************\n”);

// 取出操作

a = c >> 4; // 取出 a

number2binary(a, str);

number2binary(c, str1);

printf(“c = %d(%s), (c>>4) = %d(%s)\n”, c, str1, a, str);

b = c & 0xF; // 0xF => 0000 1111

printf(“********************\n”);

printf(“b = c & 0xF : \n”);

binary_calculate_format(c, 0xF, c & 0xF, ‘&’);

printf(“********************\n”);

/*

程式輸出:

a = 7(00000111), (a<<4) = 112(01110000)

********************

c |= b :

01110101 (117)

| 00000101 (5)

————

01110101 (117)

********************

c = 117(01110101), (c>>4) = 7(00000111)

********************

b = c & 0xF :

01110101 (117)

& 00001111 (15)

————

00000101 (5)

********************

*/

這個技巧的原理在於,使用一個指定位數的二進位制數,然後將該二進位制數指定的範圍用於存放指定的資料。例如上面的例子中,使用了一個 char 型別(1 位元組長度,即 8 位)來存放兩個資料,且每個資料佔用 4 位,分別存放於高四位和低四位。

就這個技巧在我平時的工作中常用於兩個方面:

用於製作一個組合鍵來當用 map 的 key 來使用。(下面程式碼是用 c++ 語言寫的,所以要使用對應的編譯器編譯)

#include // 用於匯入 int16_t 型別

#include // 用於匯入 printf 函式,方便格式化輸出

#include

#include

usingnamespacestd;

#define MAKE_KEY(gid, uid) ((uid) | (gid) << 8)

/* 用於根據型別生成對應位數的二進位制,例如 int16_t 為 2 位元組,即 16 位,因此會顯示 16 位二進位制*/

typedefint16_t ElemType;

// 數字轉二進位制格式

voidnumber2binary(ElemType number, string& str, int size = 0)

{

int count = sizeof(ElemType) * 8;

if (size > 0)

count = size;

int i;

for (i = count - 1; i >= 0; i——)

{

if (!size && i == 7)

str = ‘ ’ + str;

str = ((number & 1) ? ‘1’ : ‘0’) + str;

number = number >> 1;

}

}

intmain()

{

map m;

// 使用 gid(100) 和 uid(50) 生成一個組合鍵

int8_t gid = 100;

int8_t uid = 50;

int16_t key = MAKE_KEY(gid, uid);

// 將 key 生成的二進位制作為 value

string value;

number2binary(key, value);

// 存入 map 中

m[key] = value;

// 讀取 map

string strGID;

string strUID;

number2binary(gid, strGID, sizeof(gid)*8);

number2binary(uid, strUID, sizeof(uid)*8);

printf(“gid: %d(%s), uid: %d(%s)\n”, gid, strGID。c_str(), uid, strUID。c_str());

printf(“value: %d(%s)\n”, key, m[key]。c_str());

// 從 key 中讀取 gid

gid = key >> 8;

printf(“gid = %d\n”, gid);

// 從 key 中讀取 uid

uid = key & 0xFF; // 0xFF = 1111 1111

printf(“uid = %d\n”, uid);

return0;

}

/*

程式輸出:

gid: 100(01100100), uid: 50(00110010)

value: 25650(01100100 00110010)

gid = 100

uid = 50

*/

可以看到,這樣就可以使用位操作來完成了一個組合鍵了,是不是很方便。如果不使用組合鍵,那麼你就要進行 map 巢狀,又浪費記憶體,程式碼看起來還很凌亂。

用於組裝傳輸協議(下面程式碼是用 c 語言寫的,所以要使用對應的編譯器編譯)

#include

#include

intmain()

{

// 協議:訊息頭部(2位元組訊息型別 + 2位元組訊息體長度)+ 訊息體(最大長度為 65535,即 2 位元組的長度)

char msg_body[] = “I‘m Liction!!!”; // 訊息體

int16_t msg_type = 11; // 訊息型別

int16_t msg_size = sizeof(msg_body); // 訊息體長度

// 組裝協議

// 宣告訊息包,長度為:訊息型別長度 + 訊息體長度的長度 + 訊息體長度

char msg_pack[sizeof(msg_type) + sizeof(msg_size) + sizeof(msg_body)];

// 存入訊息型別,由於 msg_pack 為 char 陣列,且 char 為 1 位元組,而 msg_type 為 2 位元組,所以需要兩個元素來分別存放

msg_pack[0] = msg_type; // 先存放 msg_type 的低 8 位,即先存放 1 位元組資料

msg_pack[1] = msg_type << 8; // 再存放 msg_type 的高 8 位,即再存放 1 位元組資料

// 存入訊息體長度,由於 msg_pack 為 char 陣列,且 char 為 1 位元組,而 msg_type 為 2 位元組,所以需要兩個元素來分別存放

msg_pack[2] = msg_size; // 先存放 msg_size 的低 8 位,即先存放 1 位元組資料

msg_pack[3] = msg_size << 8; // 再存放 msg_type 的高 8 位,即再存放 1 位元組資料

// 存放訊息體資料,由於 msg_body 正好是 char 陣列,所以直接一一放入 msg_pack

int i;

for (i = 0; i < sizeof(msg_body); i++)

msg_pack[i+4] = msg_body[i];

// 無用的操作,遍歷輸出 msg_pack

for (i = 0; i < sizeof(msg_pack); i++)

{

// 前面 4 個位元組讓它輸出數字

if (i < 4)

printf(“%d ”, msg_pack[i]);

else

printf(“%c ”, msg_pack[i]);

}

printf(“\n”);

// 模擬網路傳輸

printf(“傳送資料的長度:%d\n”, sizeof(msg_pack));

printf(“接收到資料的長度:%d\n”, sizeof(msg_pack));

// 讀取訊息型別

msg_type = msg_pack[0] | msg_pack[1] << 8;

printf(“msg_type: %d\n”, msg_type);

// 讀取訊息體長度

msg_size = msg_pack[2] | msg_pack[3] << 8;

printf(“msg_size: %d\n”, msg_size);

// 讀取訊息體

// (由於是網路傳輸過來的資料,我們是不知道訊息體由多長的,因此應該使用透過解包得到的訊息體長度來構建訊息體內容)

char new_msg_body[msg_size];

for (i = 0; i < msg_size; i++)

new_msg_body[i] = msg_pack[i+4];

printf(“new_msg_body: %s\n”, new_msg_body);

return0;

}

/*

程式輸出:

11 0 15 0 I ’ m L i c t i o n ! ! !

傳送資料的長度:19

接收到資料的長度:19

msg_type: 11

msg_size: 15

new_msg_body: I‘m Liction!!!

*/

可以看到,這樣便能很好的使用位操作來封裝協議包了。具體的過程已有註釋,就不多說了。

總結

好了,差不多就到這裡了,以後碰到不懂的再繼續寫總結筆記。

Beauty is found everywhere。 Our eyes do not show a lack of sense of beauty, but a lack of observation。(你英語很厲害?)

參考資料

https://codeforwin。org/2018/05/10-cool-bitwise-operator-hacks-and-tricks。html

https://www。jianshu。com/p/ffc97c4d2306

http://www。cnblogs。com/ysocean/p/7826374。html#_label0