C&C++快速入门

7、位运算

1、课程目标

  • 掌握所有位运算符的使用
  • 理解位运算在底层编程中的应用
  • 掌握位掩码和标志位操作
  • 学会使用位运算进行性能优化

2、名词解释

术语 符号 说明
按位与 & 两位都为1则为1
按位或 | 任一位为1则为1
按位异或 ^ 两位不同则为1
按位取反 ~ 0变1,1变0
左移 << 二进制位左移,低位补0
右移 >> 二进制位右移,高位补符号位或0
位掩码 Bitmask 用于提取或设置特定位的值
标志位 Flag 用一个位表示一个开关状态

3、技术原理

3.1、位运算真值表

A  B | A&B | A|B | A^B | ~A
0  0 |  0  |  0  |  0  |  1
0  1 |  0  |  1  |  1  |  1
1  0 |  0  |  1  |  1  |  0
1  1 |  1  |  1  |  0  |  0

4、代码实现

1. 基础位运算操作

#include <stdio.h>

void PrintBinary(unsigned int num) {
    for (int i = 31; i >= 0; i--) {
        printf("%d", (num >> i) & 1);
        if (i % 8 == 0) printf(" ");
    }
    printf("\n");
}

int main() {
    printf("=== 位运算基础 ===\n\n");
    
    unsigned int a = 0b11110000;  // 240
    unsigned int b = 0b10101010;  // 170
    
    printf("a = %3d = ", a); PrintBinary(a);
    printf("b = %3d = ", b); PrintBinary(b);
    
    printf("\n【位运算结果】\n");
    printf("a & b  = %3d = ", a & b);  PrintBinary(a & b);
    printf("a | b  = %3d = ", a | b);  PrintBinary(a | b);
    printf("a ^ b  = %3d = ", a ^ b);  PrintBinary(a ^ b);
    printf("~a     = %3d = ", (unsigned char)~a); PrintBinary((unsigned char)~a);
    
    printf("\n【移位运算】\n");
    printf("a << 2 = %3d = ", a << 2); PrintBinary(a << 2);
    printf("a >> 2 = %3d = ", a >> 2); PrintBinary(a >> 2);
    
    return 0;
}

2. 位操作常用技巧

#include <stdio.h>

// 设置第n位为1
#define SET_BIT(x, n)    ((x) |= (1U << (n)))

// 清除第n位(设为0)
#define CLEAR_BIT(x, n)  ((x) &= ~(1U << (n)))

// 翻转第n位
#define TOGGLE_BIT(x, n) ((x) ^= (1U << (n)))

// 检查第n位是否为1
#define CHECK_BIT(x, n)  (((x) >> (n)) & 1U)

// 获取最低的1位
#define LOWEST_BIT(x)    ((x) & (-(x)))

// 清除最低的1位
#define CLEAR_LOWEST(x)  ((x) & ((x) - 1))

int main() {
    printf("=== 位操作技巧 ===\n\n");
    
    unsigned int flags = 0;
    
    // 设置位
    printf("【设置位】\n");
    SET_BIT(flags, 0);  // 设置第0位
    printf("设置第0位后: 0x%02X\n", flags);
    SET_BIT(flags, 3);  // 设置第3位
    printf("设置第3位后: 0x%02X\n", flags);
    
    // 检查位
    printf("\n【检查位】\n");
    printf("第0位: %d\n", CHECK_BIT(flags, 0));
    printf("第1位: %d\n", CHECK_BIT(flags, 1));
    printf("第3位: %d\n", CHECK_BIT(flags, 3));
    
    // 清除位
    printf("\n【清除位】\n");
    CLEAR_BIT(flags, 0);
    printf("清除第0位后: 0x%02X\n", flags);
    
    // 翻转位
    printf("\n【翻转位】\n");
    TOGGLE_BIT(flags, 3);
    printf("翻转第3位后: 0x%02X\n", flags);
    TOGGLE_BIT(flags, 3);
    printf("再次翻转后: 0x%02X\n", flags);
    
    // 特殊技巧
    printf("\n【特殊技巧】\n");
    unsigned int x = 0b10110100;  // 180
    printf("x = %d (0b10110100)\n", x);
    printf("最低1位: %d\n", LOWEST_BIT(x));
    printf("清除最低1位后: %d\n", CLEAR_LOWEST(x));
    
    return 0;
}

3. Windows权限标志位操作

#include <stdio.h>
#include <windows.h>

void PrintFileAccess(DWORD access) {
    printf("访问权限 (0x%08lX):\n", access);
    
    if (access & GENERIC_READ)    printf("  - 通用读取\n");
    if (access & GENERIC_WRITE)   printf("  - 通用写入\n");
    if (access & GENERIC_EXECUTE) printf("  - 通用执行\n");
    if (access & GENERIC_ALL)     printf("  - 完全控制\n");
}

void PrintMemoryProtection(DWORD protect) {
    printf("内存保护 (0x%02lX):\n", protect);
    
    switch (protect & 0xFF) {
        case PAGE_NOACCESS:          printf("  禁止访问\n"); break;
        case PAGE_READONLY:          printf("  只读\n"); break;
        case PAGE_READWRITE:         printf("  读写\n"); break;
        case PAGE_EXECUTE:           printf("  执行\n"); break;
        case PAGE_EXECUTE_READ:      printf("  执行+读\n"); break;
        case PAGE_EXECUTE_READWRITE: printf("  执行+读写\n"); break;
    }
    
    if (protect & PAGE_GUARD)        printf("  + 守护页\n");
    if (protect & PAGE_NOCACHE)      printf("  + 禁止缓存\n");
}

int main() {
    printf("=== Windows标志位 ===\n\n");
    
    // 文件访问权限组合
    DWORD access = GENERIC_READ | GENERIC_WRITE;
    PrintFileAccess(access);
    
    // 内存保护属性
    printf("\n");
    PrintMemoryProtection(PAGE_EXECUTE_READWRITE | PAGE_GUARD);
    
    // 进程创建标志
    printf("\n【进程创建标志】\n");
    DWORD createFlags = CREATE_SUSPENDED | CREATE_NEW_CONSOLE;
    printf("标志值: 0x%08lX\n", createFlags);
    if (createFlags & CREATE_SUSPENDED)   printf("  - 挂起创建\n");
    if (createFlags & CREATE_NEW_CONSOLE) printf("  - 新控制台\n");
    
    return 0;
}

4. 位运算实用算法

#include <stdio.h>

// 计算1的个数(Brian Kernighan算法)
int CountBits(unsigned int n) {
    int count = 0;
    while (n) {
        n &= (n - 1);  // 清除最低的1位
        count++;
    }
    return count;
}

// 判断是否为2的幂
int IsPowerOfTwo(unsigned int n) {
    return n && !(n & (n - 1));
}

// 向上取整到2的幂
unsigned int RoundUpToPowerOfTwo(unsigned int n) {
    n--;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n++;
    return n;
}

// 交换两个数(不用临时变量)
void SwapXOR(int* a, int* b) {
    if (a != b) {  // 必须检查,否则会出错
        *a ^= *b;
        *b ^= *a;
        *a ^= *b;
    }
}

// 取绝对值(无分支)
int Abs(int n) {
    int mask = n >> 31;  // 全0或全1
    return (n ^ mask) - mask;
}

int main() {
    printf("=== 位运算算法 ===\n\n");
    
    // 计算1的个数
    printf("【计算1的个数】\n");
    printf("255的1位数: %d\n", CountBits(255));
    printf("128的1位数: %d\n", CountBits(128));
    
    // 判断2的幂
    printf("\n【判断2的幂】\n");
    printf("16是2的幂: %s\n", IsPowerOfTwo(16) ? "是" : "否");
    printf("15是2的幂: %s\n", IsPowerOfTwo(15) ? "是" : "否");
    
    // 向上取整
    printf("\n【向上取整到2的幂】\n");
    printf("100 -> %u\n", RoundUpToPowerOfTwo(100));
    printf("1000 -> %u\n", RoundUpToPowerOfTwo(1000));
    
    // 交换
    printf("\n【XOR交换】\n");
    int x = 10, y = 20;
    printf("交换前: x=%d, y=%d\n", x, y);
    SwapXOR(&x, &y);
    printf("交换后: x=%d, y=%d\n", x, y);
    
    // 绝对值
    printf("\n【无分支绝对值】\n");
    printf("|42| = %d\n", Abs(42));
    printf("|-42| = %d\n", Abs(-42));
    
    return 0;
}

5、课后作业

5.1、作业1:实现位字段结构

使用位运算实现一个IP地址解析器:

void ParseIPv4(DWORD ipAddr);
// 输入: 0xC0A80001
// 输出: 192.168.0.1

5.2、作业2:权限管理系统

实现一个简单的权限管理系统,支持添加、删除、检查权限。

5.3、作业3:简单加密

使用XOR实现简单的数据加密解密(同一密钥加密两次还原)。