c++位操作
看到有些人对位运算还存在问题,于是决定写这篇文章作个简要说明。
! |% d+ V9 {! H
什么是位(bit)?
很简单,位(bit)就是单个的0或1,位是我们在计算机上所作一切的基础。计算机上的所有数据都是用位来存储的。一个字节(BYTE)由八个位组成,一个字(WORD)是二个字节或十六位,一个双字(DWORD)是二个字(WORDS)或三十二位。如下所示: 8 {" I6 o6 }9 i' c* r# x$ `) P
, s$ z% e7 R% w! N# g. X
0 1 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0
1 1 1 0 1 0 0 0 1 1 1 1 0 0 0 # ~6 j+ ^( w7 w. ~
| | | |
| | |
| +- bit
31 | |
| bit 0 -+ | / j7 @5 ^* O0 i. L5 J1 b" ?6 b
| | |
| |
+-- BYTE 3 ---- -+---- BYTE 2 ---+---- BYTE 1 ---+--- BYTE 0 -----+
|
|
| / K/ m, y- K4 \4 f5 @# g% c" O
+------------ WORD 1 ------------+----------- WORD 0 -------------+
|
|
+----------------------------- DWORD -----------------------------+ # [. x5 Y ^3 u5 |
使用位运算的好处是可以将BYTE, WORD 或 DWORD 作为小数组或结构使用。通过位运算可以检查位的值或赋值,也可以对整组的位进行运算。
16进制数及其与位的关系
用0或1表示的数值就是二进制数,很难理解。因此用到16进制数。 + H+ D8 ]% i5 v6 O$ b+ w) l
16进制数用4个位表示0 - 15的值,4个位组成一个16进制数。也把4位成为半字节(nibble)。一个BYTE有二个nibble,因此可以用二个16进制数表示一个BYTE。如下所示: 1 q! ^" o, X H4 Z) _$ E
NIBBLE HEX VALUE
====== =========
0000 0 . [# H' Q; a$ I6 L' [& ?
0001 1
0010 2 / v* Z' K5 O& ^
0011 3
0100 4 % U- O; k1 L% M2 n
0101 5
0110 6 : r* m& ~" V2 B; K7 Z! j' n6 n
0111 7 ! W1 j" S: i9 T* r
1000 8
1001 9 5 H: h$ q% o p$ E; B/ {, E
1010 A
1011 B
1100 C 2 f3 ?! u+ K/ i0 K
1101 D * x& C w# E4 o* V
1110 E % W1 K1 D' J7 Z* z: u8 r; n
1111 F
如果用一个字节存放字母"r"(ASCII码114),结果是:
0111 0010 二进制
7 2 16进制
可以表达为:'0x72' ) k% [. x. Z$ M& p# b) o
有6种位运算: 7 P" @3 D5 Q' ]
& 与运算 5 J5 W: i! o% L/ ]
| 或运算
^ 异或运算 ! {$ V2 U b; h. Y- ?8 p" e
~ 非运算(求补)
>> 右移运算
<< 左移运算
- r, S _! T) _1 Y/ ^2 P
与运算(&) ( P" G& E5 ~/ j# U* s' L
双目运算。二个位都置位(等于1)时,结果等于1,其它的结果都等于0。 * W+ U/ q9 W: h; |: v1 ]9 V
1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0 + ~! | {1 u* x( n/ N/ O
8 e% n& Q0 }; ?! t" g f i8 C
与运算的一个用途是检查指定位是否置位(等于1)。例如一个BYTE里有标识位,要检查第4位是否置位,代码如下: 4 z- j ^8 y' @( f: G
BYTE b = 50; , v# z. m8 l# }4 s6 l3 e# S
if ( b & 0x10 ) " x; n, c4 Z. u: J- }
cout << "Bit four is set" << endl;
else
cout << "Bit four is clear" << endl;
上述代码可表示为:
+ R; D2 m ~5 m q! M
00110010 - b
& 00010000 - & 0x10 9 ^" p0 k7 N& t* [
---------------------------- 9 k5 K$ k$ m" s, K9 k
00010000 - result , ^+ D* p7 v/ f2 q3 h+ l
( n# ]+ t1 a! x0 Q5 j) g
可以看到第4位是置位了。
或运算( | )
双目运算。二个位只要有一个位置位,结果就等于1。二个位都为0时,结果为0。
1 | 1 == 1 4 u9 }1 j# P( r
1 | 0 == 1 9 J4 g. H3 X8 r$ z" h, B5 K
0 | 1 == 1 2 A/ K+ R9 B0 W0 D* o& g
0 | 0 == 0 ( \# j# L* V6 R8 M7 A+ C
8 l6 q; g7 T5 u7 {+ P, @9 a+ N
与运算也可以用来检查置位。例如要检查某个值的第3位是否置位: * E& U6 M! j; r; l2 Y+ }
BYTE b = 50;
BYTE c = b | 0x04; . }. y) i$ A9 Z' M
cout << "c = " << c << endl; & }% a7 `4 p$ j& F7 g# ~
可表达为: + I. X- A a! i: u1 `7 m3 {
; B } V4 f5 i6 x$ s
00110010 - b ) \6 p0 P$ W5 S( r+ k! Z! h
| 00000100 - | 0x04
---------- ! {0 J3 g) {& R y
00110110 - result
异或运算(^) $ [3 p/ D( F, u# C6 J8 f
双目运算。二个位不相等时,结果为1,否则为0。
1 ^ 1 == 0 $ y5 s( i; d" B
1 ^ 0 == 1 o' J( Z( `, ~9 X' F
0 ^ 1 == 1
0 ^ 0 == 0 - N' a( |; N2 z |9 m2 T
; e9 T5 k$ e6 {. N- u$ r
异或运算可用于位值翻转。例如将第3位与第4位的值翻转:
: L6 X# |% _& n* O0 \% h
BYTE b = 50;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;
可表达为:
% Q* q" X2 M1 }( ~- K$ ~, y
00110010 - b . D* I2 @2 ^6 C E6 ^4 {( w; g" c
^ 00011000 - ^0x18
---------- 0 m9 b+ i; ?* Y: I: j4 j
00101010 - result + u. B5 ]. ~8 ~0 h+ z
00101010 - b 8 r* f. [8 y9 e* A$ V
^ 00011000 - ^0x18 & y0 F! j) R3 N
---------- ) X( n4 W+ t1 r& ~8 W
00110010 - result & N6 R1 ^0 ?8 F
' Q" Q' W, t$ f1 ?- T$ E
非运算(~)
单目运算。位值取反,置0为1,或置1为0。非运算的用途是将指定位清0,其余位置1。非运算与数值大小无关。例如将第1位和第2位清0,其余位置1:
BYTE b = ~0x03;
cout << "b = " << b << endl;
WORD w = ~0x03;
cout << "w = " << w << endl; 0 z5 q0 i, `) f" h0 c C; X8 a2 s
可表达为:
" @$ X- k9 H( x) q u0 w t
00000011 - 0x03 " Q3 G& j) \( t4 H
11111100 - ~0x03 b 1 G. W3 G4 f# i2 X
0000000000000011 - 0x03
1111111111111100 - ~0x03 w 9 A7 D5 I$ Y* N" M1 P5 l3 \, ]
非运算和与运算结合,可以确保将指定为清0。如将第4位清0: : f2 z9 ~/ O( k& }# M- c
BYTE b = 50;
cout << "b = " << b << endl; ; P# T3 w* v$ E# D$ R
BYTE c = b & ~0x10;
cout << "c = " << c << endl;
可表达为:
00110010 - b s, W' X+ S2 B3 ]7 n- k
& 11101111 - ~0x10 . z. r1 Z6 M* Q
----------
00100010 - result + F! [% V/ X N9 n6 i( A$ X7 T
& p1 Y! o: r, @$ ]3 j1 Y
移位运算(>> 与 <<) - y7 h* Z3 A9 c! f* q
将位值向一个方向移动指定的位数。右移 >> 算子从高位向低位移动,左移 << 算子从低位向高位移动。往往用位移来对齐位的排列(如MAKEWPARAM, HIWORD, LOWORD 宏的功能)。
5 G! V) `2 c0 q& G
BYTE b = 12;
cout << "b = " << b << endl; - S: [: l, k( H3 E
BYTE c = b << 2; 3 Z: K0 {0 G$ j/ t9 u6 d7 V* b
cout << "c = " << c << endl; * x- B- o4 A% ^+ P" x
c = b >> 2;
cout << "c = " << c << endl;
可表达为:
00001100 - b
00110000 - b << 2 # ]2 g+ B" k5 _; h
00000011 - b >> 2
译注:以上示例都对,但举例用法未必恰当。请阅文末链接的文章,解释得较为清楚。 / R R, v6 `6 ~" r
4 I2 b, ?3 s; K* d1 C" ]. Z' V. e
位域(Bit Field) - N# f8 M! t. Z- S
位操作中的一件有意义的事是位域。利用位域可以用BYTE, WORD或DWORD来创建最小化的数据结构。例如要保存日期数据,并尽可能减少内存占用,就可以声明这样的结构: 0 b) O$ O0 ^3 _! S& M* g( Z9 X( R
struct date_struct {
BYTE day : 5, // 1 to 31 ' I6 Q' I! l( l; l
month : 4, // 1 to 12 1 k5 C& ~8 |( ^8 K7 V0 E; Q
year : 14; // 0 to 9999 9 \/ [( P2 G5 Q2 V
}date;
! r; V* M8 U4 W5 V& K h$ `
在结构中,日期数据占用最低5位,月份占用4位,年占用14位。这样整个日期数据只需占用23位,即3个字节。忽略第24位。如果用整数来表达各个域,整个结构要占用12个字节。
1 l* D( v9 s( D0 ]4 v) {' r9 B
| 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 | $ `7 I/ Z' G, R8 f$ B
| | | | ; P0 K# K+ u- \" q8 y9 x
+------------- year --------------+ month+-- day --+
现在分别看看在这个结构声明中发生了什么
3 J2 M, W/ ^7 M3 ]2 S' t3 D" E
首先看一下位域结构使用的数据类型。这里用的是BYTE。1个BYTE有8个位,编译器将分配1个BYTE的内存。如果结构内的数据超过8位,编译器
就再分配1个BYTE,直到满足数据要求。如果用WORD或DWORD作结构的数据类型,编译器就分配一个完整的32位内存给结构。
+ f- p; A* `8 ]7 r' R; R/ H) f
其次看一下域声明。变量(day, month, year)名跟随一个冒号,冒号后是变量占用的位数。位域之间用逗号分隔,用分号结束。
使用了位域结构,就可以方便地象处理普通结构数据那样处理成员数据。尽管我们无法得到位域的地址,却可以使用结构地址。例如: 8 t3 m+ Q& C# v2 @" G( L! v5 z
date.day = 12; ' d/ I) V* [5 |4 K/ w- Y8 t' D
dateptr = &date;
dateptr->year = 1852;
浙公网安备 33010602011771号