C可以对变量的个别位进行操作,这在流行的其他高级语言中不太常见了,尤其是脚本语言很少有这样的能力了。位操作最常见的情景是设备控制的驱动程序,每一位都有特定的含义;数据的压缩与解压缩,数据加密解密等等。
基础:二进制数、位、字节
这部分是计算机理论基础,只提一个有意思的点:一个字节通常包括8个位,不过C使用术语字节(byte)表示用于存放系统字符集的空间大小,所以一个C字节可能为8,9,16位或者其他的值。然而描述存储器芯片和数据传输率时使用的字节就是指8位字节。
关于字符编码的原码,反码,补码的内容,不再赘述。
位运算符
C提供位的逻辑运算符和移位运算符。
位逻辑运算符
4个位运算符用于整型数据,位逻辑运算符与常规的逻辑运算符(&&, ||, !)不一样,使用时不要把它们混淆了。常规的逻辑运算符是对整个操作数进行的,而位逻辑运算是针对数的某一位操作,而不影响其他位。
- 取二进制反码,或者按位取反:
~
将一个数的二进制表示形式按位取反。注意,~是一个操作符,~val
并不改变val的值,就像3*val
不会改变val保存的值,该操作会返回一个操作的结果,用于赋值语句的右边。 - 位与
&
两个操作数按位与产生一个新值。两个操作数对应位都为1时,结果的那一位也为1,否则为0. - 位或
|
和 &很像 - 按位异或
^
对每一个位,如果操作数中的对应位有且只有一个为1,结果对应位也为1,都则结果对应位为0
这四个操作符和 数值运算(+, -, *, /)一样可以与赋值符号(=)结合成一个赋值运算符(&=, |=, ^=)。其中取反常写为 val = ~val;
。
掩码、打开位
掩码常用在如子网掩码的名称中,掩码是某些位设为1而某些位设为0的位组合。使用掩码与&
操作符的目的是将特定位之外的所有位都设置为0.
与使用掩码相对的是打开某个为,使用 |
操作符与掩码将某些位保持不变而使某些位都设置位1。
其他还有关闭位( f &= ~mask),转置位(f ^= mask)都是很常用的。
查看某一位的值
用 & 实现查看变量某一个位的值.
int i = 123; |
这种方法中,为避免信息漏过边界,位掩码至少应该与其所屏蔽的值具有相同的宽度。
移位运算
移位运算包括左移和右移两种。
左移运算 <<
将左侧操作数的值的每一位都向左移动,移动的位数由右侧操作数指定。空出的位用 0 填充,并且丢弃移出做操作数末端的未。移位操作返回一个新值而不改变其操作数。如 i << 2
返回i左移2位的结果,i的值不变。
右移运算 >>
将做操作数的每位向右移动。丢弃移出左侧操作数右端的位。对于无符号类型(unsigned),使用0填充左端空出的位,对于有符号数,结果依赖于机器,可能用0填充,可能用符号位填充(常用符号位填充)。
移位运算是高校的对2的幂的乘法和除法,在编译器的优化中常用移位运算和加法运算组合来优化乘法运算,比如 i*33
可以被优化为 i << 5 + i
这样子。这些优化取决于编译器实现,对程序员是透明的。
移位运算对于按位存储数据的变量很有用有用,比如颜色常保存为 0xff00ff00
这样子,使用移位与 &
操作符可以方便的取得 rgba 对应的数值。
位字段
位字段在之前的一片介绍链接的部分,ELF的结构体中使用过。这种技术在网络协议中也经常会使用。
位字段是一个singed int 或者 unsigned int 中一组相邻的位。位字段由一个结构声明建立,该结构声明为每一个字段提供标签,并决定字段的宽度。
struct { |
该定义使prnt包含4个1位的字段,可以像使用普通结构成员运算符一样将值赋给单独的字段。变量prnt被存储在一个int大小的存储单元中,且只有4位被使用。
带有位字段的结构允许在单个单元中存储多项设置。但是要确保设置的值没有超出字段的容量。
如果声明的结构中总的字段数超过了一个unsigned int 大小,那么将会使用下一个unsigned int存储位置。不允许一个字段跨越两个存储位置的边界,如果有这种情况编译器将自动地移位字段定义,使字段按照unsigned int边界对齐。这样会在前一个存储位置留下一个未命名的洞。
可以使用未命名字段填充未命名的洞。使用一个宽度为0的未命名字段迫使下一个字段与下一个存储位置对齐。也就是说位字段结构成员的大小没有什么限制,只要注意边界对齐就可以了。
struct { |
一个可能的问题是字段在int中的顺序依赖于机器实现,有的机器上从左到右存储,在另一些机器则是从右向左,所以位字段往往难以移植,典型地,把他们用于不可移植的用途,例如按照某个特定硬件设备所使用的确切格式来存放数据。
位字段和位运算符是对于同类编程问题的两种可选择的方法,可以使用其中任何一种,通常来说位字段的方法比较好理解,使用也更加方便。
完?