C: 位运算
位运算就像控制一排灯的开关——每盏灯对应一个比特,你可以单独开、关、翻转某一盏,而不影响其他灯。
1. 二进制回顾
计算机内部用二进制表示所有数据。一个字节(byte)= 8 个比特(bit),每个比特是 0 或 1。
十进制 42 = 二进制 00101010
十进制 255 = 二进制 11111111
有符号整数用补码表示:最高位是符号位,0 为正,1 为负。
十进制 5 = 00000101
十进制 -5 = 11111011 (补码)
2. 六种位运算符
| 运算符 | 名称 | 规则 |
|---|---|---|
& |
按位与 | 两位都为1结果才为1 |
| |
按位或 | 有一位为1结果就为1 |
^ |
按位异或 | 两位不同结果为1 |
~ |
按位取反 | 0变1,1变0 |
<< |
左移 | 各位左移,低位补0 |
>> |
右移 | 各位右移,高位补符号位(有符号)或0(无符号) |
(1) 按位与 (&)
1010 (10)
& 1100 (12)
------
1000 (8)
用途:清除某些位、检查某些位是否为1。
(2) 按位或 (|)
1010 (10)
| 1100 (12)
------
1110 (14)
用途:设置某些位为1。
(3) 按位异或 (^)
1010 (10)
^ 1100 (12)
------
0110 (6)
特性:a ^ a = 0,a ^ 0 = a。异或是可逆的,用同一个值异或两次就还原了。
(4) 按位取反 (~)
~ 00101010 (42)
= 11010101 (-43, 补码)
对无符号数:~0 = 255(8位),~0 = 0xFFFFFFFF(32位)。
(5) 左移 (<<)
5 << 2
= 00000101 << 2
= 00010100
= 20
左移 n 位等于乘以 2^n。5 << 2 = 5 * 4 = 20。
(6) 右移 (>>)
20 >> 2
= 00010100 >> 2
= 00000101
= 5
右移 n 位等于除以 2^n(向下取整)。
3. 位操作四大操作
(1) 设置位(置1)
flags |= (1 << n);
把第 n 位设为 1,其他位不变。
(2) 清除位(置0)
flags &= ~(1 << n);
~(1 << n) 产生一个除第 n 位为 0 外全为 1 的掩码,与运算后第 n 位清零。
(3) 翻转位
flags ^= (1 << n);
异或运算:0^1=1,1^1=0,正好实现翻转。
(4) 检查位
if (flags & (1 << n)) {
}
如果第 n 位是 1,结果非零;是 0 则结果为零。
▶ 示例
#include <stdio.h>
void print_bits(unsigned char val) {
for (int i = 7; i >= 0; i--) {
printf("%d", (val >> i) & 1);
}
printf("\n");
}
int main(void) {
unsigned char flags = 0;
flags |= (1 << 3);
printf("设置第3位: ");
print_bits(flags);
flags |= (1 << 5);
printf("设置第5位: ");
print_bits(flags);
flags &= ~(1 << 3);
printf("清除第3位: ");
print_bits(flags);
flags ^= (1 << 5);
printf("翻转第5位: ");
print_bits(flags);
flags ^= (1 << 7);
printf("翻转第7位: ");
print_bits(flags);
if (flags & (1 << 7)) {
printf("第7位是1\n");
}
return 0;
}
设置第3位: 00001000
设置第5位: 00101000
清除第3位: 00100000
翻转第5位: 00000000
翻转第7位: 10000000
第7位是1
4. 掩码技术
掩码(mask)是一组预定义的位组合,用来提取或修改数据中的特定位域。
(1) 提取低位
unsigned int val = 0xABCD;
unsigned int low_byte = val & 0xFF;
0xFF 是掩码,只保留最低 8 位。
(2) 提取高位
unsigned int high_byte = (val >> 8) & 0xFF;
先右移 8 位,再用掩码取低 8 位。
(3) 组合值
unsigned int combined = (high << 8) | low;
把两个字节拼成一个 16 位值。
▶ 示例
RGB 颜色值提取。一个 24 位颜色值,红绿蓝各占 8 位:
#include <stdio.h>
int main(void) {
unsigned int color = 0xFF6633;
unsigned char r = (color >> 16) & 0xFF;
unsigned char g = (color >> 8) & 0xFF;
unsigned char b = color & 0xFF;
printf("颜色 #FF6633:\n");
printf(" 红: %d\n", r);
printf(" 绿: %d\n", g);
printf(" 蓝: %d\n", b);
unsigned int new_color = 0x00;
new_color |= ((r / 2) << 16);
new_color |= ((g / 2) << 8);
new_color |= (b / 2);
printf("变暗后: #%06X\n", new_color);
return 0;
}
颜色 #FF6633:
红: 255
绿: 102
蓝: 51
变暗后: #7F3319
5. 权限标志位
Linux 文件权限是位运算的典型应用。9 位权限分别表示 owner/group/other 的 read/write/execute:
rwxr-xr-x = 111101101 = 0755
rw-r--r-- = 110100100 = 0644
#include <stdio.h>
#define READ (1 << 2)
#define WRITE (1 << 1)
#define EXECUTE (1 << 0)
void show_permission(unsigned char perm) {
printf("%c", (perm & READ) ? 'r' : '-');
printf("%c", (perm & WRITE) ? 'w' : '-');
printf("%c", (perm & EXECUTE) ? 'x' : '-');
}
int main(void) {
unsigned char owner = READ | WRITE | EXECUTE;
unsigned char group = READ | EXECUTE;
unsigned char other = READ | EXECUTE;
printf("权限: ");
show_permission(owner);
show_permission(group);
show_permission(other);
printf("\n");
owner &= ~WRITE;
printf("去掉写权限后: ");
show_permission(owner);
show_permission(group);
show_permission(other);
printf("\n");
return 0;
}
权限: rwxr-xr-x
去掉写权限后: r-xr-xr-x
权限: rwxr-xr-x
去掉写权限后: r-xr-xr-x
6. 位运算实用技巧
(1) 交换两个变量(不用临时变量)
a ^= b;
b ^= a;
a ^= b;
原理:异或的自反性。但可读性差,实际开发中不推荐使用。
(2) 判断奇偶
if (n & 1) {
}
最低位为 1 就是奇数。比 n % 2 更快。
(3) 乘除 2 的幂
n << 1
n << 2
n >> 1
编译器通常会把 n * 2 优化成移位,但移位只对正整数安全。
(4) 计算 2 的幂次
unsigned int pow2 = 1u << n;
1u << 0 = 1,1u << 1 = 2,1u << 8 = 256……用移位比 pow(2, n) 快得多。
(5) 对齐到 2 的幂
unsigned int aligned = (value + mask) & ~mask;
比如对齐到 4 字节边界:(n + 3) & ~3。
#include <stdio.h>
int main(void) {
int values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int i = 0; i < 10; i++) {
int aligned = (values[i] + 3) & ~3;
printf("%d -> %d\n", values[i], aligned);
}
return 0;
}
0 -> 0
1 -> 4
2 -> 4
3 -> 4
4 -> 4
5 -> 8
6 -> 8
7 -> 8
8 -> 8
9 -> 12
❓ 常见问题
memcpy 把浮点数的字节复制到整数中。📖 小节
- 六种位运算符:
&、|、^、~、<<、>> - 设置位用
|=,清除位用&= ~,翻转位用^=,检查位用& - 掩码是位运算的核心技术,用于提取和组合位域
- 权限标志位用或运算组合,与运算检查,取反清除
- 左移乘 2,右移除 2,对齐用
(n + mask) & ~mask
📝 作业
- 编写函数,返回一个整数的二进制中 1 的个数(用位运算实现,不用循环除2)
- 编写程序,用位运算实现 RGB 颜色的亮度调整(每个通道乘以一个系数后重新组合)
- 编写函数,将一个 32 位整数的第 m 位到第 n 位提取出来(m < n,从0开始计数)