回顧之前的篇幅,C語言的主體部分基本已經介紹完了。之所以沒有介紹C++的相關特性是因為在之前的文章中C和C++在這些方面都有共性,所以在面向對象之前。我們先把這些共性給介紹完。也就是說在介紹面向對象之前,所有的文章都是CC++中都能使用的。從這點上來看,現在正極力奮斗於C++戰線上的初學者還是很有用處的。
本篇繼續沿著這條路線,到本篇為止包括本篇都還不會急於去介紹C++的面向對象的特性。那麼在之前的文章中,可以說基本都把內容給介紹完了。本篇雖然不是大概念,但是在實際的項目中是絕對離不開的。那麼我們就在本篇開始我們的位運算旅程。
首先,位運算到底用來做什麼,用處多不,好像到現在我也沒有怎麼用位運算呢? 很多初學者我相信會有這樣的疑問。那麼本篇就將介紹位運算的強大用途及無限魅力。
位運算跟二進制聯系非常緊密,二進制這個概念相信大家都不陌生,我們的位運算也就是在這些0或1上進行操作。不要說二進制你都不知道。比如:
7的8位二進制為: 0000 0111
7的32位二進制為: 0000 0000 0000 0000 0000 0000 0000 0111
二進制與十進制的換算我就不說了。上面為什麼三個1就表示7,不知道的話就看看書哈。
上面說到了8位和32位,我們知道一個字節(byte)表示8位,那麼二進制的一位就是這個位的意思。int是32位,那麼寫完整數字0的二進制就有32個0。這樣思考起來在後面的位運算上要好理解一點。
先來看看我們經常用到的位運算符:& (按位與)、| (按位或)、^ (按位異或)、~ (按位取反)、>> (按位右移)、<< (按位左移)。
& ( 按位與): 概念上來講就是二進制上按每一位(0或1)進行與運算。 那麼與運算是什麼意思該不用我說吧,就是兩者都是1結果為真。其中一個為0結果為假。這裡不可能有0、1之外的數,這裡是二進制。先看一個8位二進制的例子:
7 & 8 = 0000 0 111 & 0000 1000 = 0000 0000 = 0
7 & 3 = 0000 0111 & 0000 0011 = 0000 0011 = 3
很簡單吧。不用多說了,就是操作0和1。
| ( 按位或): 概念上來講就是二進制上按每一位(0或1)進行或運算。 那麼或運算是什麼意思該不用我說吧,就是兩者都是0結果為假。其它情況都為真。
7 | 8 = 0000 0 111 | 0000 1000 = 0000 1111 = 15
7 | 3 = 0000 0111 | 0000 0011 = 0000 0111 = 7
^( 按位異或): 概念上來講就是二進制上按每一位(0或1)進行異或運算。 異或運算簡單講就是相同就為假,不同為真。
7 ^ 3 = 0000 0111 ^ 0000 0011 = 0000 0100 = 4
~( 按位取反): 概念上來講就是二進制上按每一位(0或1)進行取反運算。 取反運算簡單講就是0變1,1變0。
~7 = ~0000 0111 = 1111 1 000 = 0xf8 = 248 (無符號)
>>( 按位右移): 概念上來講就是二進制上按每一位(0或1)進行右移運算。 右移運算簡單講就是將二進制的位整體向右移動。
7 >> 2 = 0000 0111 >> 2 = 0000 0001 = 1 // 這裡向右移動了2位,最低位的兩個1被抹去。
這裡右移兩位等於除了2的2次方,7/4 = 1 在整數除法中則看成是被捨掉了小數部分。
<<( 按位左移): 這個就不說了,與上面右移方向的相反。
好了,有了基本的概念。那麼下面就進入實際應用了。
我們都知道顏色,比如你再惹我。我就給你顏色看看。那麼這裡的顏色就是RGB,我們在這裡談24位顏色。也就是RGB中的R(紅)、G(綠)、B(藍)分別占8位。這下有的朋友疑惑了,24位?想想前面的基本數據類型裡,沒有24位的類型啊,怎麼辦呢?
於是,我們便用到了位運算。一個32位的無符號整數,高8位置零。低24位用於表示顏色,到這裡又有朋友想了。低24位怎麼表示?我們都知道顏色通常每個分量是0~255之間,三種顏色存放在24位裡怎麼存?
typedef unsigned char BYTE;
typedef unsigned int UINT;
BYTE r = 255;
BYTE g = 255;
BYTE b = 255;
我們將三個分量都定成是255,這裡的目的是想表示白色。
UINT color = ( r << 16 ) | ( g << 8 ) | b;
然後這樣就組成了我們的顏色:白色。
那麼這裡的原理很簡單:
0000 0000 1111 1111 1111 1111 1111 1111
這裡的顏色分量我都標識了字體的顏色,看紅色的部分是不是就是左移了16位,其他同理,具體的過程就是:
r << 16
0000 0000 1111 1111 0000 0000 0000 0000
g << 8
0000 0000 0000 0000 1111 1111 0000 0000
b
0000 0000 0000 0000 0000 0000 1111 1111
然後看這3個二進制數按位或運算後就是我們的目標顏色,用十六進制看就是:0x00ffff ff 。0xff就是255。
32位的顏色只是比24位顏色多了一個分量,可以用來做透明。也就是我們上面沒有用到的最高8位。32位也可以將高8位的分量放在低8位,RGB放在高24位。比如:
1111 1111 1111 1111 1111 1111 1111 1111
現在我們知道了color,那麼要取得分一個分量怎麼辦呢?很簡單:
BYTE r = ( color >> 16 ) & 0xff;
BYTE g = ( color >> 8 ) & 0xff;
上面三句相當於逆運算。那麼這裡按位與上一個0xff的原理是什麼呢? 我們看g分量:
color >> 8
0000 0000 0000 0000 1111 1111 1111 1111
0xff
0000 0000 0000 0000 0000 0000 1111 1111
兩者相與,是不是就將紅色分量給去掉了呢?
0000 0000 0000 0000 0000 0000 1111 1111
就只剩下綠色的8個1了。這裡我只是舉的255,因此可能有的朋友會說我直接:
BYTE g = ( color >> 16 ) & 0xff; 這樣也等於255啊。這裡我是舉的一個比較特殊的例子,當這裡r g b不相等的時候,就不能這樣用了,這裡是通用的用法,我們不能特殊化。
再來看16位色的RGB565, 字面上的意思很簡單就是r和g占5位, b占6位。一共是16位。如果是16位我們就不需要一個UINT了,只需要:
typedef unsigned short UINT16;
BYTE r = 255;
BYTE g = 255;
BYTE b = 255;
UINT16 color16 = ( ( r & 0xf8 ) << 8 ) | ( ( g & 0xfc ) << 3 ) | ( ( b & 0xf8 ) >> 3 );
天啊,有的朋友可能看到這一串就暈了,其實我們碰到這種問題,如果對十六進制數不敏感不熟悉的話你就用WINDOWS自帶的計算器進行算嘛。我們還是一步一步來說明吧。
因為是“565”模式的顏色,那麼r要拋棄掉低3位,只需要高5位。g需要拋棄掉低2位,只要6位,b和r相同,也拋棄低3位。一共加起來就是16位了。那麼要把這16位分別保存這3個分量。同樣是按位或運算。r只剩下高5位,要到UINT16的最高5位,所以需要左移8位。
0000 0000 1111 1000 // 很明顯需要向左移動8位
同樣b分量被拋棄掉低2位後:
1111 1 000 1111 1100 // 很明顯需要向左移動3位
而b分量:
1111 1 111 1 11 1 1111 000 // 很明顯多出兩個0需要向右移動3位
上面的拋棄掉低位的算法不用說了吧,不熟悉的就用計算器算相與後是不是想要的結果。正因為有拋棄,因此16位顏色就沒有24位顏色真實。
問題一: 為什麼要拋棄低位,不拋棄高位?(比如紅色就可以是:r & 0x1f)
上面24位色反過來逆運算獲得每一個分量我們已經知道了,那麼:
問題二: 怎麼獲得RGB565顏色color16中的每一個分量。
上面的顏色了解後,我相信大家對於& | << >>這幾個該沒有什麼問題了吧,當然顏色的組合還有其他的,這裡不是為了介紹顏色。而是為了了解位運算。
位運算很靈活,這裡只是一個基本的介紹。更多的還需要大家多實踐。
了解了上面的幾個運算符,下面介紹剩下的兩個:按位取反和按位異或。
在實際的工作中,通常會有一些狀態需要表示。我們這些狀態又想節約一點空間。於是我們選擇了用一個32位的無符號整數來存放這些狀態。比如:
在游戲裡面,某個玩家的一些狀態也就是我們經常說的BUFF,比如:持續加血,持續加藍,持續加體力,經脈受傷,被點穴等等。於是我們就有一個枚舉:
enum EPLAYER_STATE
{
EPST_NONE = 0x00000000, // 沒有狀態
EPST_ADDHP = 0x00000001 , // 加血
EPST_ADDMP = 0x00000002, // 加藍
EPST_ADDSP = 0x00000004, // 加體力
EPST_JMDAM = 0x00000008, // 經脈受傷
EPST_DIANX = 0x00000010, // 被點穴
EPST_XUANY = 0x00000020, // 被眩暈
EPST_ATTCK = 0x00000040, // 被攻擊
// .