符号 | 说明 | 符号 | 说明 |
---|---|---|---|
& | 按位与 | ~ | 按位取反 |
| | 按位或 | << | 按位左移 |
^ | 按位异或 | >> | 按位右移 |
注意:参与位运算的运算量只能是整型或者字符型,不能是实型
说明:单目运算符,数据的每一个bit位取反,也就是二进制数位上的0变1,1变0
举例:
unsigned char ret = ~0x05; // 0000 0101 --> 1111 1010
printf("%d\n",~5);// -6 // %d:有符号十进制int,%u:无符号十进制int
运算规则:对与左右操作数,只有相应二进制位数据都为1时,结果数据对应位数据为1,简单来说:
你是1,我是1,结果就是
举例:
5 & 6 = 4 // 0000 0101 & 0000 0110 = 0000 0100
作用(四阶段理解):
① 获取某二进制位的数据
② 将指定二进制数据清零
③ 保留指定位
运算规则:对于左右操作数据,只要相应二进制位数据有一个为1,结果数据对应位数据为1,简单来说:
你是1 或 我是1 结果就是1
举例:
5 | 6 = 7 // 0000 0101 | 0000 0110 = 0000 0111
作用:
① 置位某二进制位数据
运算规则:对与左右操作数据,只要相应二进制位数据相同,结果数据对应位数据为0,不同为1,简单来说:
你我相同,结果为0;你我不同,结果为1
作用:
① 反转
② 值交换
运算规则:原操作数所有的二进制位数向左移动指定位;
无符号左移:
unsigned int a = 3 << 3; // 计算规则:3 * 2 ^ 3
unsigned int b = 5 << 4; // 计算规则:5 * 2 ^ 4
printf("%d\n",a); // 24
有符号左移:
int a = -3 << 3; // -3 * 2 ^ 3
printf("%d\n",a); // -24
运算规则:原操作数所有的二进制位数据向右移动指定位,移出的数据舍弃。
如果操作数是无符号数:左边用0补齐
如果操作数是有符号数:左边用什么去补全,取决于计算机系统:
逻辑右移:用0补全
算术右移:用1补全
大部分情况下,系统是遵循“算术右移”的
无符号右移:
操作数 >> 移动位数
unsigned char a = 3 >> 3;
printf("%d\n",a);// 0
有符号右移:
语法: 操作数 >> 移动位数
char a = -3 >> 3;
printf("%d\n",a);// -1
运算符:&=, |=, >>=, <<=, ∧=
举例:
a &= b 相当于 a = a & b 相同bit位,都为1,结果为1
a <<=2 相当于 a = a << 2
a >>=2 相当于 a = a >> 2
a |= b 相当于 a = a | b 相同bit位,只要有一个为1,结果为1
a ^= b 相当于 a = a ^ b 相同bit位,相同为0,不同为1
~a 对a取反 每一个bit位,0变1,1变0
如果两个数据长度不同(例如long型和short型),进行位运算时(如a & b,而a为long型,b为short型),系统会将二者按右端对齐。
如果b为正数,则左侧16位补满0;
如果b为负数,左端应补满1;
如果b为无符号整数型,则左侧添满0。
注意:以下所有场景的n都是从右侧开始计数,从0开始
将某个数据从右侧(低位)指定的二进制位(n1,n2,n3…)清零
a &= ~(1<<(n1-1) | 1<<(n2-1) | 1<<(n3-1) | ...); // 如果是指定第几,n就代表第n个,从1开始
a &= ~(1<<n1 | 1<<n2 | 1<<n3) | ...); // 如果是指定索引,n代表索引,从0开始
解析
其中, n1, n2, n3, ...
是我们想清零的位的索引,从右侧开始计数(从0开始)。
通过一个例子来详细解释这个公式:
假设我们有一个 char
类型的变量 a
,其二进制表示是 1011 0110
(即十进制的94)。我
们想要将它的第1位、第3位和第5位清零(注意:从右侧开始计数,从0开始)。
第1位:从右侧开始第1位(索引0)
第3位:从右侧开始第3位(索引2)
第5位:从右侧开始第5位(索引4)
我们需要生成一个掩码,其中这些位是1,其他位是0。然后取反(使用 ~ 运算符),使得这些位变成0,其他位变成1。
1<<(1-1)
即 1<<0
结果是 1011 0110 -- 0000 0001
1<<(3-1)
即 1<<2
结果是 1011 0110 -- 0000 0100
1<<(5-1)
即 1<<4
结果是 1011 0110 -- 0001 0000
将这些位掩码进行按位或运算: 0000 0001 | 0000 0100 | 0001 0000 结果是 0001
取反: ~(0001 0101)
结果是 1110 1010
使用按位与运算将 a
与掩码 1110 1010
进行运算: 1011 0110 & 1110 1010
结果是 1010 0010
#include
#include
int main(int argc, char *argv[])
{
#include
// unsigned int a = 0b10110110;
unsigned int a = 0xB6; // 初始值:1011 0110(二进
制),182(十进制)
a &= ~(1 << (1 - 1) | 1 << (3 - 1) | 1 << (5 - 1)); // 清零第1(索引0)、3(索引
2)、5(索引4)位
// 打印结果
printf("Result: %u,%x\n", a,a); // 十进制:162,十六进制:A2
printf("Binary: ");
for (int i = 7; i >= 0; i--)
{
printf("%d", (a >> i) & 1); // 二进制:1010 0010
}
printf("\n");
return 0;
}
(a & (1 << n)) >> n
1<
a & (1<
a
与这个掩码进行运算。结果将是一个只在第 n 位上有可能是 1(如果 a
的第 n
位是 1 的话),其他位上都是 0 的数。
(...) >> n
:将上一步的结果右移 n
位。由于结果中只有在第 n
位上可能是 1,右移 n
位后,这个 1 就会移动到最低位(索引 0)上。如果 a
的第 n
位原来是 0,那么结果就会是 0。
这样,我们就可以通过检查最低位是 0 还是 1 来确定 a 的第 n 位是 0 还是 1。
#include
#include
int main(int argc, char *argv[])
{
#include
unsigned int a = 0b10110110; // 二进制 1011 0110 ,十进制 182 初始值
int n = 3;
int bit = (a & (1<<n)) >> n;
printf("这个二进制数据%d位上的数据是%d\n", n, bit); // 3,0
return 0;
}
将某个数据从右侧指定的二进制位(n1,n2,n3)设置为1
a |= (1 << n1 | 1 << n2 | 1 << n3 |...);
解析:
1 << n :将数字1左移n位,得到一个仅在n位上为1的数。
1 << n1 | 1 << n2 | 1 << n3 :将多个这样的数进行按位或运算,得到一个在n1, n2,n3位上为1的数。
a |= (1 << n1 | 1 << n2 | 1 << n3) :将a与上述得到的数进行按位或运算,从而将a在n1, n2, n3位上设置为1。
#include
#include
int main(int argc, char *argv[])
{
#include
unsigned int a = 0b10101010; // 初始值,二进制表示为10101010
int n1 = 2, n2 = 4, n3 = 6; // 要设置为1的位
// 将a在第n1, n2, n3位上设置为1
a |= (1 << n1) | (1 << n2) | (1 << n3);
// 打印结果
printf("Result: %u\n", a); // 输出结果,二进制表示为11111110
printf("Binary: ");
for (int i = 7; i >= 0; i--)
{
printf("%d", (a >> i) & 1);
}
printf("\n");
return 0;
}
将某个数据指定的二进制位(n)反转
a ^= (1 << n)
解析:
1 << n
:将数字1左移n位,得到一个仅在n位上为1的数a ^= (1 << n)
:将a与上述得到的数进行按位异或运算,从而反转a在n位上的值(0变为1,1变为0)案例:
#include
int main() {
unsigned int a = 0b10101010; // 初始值,二进制表示为10101010
int n = 2; // 要反转的位
// 将a在第n位上反转
a ^= (1 << n);
// 打印结果
printf("Result: %u\n", a); // 输出结果,十进制表示为174
printf("Binary: ");
for (int i = 7; i >= 0; i--) {
printf("%d", (a >> i) & 1);// 输出结果,二进制表示为10101110
}
printf("\n");
return 0;
}
在结构体中,以位(bit)为单位的成员,称为位段或位域 位段本质是结构体的成员,可以通过数字指明它所占内存空间的大小(以bit为单位)
// eg: STM32中电源控制寄存器(PWR_CR)
struct PWR
{
unisgned int ldps : 1; -- 1bit
unsigned int pdds : 1; -- 1bit
unsigned int cwuf : 1; -- 1bit
unsigned int csbf : 1; -- 1bit
unsigned int pvde : 1; -- 1bit
unsigned int pls : 3; -- 3bit
unsigned int pvde : 1; -- 1bit
unsigned short d ; -- 16bit
};
struct Stu stu = {1,0,1,1,1,7,1};
// 应用:
// 方便给寄存器中的某些数据位设置数据
struct S
{
char a:2; // 2bit
unsigned char b:4;// 4bit
};
struct S s;
printf("%p\n",&s); // s是结构体变量,可以取地址
printf("%p\n",&(s.a)); // s.a是结构体成员位段a,不能对它取地址
struct S
{
char a : 2; // 2bit
unsigned char b : 4; // 4bit
unsigned char c : 5; // 5bit
};
struct S s = {1, 3};
// 赋值操作
s.a = 1; // a占2个bit位,并且是有符号数,它能被赋值的范围:-2,-1,0,1除此之外的都会报错
s.b = 10; // b占4个bit位,并且是无符号数,它的取值范围为:[0,15],如果取值是[-8,15],编译正常,只
是在-8输出时值为15
s.c = 20; // c占5个bit位,并且是无符号数,它的取值范围为:[0,31],如果取值是[-16,31],编译正常,
只是在-16输出时值为31
struct A
{
char a:7; //a在一个内存单元中,剩余一个bit位
char b:7; //b得从下一个内存单元开始,因为上一个内存单元剩余一个bit,放不下7个bit
char c:3; //c得从下一个内存单元开始,因为上一个内存单元剩余一个bit, 放不下3个bit
char d:3; //d和c可以在同一个内存单元中,因为c只占了3个还剩余5个bit,能够放下d的3个bit不用跨
内存单元
};
struct A
{
char a:1;
char b:8; //一个char占一个字节,最大分配8个bit位
char c:9; //一个char占一个字节,最大分配8个bit位,如果分配9个就是错误的,无法通过编译
};
struct A
{
char a:1;
char b:2; //a,b总共占3个字节,在一个内存单元中就可以存放,所以不用跨内存单元
char :0; //匿名成员,占0个字节
char c:2; //c不和a,b在一个字节(内存单元)中,而是在另外一个字节(内存单元)中
}
说明:如果成员c上面没有匿名成员,c就和a,b在同一个字节(同一个内存单元中)