目录
1.位移运算符介绍
位运算符用来对二进制位进行操作,C语言中提供了如上表所示的位运算符。
位运算符中,除 ~ (取反)以外,其余均为双目运算符。
- & 按位与
- | 按位或
- ^ 按位异或
- ~取反
- >>右移
- <<左移
整数在内存中存储的形式是补码的二进制
位运算符是对补码进行运算
2.原码、反码、补码
整数的二进制表示:有3种(原码、反码、补码)
原码:直接根据数值写出的二进制序列就是原码(32位)
反码:原码的符号位不变,其他位按位取反就是反码
补码:反码加1,就是补码
对于正整数的原码、反码、补码都相同;负数是存放在二进制的补码中,负整数的原码、反码、补码都不相同。
例如:1(正整数的原码、反码、补码都相同)(最高位是符号位,0 表示正数)
原码:0000000 00000000 00000000 00000001
反码:0000000 00000000 00000000 00000001
补码:0000000 00000000 00000000 00000001
例如:-1(负整数的原码、反码、补码都不相同)(最高位为是符号位,1表示负数)
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110(按位取反,符号位不变)
补码:11111111 11111111 11111111 11111111(反码加1)
3.运算符讲解
<< 左移动运算符
规则:左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0。
简单说就是:左边丢弃,右边补0
举例说明
#include <stdio.h>
int main()
{
int a = -5;
int b = a << 1;
printf("%d\n", a); // -5
printf("%d\n", b); // -10
return 0;
}
过程说明
先写出 -5 的补码
原码:10000000 00000000 00000000 00000101 (最高位为1)
反码:11111111 11111111 11111111 11111010 (按位取反,符号位不变)
补码:11111111 11111111 11111111 11111011 (反码加1)
补码向左移动一位,左边去掉,右边补0,得到
11111111 11111111 11111111 11110110
反推原码进行打印(%d为打印形式,因此对补码按int解读,首位为符号位)
补码:11111111 11111111 11111111 11110110
反码:11111111 11111111 11111111 11110101(补码 -1 得到反码)
原码:10000000 00000000 00000000 00001010(按位取反得到原码)
此时得到的原码打印就是-10了
左移操作符通常用于对数值进行快速的乘以2的幂次方的操作,因为每向左移动一位就相当于乘以2。
也常用于在底层编程中操作位字段。
int a = 5; // 二进制表示为 0000 0101
int n = 2;
int result = a << n; // 结果是 20,二进制表示为 0001 0100
int a = 7; // 二进制表示为 0000 0111
int result = a << 3; // 结果是 56,因为 7 * 2^3 = 56
// 二进制表示:0000 0111 -> 0011 1000
>> 右移运算符
右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0。然后符号位与原来保持一致。
举例说明
int main()
{
char a = -126;
char b = a >> 1;
printf("%d\n", a); // -126
printf("%d\n", b); // -63
return 0;
}
-126的补码
原码:1111 1110
反码:1000 0001
补码:1000 0010
右移1位
补码:1000 0010
右移一位后的补码:0100 0001
符号位与原来保持一致:1100 0001
反码:1100 0000
原码:1011 1111
1011 1111就是-63
这个操作通常用于对整数进行快速的除以2的幂次方的操作,或者用于在处理位字段时调整位的位置。
#include <stdio.h>
int main()
{
// 7 >> 1 就是 7 / 2
printf("%d\n", 7 >> 1); // 3
return 0;
}
& 按位与
规则:两个二进制操作数对应位同为 1 ,结果位才为 1 ,其余情况为 0 。
示例代码
#include <stdio.h>
int main()
{
unsigned char a = 252;
unsigned char b = 143;
char c = a & b;
printf("%d\n", c); // -116
return 0;
}
解释
a的补码: 1111 1100
b的补码: 1000 1111
按&运算------------
c的补码: 1000 1100
注意c类型为char,是有符号的,首位看作符号,获取原码
c的补码: 1000 1100
c的反码:1000 1011
c的原码:1111 0100
111 0100对应116.所以打印-116
| 按位或
规则:两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0
示例代码
#include <stdio.h>
int main()
{
int a = 5;
int b = -2;
int c = a | b;
printf("%d\n", c); // -1
return 0;
}
5 的补码:00000000 00000000 00000000 00000101
-2 的补码:11111111 11111111 11111111 11111110
5 | -2 的补码:11111111 11111111 11111111 11111111
补码,需要原码才能打印
5 | -2 的补码:11111111 11111111 11111111 11111111
5 | -2 的反码:11111111 11111111 11111111 11111110
5 | -2 的原码:10000000 00000000 00000000 00000001
^ 按位异或
规则:两个二进制操作数对应 位 相同为 0 ,不同为 1
示例
#include <stdio.h>
int main()
{
int a = 5;
int b = -2;
int c = a ^ b;
printf("%d\n", c); // -5
return 0;
}
过程
5 的补码:00000000 00000000 00000000 00000101
-2 的补码:11111111 11111111 11111111 11111110
5 ^ -2 的补码:11111111 11111111 11111111 11111011
补码,需要原码才能打印
5 ^ -2 的补码:11111111 11111111 11111111 11111011
5 ^ -2 的反码:11111111 11111111 11111111 11111010
5 ^ -2 的原码:10000000 00000000 00000000 00000101
~ 取反
规则:一个二进制操作数,对应位为 0 ,结果位为 1 ;对应位为 1 ,结果位为 0
作用是将每位二进制取反
示例
#include <stdio.h>
int main()
{
int a = 5;
int c = ~a;
printf("%d\n", c); // -6
return 0;
}
过程
5 的补码:00000000 00000000 00000000 00000101
取反:11111111 11111111 11111111 11111010
反码:11111111 11111111 11111111 11111001
原码:10000000 00000000 00000000 00000110
4.位运算宏举例
获取低16位
// 定义一个宏,提取低16位赋值给uint16_t
#define EXTRACT_LOW_16_BITS(value) ((uint16_t)((value) & 0xFFFF))
uint32_t idx = 0x12345678; // 假设这是你的原始32位值
uint16_t index;
// 使用宏来提取低16位
index = EXTRACT_LOW_16_BITS(idx);
// 此时index将包含0x5678
// 你可以安全地将这个值作为uint16_t类型使用
// 定义一个宏,提取低8位赋值给uint8_t
#define EXTRACT_LOW_8_BITS(value) ((uint8_t)((value) & 0xFF))
获取某一位
// 获取第bitPos位的bit值,0,1,2....等
#define GET_BIT(num, bitPos) ((num >> bitPos) & 0x1)
// 通过右移操作将目标位移动到最低位
// 然后使用位与操作(&)与1进行运算,以获取最低位的值
2的n次幂
#define Power_Two(n) ((uint32)(0x1UL << (n)))
作用是计算并返回2的n次幂,其中n是函数的参数,表示幂次
0x1UL << (n) 是一个C或C++语言中的表达式,它结合了字面量、类型转换和左移操作符
- 0x1:这是一个十六进制字面量,它等于十进制的 1
- UL:这是两个字符的组合,分别代表 unsigned long 类型的字面量后缀。在这个上下文中,UL 主要用于确保字面量具有足够的位宽来容纳左移操作的结果。
- <<:这是左移操作符。
- 0x1UL << (n) 的意思是:将无符号长整型字面量 1(在二进制中表示为 0000 0001,这里省略了前导零以简化说明)向左移动 n 位。
- 在它原来所在的位置和更高位之间填充了 0。
- 如果 n 是 2,则 0x1UL << (n) 的结果是 4(在二进制中表示为 0000 0100)
对位截取
#define GET_BITS_U32(x, hi, lo) (((x) >> (lo)) & ((1U << ((hi) - (lo) + 1)) - 1))
这是一个宏定义,用于从一个无符号32位整数(x
)中提取从低位(lo
)到高位(hi
,包括)之间的位。这个宏利用了位操作和位掩码的概念来实现其功能。下面是对这个宏的详细解释:
参数说明
x
:待操作的无符号32位整数。hi
:需要提取的最高位的索引(从0开始计数)。lo
:需要提取的最低位的索引(从0开始计数)。
宏的工作原理
- 右移操作:
((x) >> (lo))
:这部分将x
向右移动lo
位。这样做的目的是将目标位段(从lo
到hi
)移动到最低位(即0到(hi-lo+1)
)的位置。 - 生成掩码:
((1U << ((hi) - (lo) + 1)) - 1)
1U
是一个无符号整数常量,值为1。1U << ((hi) - (lo) + 1)
将1向左移动(hi - lo + 1)
位。这样做是为了创建一个掩码,这个掩码在第(hi - lo + 1)
位上为1,其余位都是0。- 然后通过
-1
操作,第(hi - lo + 1)
位上为1变为0,第0位到第(hi - lo)位为1
。
- 按位与操作:
(((x) >> (lo)) & ((1U << ((hi) - (lo) + 1)) - 1))
:最后,将上述两步的结果进行按位与操作。得到从lo位到hi位的截取。
示例
假设x = 0b 1101 0010 1011 0011 0011 0011 0011 0011
(二进制表示),hi = 10
,lo = 4
。
- 首先,
(x >> lo)
得到0b
0000 1101 0010 1011 0011 0011 0011 0011。 - 然后,
(1U << ((hi) - (lo) + 1)) - 1
生成掩码0b
0111 1111。 - 最后,两者按位与得到
0b
011 0011,这就是从x
中提取的从第4位到第10位(包括)的值。
end