位运算概述
为什么使用位运算?
- 性能优化:位运算直接操作二进制位,速度极快
- 内存节省:可以用单个整型变量存储多个布尔标志
很多三方轮子的源码底层都会用到大量的位运算。
比如,最经典的用 “>>1”代替“/2”
比如,加密算法、哈希函数等都用到位运算。
这可以提高性能,加减乘除底层其实都是通过位运算实现的。
基本概念
位运算直接对整数的二进制位进行操作,比算术运算更接近计算机底层。
下面对六种位运算符进行详解。
按位与(&)运算
按位与运算对两个数的二进制表示的每一位进行逻辑与操作:
- 规则:两个对应位都为1时,结果才为1;否则为0
-
符号:
& -
特点:任何位与0相
与都会变成0,与1相与保持原值
运算过程图示
#include <stdio.h>
int main() {
unsigned int a = 0b00111; // 十进制 7
unsigned int b = 0b11100; // 十进制 28
printf("按位与运算示例:\n");
printf("a = 7 (二进制: 00111)\n");
printf("b = 28 (二进制: 11100)\n");
printf("\n逐位运算过程:\n");
printf(" 00111\n");
printf("& 11100\n");
printf(" -----\n");
printf(" 00100 (十进制 4)\n");
printf("\n实际计算结果: %d & %d = %d\n", a, b, a & b);
return 0;
}
运行结果:
按位与运算示例:
a = 7 (二进制: 00111)
b = 28 (二进制: 11100)
逐位运算过程:
00111
& 11100
-----
00100 (十进制 4)
实际计算结果: 7 & 28 = 4
实用技巧详解
1. 判断奇偶性(最常用)
#include <stdio.h>
void print_binary_5bit(int number) {
printf("%d的二进制表示: ", number);
for (int i = 4; i >= 0; i--) {
printf("%d", (number >> i) & 1);
}
printf("\n");
}
void check_odd_even(int number) {
printf("\n判断 %d 的奇偶性:\n", number);
print_binary_5bit(number);
printf("与 1 进行按位与运算:\n");
printf(" ");
print_binary_5bit(number);
printf("& ");
print_binary_5bit(1);
printf(" -----\n");
printf(" ");
// 打印结果,对齐格式
for (int i = 4; i >= 0; i--) {
if (i == 0) {
printf("%d", number & 1);
} else {
printf(" ");
}
}
if (number & 1) {
printf(" → 结果为1,说明%d是奇数\n", number);
} else {
printf(" → 结果为0,说明%d是偶数\n", number);
}
}
int main() {
printf("=== 按位与运算判断奇偶性 ===\n");
check_odd_even(10); // 偶数
check_odd_even(15); // 奇数
check_odd_even(7); // 奇数
check_odd_even(8); // 偶数
return 0;
}
运行结果:
=== 按位与运算判断奇偶性 ===
判断 10 的奇偶性:
10的二进制表示: 01010
与 1 进行按位与运算:
01010
& 00001
-----
0 → 结果为0,说明10是偶数
判断 15 的奇偶性:
15的二进制表示: 01111
与 1 进行按位与运算:
01111
& 00001
-----
1 → 结果为1,说明15是奇数
判断 7 的奇偶性:
7的二进制表示: 00111
与 1 进行按位与运算:
00111
& 00001
-----
1 → 结果为1,说明7是奇数
判断 8 的奇偶性:
8的二进制表示: 01000
与 1 进行按位与运算:
01000
& 00001
-----
0 → 结果为0,说明8是偶数
2. 权限检查系统
#include <stdio.h>
// 定义权限标志
#define READ_PERM (1 << 0) // 0001 - 读权限
#define WRITE_PERM (1 << 1) // 0010 - 写权限
#define EXEC_PERM (1 << 2) // 0100 - 执行权限
#define DELETE_PERM (1 << 3) // 1000 - 删除权限
void check_permissions(unsigned int user_perm, const char* user_name) {
printf("\n%s 的权限检查:\n", user_name);
printf("用户权限码: %04b\n", user_perm);
printf("读权限: ");
printf("%04b & %04b = ", user_perm, READ_PERM);
printf("%04b → ", user_perm & READ_PERM);
printf("%s\n", (user_perm & READ_PERM) ? "有权限" : "无权限");
printf("写权限: ");
printf("%04b & %04b = ", user_perm, WRITE_PERM);
printf("%04b → ", user_perm & WRITE_PERM);
printf("%s\n", (user_perm & WRITE_PERM) ? "有权限" : "无权限");
printf("执行权限: ");
printf("%04b & %04b = ", user_perm, EXEC_PERM);
printf("%04b → ", user_perm & EXEC_PERM);
printf("%s\n", (user_perm & EXEC_PERM) ? "有权限" : "无权限");
printf("删除权限: ");
printf("%04b & %04b = ", user_perm, DELETE_PERM);
printf("%04b → ", user_perm & DELETE_PERM);
printf("%s\n", (user_perm & DELETE_PERM) ? "有权限" : "无权限");
}
int main() {
unsigned int admin = READ_PERM | WRITE_PERM | EXEC_PERM | DELETE_PERM;
unsigned int user = READ_PERM | EXEC_PERM;
unsigned int guest = READ_PERM;
check_permissions(admin, "管理员");
check_permissions(user, "普通用户");
check_permissions(guest, "访客");
return 0;
}
运行结果:
管理员 的权限检查:
用户权限码: 1111
读权限: 1111 & 0001 = 0001 → 有权限
写权限: 1111 & 0010 = 0010 → 有权限
执行权限: 1111 & 0100 = 0100 → 有权限
删除权限: 1111 & 1000 = 1000 → 有权限
普通用户 的权限检查:
用户权限码: 0101
读权限: 0101 & 0001 = 0001 → 有权限
写权限: 0101 & 0010 = 0000 → 无权限
执行权限: 0101 & 0100 = 0100 → 有权限
删除权限: 0101 & 1000 = 0000 → 无权限
访客 的权限检查:
用户权限码: 0001
读权限: 0001 & 0001 = 0001 → 有权限
写权限: 0001 & 0010 = 0000 → 无权限
执行权限: 0001 & 0100 = 0000 → 无权限
删除权限: 0001 & 1000 = 0000 → 无权限
总结
按位与运算的核心用途:
-
奇偶判断:
number & 1- 结果为0 → 偶数
- 结果为1 → 奇数
-
位提取:使用掩码提取特定位
-
data & mask只保留mask中为1的位
-
-
权限检查:使用位标志表示多种状态
- 每个位代表一种权限或状态
清零操作:与0相与可以清除特定位
优势:
- 高效:直接操作二进制位,速度快
- 简洁:一个操作同时检查多个条件
- 节省内存:一个整数可以表示多个布尔状态
3. 按位异或(^)
规则:两个位不同时结果为1,相同时结果为0
#include <stdio.h>
void bitwise_xor_demo() {
unsigned int a = 0b00111; // 十进制 7
unsigned int b = 0b11100; // 十进制 28
unsigned int result = a ^ b;
printf("按位异或运算:\n");
printf(" a = 00111 (7)\n");
printf(" b = 11100 (28)\n");
printf(" a ^ b = 11011 (27)\n");
printf(" 实际结果: %u\n\n", result);
}
// 实用技巧:交换两个变量的值(不借助临时变量)
void swap_without_temp() {
int x = 10, y = 20;
printf("交换前: x = %d, y = %d\n", x, y);
x = x ^ y;
y = x ^ y;
x = x ^ y;
printf("交换后: x = %d, y = %d\n", x, y);
}
// 实用技巧:特定位取反
void toggle_bits() {
unsigned int value = 0b11001100; // 204
unsigned int mask = 0b00001111; // 15
unsigned int result = value ^ mask;
printf("特定位取反:\n");
printf(" 原值: 11001100 (204)\n");
printf(" 掩码: 00001111 (15)\n");
printf(" 结果: 11000011 (195)\n");
printf(" 实际: %u\n", result);
}
int main() {
bitwise_xor_demo();
swap_without_temp();
printf("\n");
toggle_bits();
return 0;
}
运行结果:
按位异或运算:
a = 00111 (7)
b = 11100 (28)
a ^ b = 11011 (27)
实际结果: 27
交换前: x = 10, y = 20
交换后: x = 20, y = 10
特定位取反:
原值: 11001100 (204)
掩码: 00001111 (15)
结果: 11000011 (195)
实际: 195
4. 左移位运算(<<)
规则:将所有位向左移动,右侧补0
#include <stdio.h>
void left_shift_demo() {
int a = 5; // 二进制: 0101
printf("左移位运算:\n");
printf(" 原始值: %d (二进制: 0101)\n", a);
printf(" a << 1 = %d (二进制: 1010) - 相当于 ×2\n", a << 1);
printf(" a << 2 = %d (二进制: 10100) - 相当于 ×4\n", a << 2);
printf(" a << 3 = %d (二进制: 101000) - 相当于 ×8\n\n", a << 3);
}
// 实用技巧:快速计算2的幂次方
void power_of_two() {
printf("2的幂次方快速计算:\n");
for (int i = 0; i < 5; i++) {
printf(" 2的%d次方 = %d\n", i, 1 << i);
}
}
int main() {
left_shift_demo();
power_of_two();
return 0;
}
运行结果:
左移位运算:
原始值: 5 (二进制: 0101)
a << 1 = 10 (二进制: 1010) - 相当于 ×2
a << 2 = 20 (二进制: 10100) - 相当于 ×4
a << 3 = 40 (二进制: 101000) - 相当于 ×8
2的幂次方快速计算:
2的0次方 = 1
2的1次方 = 2
2的2次方 = 4
2的3次方 = 8
2的4次方 = 16
5. 右移位运算(>>)
规则:将所有位向右移动,左侧补0(无符号数)或符号位(有符号数)
#include <stdio.h>
void right_shift_demo() {
unsigned int a = 20; // 二进制: 10100
printf("右移位运算(无符号数):\n");
printf(" 原始值: %u (二进制: 10100)\n", a);
printf(" a >> 1 = %u (二进制: 01010) - 相当于 ÷2\n", a >> 1);
printf(" a >> 2 = %u (二进制: 00101) - 相当于 ÷4\n", a >> 2);
printf(" a >> 3 = %u (二进制: 00010) - 相当于 ÷8\n\n", a >> 3);
}
// 有符号数的右移(注意符号位扩展)
void signed_right_shift() {
int positive = 16; // 正数
int negative = -16; // 负数
printf("有符号数右移:\n");
printf(" 正数 16 >> 2 = %d\n", positive >> 2);
printf(" 负数 -16 >> 2 = %d\n", negative >> 2);
}
int main() {
right_shift_demo();
signed_right_shift();
return 0;
}
运行结果:
右移位运算(无符号数):
原始值: 20 (二进制: 10100)
a >> 1 = 10 (二进制: 01010) - 相当于 ÷2
a >> 2 = 5 (二进制: 00101) - 相当于 ÷4
a >> 3 = 2 (二进制: 00010) - 相当于 ÷8
有符号数右移:
正数 16 >> 2 = 4
负数 -16 >> 2 = -4
6. 按位取反(~)
规则:将所有位取反(0变1,1变0)
#include <stdio.h>
void bitwise_not_demo() {
unsigned char a = 0b00111001; // 十进制 57
unsigned char result = ~a;
printf("按位取反运算(无符号数):\n");
printf(" a = 00111001 (57)\n");
printf(" ~a = 11000110 (198)\n");
printf(" 实际结果: %u\n\n", result);
}
// 有符号数的取反(注意补码表示)
void signed_bitwise_not() {
char a = 5; // 00000101
char b = ~a; // 11111010
printf("有符号数取反:\n");
printf(" 5 的二进制: 00000101\n");
printf(" ~5 的二进制: 11111010 (补码)\n");
printf(" ~5 的十进制: %d\n\n", b);
// 通用公式:~n = -(n + 1)
printf("按位取反通用公式验证:\n");
printf(" ~5 = %d, -(5+1) = %d\n", ~5, -(5+1));
printf(" ~10 = %d, -(10+1) = %d\n", ~10, -(10+1));
printf(" ~-4 = %d, -(-4+1) = %d\n", ~-4, -(-4+1));
}
int main() {
bitwise_not_demo();
signed_bitwise_not();
return 0;
}
运行结果:
按位取反运算(无符号数):
a = 00111001 (57)
~a = 11000110 (198)
实际结果: 198
有符号数取反:
5 的二进制: 00000101
~5 的二进制: 11111010 (补码)
~5 的十进制: -6
按位取反通用公式验证:
~5 = -6, -(5+1) = -6
~10 = -11, -(10+1) = -11
~-4 = 3, -(-4+1) = 3
三、位运算复合赋值操作
#include <stdio.h>
void compound_assignment_demo() {
unsigned int flags = 0;
printf("位运算复合赋值操作:\n");
// 定义一些标志位
const unsigned int FLAG_A = 1 << 0; // 0001
const unsigned int FLAG_B = 1 << 1; // 0010
const unsigned int FLAG_C = 1 << 2; // 0100
const unsigned int FLAG_D = 1 << 3; // 1000
// 设置标志位
flags |= FLAG_A; // 设置标志A
flags |= FLAG_C; // 设置标志C
printf("设置A和C标志后: 0x%X\n", flags);
// 检查标志位
if (flags & FLAG_A) {
printf(" 标志A已设置\n");
}
if (flags & FLAG_B) {
printf(" 标志B已设置\n");
}
// 清除标志位
flags &= ~FLAG_A; // 清除标志A
printf("清除标志A后: 0x%X\n", flags);
// 切换标志位
flags ^= FLAG_B; // 切换标志B
printf("切换标志B后: 0x%X\n", flags);
flags ^= FLAG_B; // 再次切换标志B
printf("再次切换标志B后: 0x%X\n", flags);
}
int main() {
compound_assignment_demo();
return 0;
}
运行结果:
位运算复合赋值操作:
设置A和C标志后: 0x5
标志A已设置
清除标志A后: 0x4
切换标志B后: 0x6
再次切换标志B后: 0x4
四、实用技巧总结
1. 权限管理系统
#include <stdio.h>
// 定义权限标志
#define PERM_READ (1 << 0) // 0001
#define PERM_WRITE (1 << 1) // 0010
#define PERM_EXECUTE (1 << 2) // 0100
#define PERM_DELETE (1 << 3) // 1000
void check_permissions(unsigned int user_perm) {
printf("用户权限检查:\n");
printf(" 读取权限: %s\n", (user_perm & PERM_READ) ? "有" : "无");
printf(" 写入权限: %s\n", (user_perm & PERM_WRITE) ? "有" : "无");
printf(" 执行权限: %s\n", (user_perm & PERM_EXECUTE) ? "有" : "无");
printf(" 删除权限: %s\n", (user_perm & PERM_DELETE) ? "有" : "无");
}
int main() {
// 管理员权限:所有权限
unsigned int admin_perm = PERM_READ | PERM_WRITE | PERM_EXECUTE | PERM_DELETE;
// 普通用户权限:只有读取和执行
unsigned int user_perm = PERM_READ | PERM_EXECUTE;
printf("管理员权限:\n");
check_permissions(admin_perm);
printf("\n普通用户权限:\n");
check_permissions(user_perm);
return 0;
}
2. 性能优化技巧
#include <stdio.h>
void performance_tips() {
int a = 100;
printf("性能优化技巧:\n");
printf(" 使用 a << 1 代替 a * 2: %d\n", a << 1);
printf(" 使用 a >> 1 代替 a / 2: %d\n", a >> 1);
printf(" 使用 a & 1 代替 a %% 2: %d\n", a & 1);
// 判断是否为2的幂次方
int b = 16;
if ((b & (b - 1)) == 0) {
printf(" %d 是2的幂次方\n", b);
}
}
int main() {
performance_tips();
return 0;
}
五、重要注意事项
- 符号位问题:有符号数的右移和取反要特别注意符号位
- 移位范围:避免移位超过数据类型的位数
- 优先级:位运算符的优先级较低,多用括号确保正确性
- 可读性:在性能不关键的地方,优先使用算术运算保证代码可读性
掌握位运算可以让你写出更高效、更底层的代码,是进阶C语言编程的重要技能!