首页 > 编程笔记

C语言位运算

位运算是计算机科学中的一种基本运算,它主要是对二进制位进行操作。C语言中提供了六种位运算操作符,分别是按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)、右移(>>)。这些位运算可以用来完成许多有趣的操作,例如加密、解密、优化算法等等。在这篇文章中,我将详细介绍C语言中的位运算,包括每种运算的使用方法、应用场景和注意事项。

按位与(&)

按位与操作符(&)可以对两个二进制数的每一位进行比较,如果两个位都是 1,则结果为 1,否则为 0。下面是一个按位与的示例:
int a = 5; // 二进制表示为 101
int b = 3; // 二进制表示为 011
int c = a & b; // 按位与结果为 001,即 1
在这个示例中,a 和 b 的二进制位都是三位数,因为它们都是 int 类型,所以 C语言会自动把它们扩展到 32 位。然后,按位与操作符(&)对每一位进行比较,最后得到一个结果,结果的二进制位中,只有在 a 和 b 的相应位置都为 1 时才为 1。

按位与操作符的应用非常广泛,例如可以用来判断一个二进制数中的某一位是否为 1,可以用来对某些位清零等等。

按位或(|)

按位或操作符(|)可以对两个二进制数的每一位进行比较,如果两个位中至少有一个是 1,则结果为 1,否则为 0。下面是一个按位或的示例:
int a = 5; // 二进制表示为 101
int b = 3; // 二进制表示为 011
int c = a | b; // 按位或结果为 111,即 7
在这个示例中,a 和 b 的二进制位都是三位数,因为它们都是 int 类型,所以 C语言会自动把它们扩展到 32 位。然后,按位或操作符(|)对每一位进行比较,最后得到一个结果,结果的二进制位中,只要在 a 和 b 的相应位置有一个为 1 时就为 1。

按位或操作符的应用也非常广泛,例如可以用来将某些位设置为 1,可以用来合并两个二进制数等等。

按位异或(^)

在 C语言中,按位异或操作符(^)可以对两个二进制数的每一位进行比较,如果两个位不相同,则结果为 1,否则为 0。下面是一个按位异或的示例:
int a = 5; // 二进制表示为 101
int b = 3; // 二进制表示为 011
int c = a ^ b; // 按位异或结果为 110,即 6
在这个示例中,a 和 b 的二进制位都是三位数,因为它们都是 int 类型,所以 C语言会自动把它们扩展到 32 位。然后,按位异或操作符(^)对每一位进行比较,最后得到一个结果,结果的二进制位中,只有在 a 和 b 的相应位置不相同时才为 1。

按位异或操作符的应用也非常广泛,例如可以用来交换两个变量的值,可以用来检测一个二进制数中有多少位不同等等。

按位取反(~)

按位取反操作符(~)可以将一个二进制数的每一位都取反,即 0 变为 1,1 变为 0。下面是一个按位取反的示例:
int a = 5; // 二进制表示为 101
int b = ~a; // 按位取反结果为 11111111111111111111111111111010,即 -6
在这个示例中,a 的二进制位是三位数,因为它是 int 类型,所以 C语言会自动把它扩展到 32 位。然后,按位取反操作符(~)将 a 的每一位都取反,最后得到一个结果。

按位取反操作符的应用也非常广泛,例如可以用来对某些位取反,可以用来将一个二进制数变为其相反数等等。

左移(<<)

左移操作符(<<)可以将一个二进制数的所有位向左移动指定的位数。左移 n 位相当于将这个数乘以 2 的 n 次方。下面是一个左移的示例:
int a = 5; // 二进制表示为 101
int b = a << 2; // 左移两位结果为 10100,即 20
在这个示例中,a 的二进制位是三位数,因为它是 int 类型,所以 C语言会自动把它扩展到 32 位。然后,左移操作符(<<)将 a 的所有位向左移动两位,最后得到一个结果。

右移(>>)

右移操作符(>>)可以将一个二进制数的所有位向右移动指定的位数。右移 n 位相当于将这个数除以 2 的 n 次方,但是对于带符号数,右移时需要注意符号位的处理。下面是一个右移的示例:
int a = 20; // 二进制表示为 10100
int b = a >> 2; // 右移两位结果为 101,即 5
在这个示例中,a 的二进制位是五位数,因为它是 int 类型,所以 C语言会自动把它扩展到 32 位。然后,右移操作符(>>)将 a 的所有位向右移动两位,最后得到一个结果。

对于带符号数,右移操作符(>>)需要注意符号位的处理。如果原数是正数,右移时会在左边填充 0,如果原数是负数,右移时会在左边填充 1。例如:
int a = -20; // 二进制表示为 11111111111111111111111111101100
int b = a >> 2; // 右移两位结果为 11111111111111111111111111111011,即 -5
在这个示例中,a 是一个负数,右移时会在左边填充 1,因此最后得到的结果也是一个负数。

右移操作符的应用也非常广泛,例如可以用来将一个二进制数除以 2 的 n 次方,可以用来将一个有符号数的高位补齐等等。

按位操作的应用

按位操作在 C语言中有很多应用场景,下面列举一些常见的应用:

位字段

位字段是一种特殊的结构体类型,它可以将一个整数拆分成多个部分,每个部分表示一个特定的属性。例如:
struct {
    unsigned int is_deleted : 1; // 标志位,表示是否删除
    unsigned int is_locked : 1; // 标志位,表示是否锁定
    unsigned int reserved : 30; // 预留位
} status;
在这个例子中,status 结构体包含三个位字段,is_deleted 和 is_locked 都是 1 位长的标志位,reserved 是 30 位长的预留位。

位运算优化

位运算可以用来优化一些计算,例如将乘法和除法转换成位移运算。例如,将一个数乘以 2 的 n 次方可以写成左移 n 位,将一个数除以 2 的 n 次方可以写成右移 n 位。这种优化方法可以提高程序的执行效率。

位掩码

位掩码是一种常见的技术,用于提取和修改一个数的特定位。例如,我们可以定义一个掩码来提取一个字节的低 4 位:
unsigned char value = 0xAB; // 10101011
unsigned char mask = 0x0F; // 00001111
unsigned char result = value & mask; // 00001011,即 11
在这个例子中,我们定义了一个掩码 0x0F,它的二进制表示为 00001111。然后,我们将 value 和 mask 进行按位与运算,得到的结果只包含 value 的低 4 位,即 00001011。

位掩码还可以用于修改一个数的特定位。例如,我们可以将一个字节的低 4 位清零:
unsigned char value = 0xAB; // 10101011
unsigned char mask = 0xF0; // 11110000
unsigned char result = value & mask; // 10100000
在这个例子中,我们定义了一个掩码 0xF0,它的二进制表示为 11110000。然后,我们将 value 和 mask 进行按位与运算,得到的结果只包含 value 的高 4 位,即 10100000。

嵌入式开发

在嵌入式系统中,资源非常有限,因此需要尽可能地节省内存和处理器时间。位运算可以用来压缩数据和优化代码,从而提高系统的性能和效率。

例如,可以用位运算将多个标志位压缩到一个字节中:
#define FLAG1 0x01 // 标志位1
#define FLAG2 0x02 // 标志位2
#define FLAG3 0x04 // 标志位3
#define FLAG4 0x08 // 标志位4

unsigned char flags = 0;
flags |= FLAG1;
flags |= FLAG3;

if (flags & FLAG1) {
    // 处理标志位1
}

if (flags & FLAG2) {
    // 处理标志位2
}

if (flags & FLAG3) {
    // 处理标志位3
}

if (flags & FLAG4) {
    // 处理标志位4
}
在这个例子中,我们使用位运算将四个标志位压缩到一个字节中。首先,我们定义了四个标志位,它们分别占据了一个字节中的不同位置。然后,我们使用按位或运算将 FLAG1 和 FLAG3 设置为 1,表示这两个标志位被激活。

最后,我们使用按位与运算检查每个标志位的状态,以确定需要执行哪些操作。

注意事项

在使用位运算时,需要注意以下几点:

总结

位运算是一种强大的工具,可以用来处理二进制数据和优化代码。它可以进行逻辑运算、移位运算和掩码操作,可以用来提取和修改一个数的特定位。在使用位运算时,需要注意可读性、可移植性、溢出问题和位运算优化等方面的问题。

推荐阅读