C语言学习13-按位与(&)运算

位运算概述

为什么使用位运算?

  • 性能优化:位运算直接操作二进制位,速度极快
  • 内存节省:可以用单个整型变量存储多个布尔标志

很多三方轮子的源码底层都会用到大量的位运算

比如,最经典的用 “>>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 → 无权限

总结

按位与运算的核心用途:

  1. 奇偶判断number & 1

    • 结果为0 → 偶数
    • 结果为1 → 奇数
  2. 位提取:使用掩码提取特定位

    • data & mask 只保留mask中为1的位
  3. 权限检查:使用位标志表示多种状态

    • 每个位代表一种权限或状态
  4. 清零操作:与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;
}

五、重要注意事项

  1. 符号位问题:有符号数的右移和取反要特别注意符号位
  2. 移位范围:避免移位超过数据类型的位数
  3. 优先级:位运算符的优先级较低,多用括号确保正确性
  4. 可读性:在性能不关键的地方,优先使用算术运算保证代码可读性

掌握位运算可以让你写出更高效、更底层的代码,是进阶C语言编程的重要技能!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容