判断奇数和偶数
所有偶数的 2 进制表⽰中,最低位⼀定是 0 ,所有奇数的 2 进制表⽰中,最低位⼀定是 1 。所以将⼀个数字与 1 进⾏按位与运算,即可判断这个数是奇数还是偶数。
(x & 1) == 1, 说明x是奇数
(x & 1) == 0, 说明x是偶数
B2037 奇偶数判断 - 洛谷
#include <iostream>
using namespace std;
int n;
int main()
{
cin >> n;
if ((n & 1) == 1)
cout << "odd" << endl;
else
cout << "even" << endl;
return 0;
}
保留⼆进制位中的指定位
有时候需要从⼀个整数 x 的 2 进制中取出某个位或者某⼏个位,使取出的位置上保留原来的值,其他位置为 0 ,这时候可以使⽤⼀个值 m ,使 m 的 2 进制位中对应取出的位置为 1 ,其他位为 0 。
然后使这两个数按位与( x & m )即可。
⽐如:取出x=
0010101
1
2
00101011_{2}
001010112 的最低1位,则只需要将x=
0010101
1
2
00101011_{2}
001010112 与m=
0000000
1
2
00000001_{2}
000000012 (末尾 1 位是1 ,其余位为 0 )进⾏按位与运算。
00101011
& 00000001
------------
00000001
再⽐如:取出x= 0010101 1 2 00101011_{2} 001010112 的最低4位,则只需要将x= 0010101 1 2 00101011_{2} 001010112 与m= 0000111 1 2 00001111_{2} 000011112 (末尾 4 位是1 ,其余位为 0 )进⾏按位与运算。
00101011
& 00001111
------------
00001011
再⽐如:取出x= 0010101 1 2 00101011_{2} 001010112 的低 2~4 位,则只需要将x= 0010101 1 2 00101011_{2} 001010112 与m= 0000111 0 2 00001110_{2} 000011102 (低 2~4 位是 1 ,其余位为 0 )进⾏按位与运算。
00101011
& 00001110
------------
00001010
应⽤场景
⽐如,在权限管理中,系统常⽤⼀个整数的不同⼆进制位来表⽰不同的权限,⽐如:
- 位0:读权限(Read),设置为1表⽰有读权限,0表⽰没有读权限
- 位1:写权限(Write),设置为1表⽰有写权限,0表⽰没有写权限
- 位2:执⾏权限(Execute),设置为1表⽰有执⾏权限,0表⽰没有执⾏权限
假设我们有⼀个权限标志位,存储在⼀个变量中,我们可以通过按位与操作来检查某个特定位是否被设置。
#include <cstdio>
#define READ_PERMISSION 0x01 // 0001,表⽰读权限
#define WRITE_PERMISSION 0x02 // 0010,表⽰写权限
#define EXECUTE_PERMISSION 0x04 // 0100,表⽰执⾏权限
int main()
{
int permissions = 0x03; // ⼆进制: 0011,具有读和写权限
if (permissions & READ_PERMISSION)
{
printf("阅读权限被设置\n");
}
else
{
printf("阅读权限未被设置\n");
}
if (permissions & WRITE_PERMISSION)
{
printf("写权限被设置\n");
}
else
{
printf("写权限未被设置\n");
}
if (permissions & EXECUTE_PERMISSION)
{
printf("执⾏权限被设置\n");
}
else
{
printf("执⾏权限未被设置\n");
}
return 0;
}
获取⼆进制中的指定位
当我们需要获取⼀个整数x的⼆进制中第 i 位(从低到⾼,以最低位为第 0 位)是1还是0的时候,我们可以对 x 做这样的运算: (x >> i) & 1 ,如果结果是 0 ,表⽰第 i 位是 0 ,如果结果是 1 ,表⽰第 i 位是 1 。
⽐如:确定x=
0010101
1
2
00101011_{2}
001010112 的第3位的值,只需要将x=
0010101
1
2
00101011_{2}
001010112 右移 3 位,第 3 位就到最低位,然后和 1 进⾏按位与运算即可。
x: 00101011
右移三位后:
00000101
& 00000001
-----------
00000001
190. 颠倒二进制位 - 力扣(LeetCode)
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
int i = 0;
uint32_t ret = 0;
for (i = 0; i < 32; i++)
{
//获取n中的一个2进制位
uint32_t b = ((n >> i) & 1);
//移动这一位到合适的位置,然后存放在ret中
ret |= (b << (31 - i));
}
return ret;
}
};
应⽤场景
⽐如:在嵌⼊式系统中,传感器通常通过寄存器返回数据。某些寄存器可能包含多个信息字段,例如状态标志、错误代码等。这些字段往往位于寄存器的特定位中,需要通过获取指定位的值来读取。
假设有⼀个传感器状态寄存器,其中:
- 位0-2:传感器状态代码
- 位3:是否存在错误
- 位4-7:保留
可以提取状态代码和错误标志来判断传感器的状态
unsigned char sensorRegister = 0x0B; // ⼆进制: 00001011
//获取状态代码
sensorRegister & 0x07; //0111
//获取错误标志
(sensorRegister >> 3) & 0x1;
将指定⼆进制位设置为1
有时候需要将⼀个整数 x 的⼆进制表⽰中的某⼀位(⼏位)设置为 1 ,其余位置保留原值,则可以使⽤另⼀个数 m ,使 m 的⼆进制上对应位置为 1 ,其余位置为 0 。然后使两个数进⾏按位或运算( x | m ),即可得到想要的数。
⽐如:将x=
0010101
1
2
00101011_{2}
001010112 的低 4 位置为 1 ,其余位保持原来的值不变。只需要将x=
0010101
1
2
00101011_{2}
001010112 与m=
0000111
1
2
00001111_{2}
000011112 (低 4 位为 1 ,其余位为 0 )进⾏按位或运算。
x: 00101011
| 00001111
--------------
00101111
当然也可以只设置 x ⼆进制中的某 1 位,也就是将 x ⼆进制中的第 i 位(从低到⾼,以最低位为
第 0 位)置为 1 ,则可以进⾏下⾯的运算: x |= (1<<i) ;
⽐如:将x=
0010101
1
2
00101011_{2}
001010112 的第 4 位置为 1 ,其余位保持原来的值不变。只需要将x=
0010101
1
2
00101011_{2}
001010112 与m=
0001000
0
2
00010000_{2}
000100002 (第 4 位为 1 ,其余位为 0 )进⾏按位或运算。
x: 00101011
| 00010000 //这个数字可以通过 1<<4得到
------------
00111011
476. 数字的补数 - 力扣(LeetCode)
class Solution {
public:
int findComplement(int num) {
int i = 0;
int ret = 0;
while (num)
{
int b = num & 1;
if (b == 0)
{
ret |= (1 << i);
}
num >>= 1;
i++;
}
return ret;
}
};
应⽤场景
在嵌⼊式开发的时候,假设我们要控制⼀个LED灯的开关,某⼀个寄存器的第2位⽤于控制这个LED灯的开关。我们可以通过将该位设置为1来点亮LED
#include <cstdio>
#define LED_CONTROL_BIT 0x04 // 00000100,第2位控制LED
int main()
{
unsigned char Register = 0x00; // 初始所有位为0
Register |= LED_CONTROL_BIT; // 打开LED
printf("Register: 0x%02X\n", Register); // 输出: 0x04
return 0;
}
将指定⼆进制位设置为0
有时候需要将⼀个整数 x 的⼆进制表⽰中的某 1 位设置为 0 ,其余位置保留原值。也就是将 x ⼆进制中的第 i 位(从低到⾼,以最低位为第 0 位)置为 0 ,其他位保持不变,则可以进⾏下⾯的运算: x &= ~(1<<i) ;
⽐如:将x=
0000101
1
2
00001011_{2}
000010112 的第 3 位置为 0 ,其余位保持原来的值不变。只需要将x=
0000101
1
2
00001011_{2}
000010112 与m=
1111011
1
2
11110111_{2}
111101112 (第 3 位为 0 ,其余位为 1 )进⾏按位与运算。这⾥的 m 可以通过~(1<<3) 得到
x: 00001011
& 11110111 //这个数字可以通过 1<<3,后然后按位取反得到
------------
00000011
当然也可以将连续的⼏位设置为 0
应⽤场景
在嵌⼊式开发的时候,假设我们要控制⼀个LED灯的开关,某⼀个寄存器的第2位⽤于控制这个LED灯的开关。我们可以通过将该位设置为0来关闭LED
#include <cstdio>
#define LED_CONTROL_BIT 0x04 // 00000100,第2位控制LED
// 11111011
int main()
{
unsigned char Register = 0x04; // 初始状态为⾼电平,LED亮
Register &= ~LED_CONTROL_BIT; // LED灭
printf("Register: 0x%02X\n", Register ); // 输出: 0x00
return 0;
}
反转指定⼆进制位
有时候需要将⼀个整数 x 的⼆进制表⽰中的第 i 位反转(从低到⾼,以最低位为第 0 位),也就是原来是 1 的变成 0 ,原来是 0 的变成 1 。则可以使⽤另⼀个数 m ,使得 m 的⼆进制中第 i 位为1 ,其余位置为 0 。然后令两个数进⾏按位异或运算( x ^ m ),即可得到想要的数。
⽐如:将x=
0010101
1
2
00101011_{2}
001010112 的第 3 位反转,其余位保持原来的值不变。只需要将x=
0010101
1
2
00101011_{2}
001010112 与m=
0000010
0
2
00000100_{2}
000001002 (第 1 位为 1 ,其余位为 0 )进⾏按位异或运算。
00101011
^ 00000100
------------
00101111
应⽤场景
在嵌⼊式开发的时候,假设我们要控制⼀个LED灯的亮灭,某⼀个寄存器的第2位⽤于控制这个LED灯的开关。现在想每次反转引脚状态可以切换LED的亮灭
#include <cstdio>
#define LED_CONTROL_BIT 0x04 // 00000100,第2位控制LED
int main()
{
unsigned char Register = 0x00; // 初始状态为低电平,LED灭
//00000000
//00000100
//00000100
printf("Register: 0x%02X\n", Register); // 输出: 0x00
Register ^= LED_CONTROL_BIT; // 反转第2位, 第⼀次切换,LED亮
printf("Register: 0x%02X\n", Register); // 输出: 0x04
//00000100
//00000100
//00000000
Register ^= LED_CONTROL_BIT; // 反转第2位, 第⼆次切换,LED灭
printf("Register: 0x%02X\n", Register); // 输出: 0x00
return 0;
}
将⼆进制中最右边的1变为0
有时候,我们需要将⼀个整数 x 的⼆进制表⽰中最右边的 1 变为 0 ,这时候就可以使⽤ x & (x-1)
来得到想要的数字。
⽐如:将x=
0010110
0
2
00101100_{2}
001011002 的⼆进制表⽰中最右边的 1 变成 0 ,其余的⼆进制位保持不变。只需要将x=
0010110
0
2
00101100_{2}
001011002 与 x-1 的⼆进制,进⾏按位与的运算即可。
00101100 (x)
& 00101011 (x-1)
-----------------
00101000
这种运算通常应⽤到求⼀个数的⼆进制序列中有⼏个 1 ,因为 x = x & (x-1)
这种运算,每计算⼀次就会将 x 的⼆进制表⽰中最右边的 1 置为 0 ,那么不断的执⾏ x = x & (x-1)
这样的运算,就会将 x 的⼆进制中的 1 逐个都置 0 ,这时候 x 也就变成了 0 ,当 x 变成 0 之前,这个运算被执⾏多少次就说明x的⼆进制中有多少个 1 。
191. 位1的个数 - 力扣(LeetCode)
class Solution {
public:
int hammingWeight(int n) {
int cnt = 0;
while (n)
{
n &= n - 1;
cnt ++;
}
return cnt;
}
};
class Solution {
public:
int hammingWeight(int n) {
int cnt = 0;
for (int i = 0; i < 32; i++)
{
if((n >> i) & 1 == 1)
cnt++;
}
return cnt;
}
};
231. 2 的幂 - 力扣(LeetCode)
class Solution {
public:
bool isPowerOfTwo(int n) {
return (n >= 1) && ((n & (n-1)) == 0);
}
};
应⽤场景
- 位图算法:
分配和释放资源
位图(bitmap)是操作系统、⽂件系统等经常使⽤的数据结构,⽤于⾼效管理资源,如内存块、磁盘块等。通过将最右边的1变为0,可以快速释放资源。假设位图⽤于管理内存块,每个位表⽰⼀个内存单元的状态:1表⽰已分配,0表⽰未分配。要释放最右侧已分配的块,只需将最右边的1变为0。 - 计数算法:
统计⼆进制数中的1的个数
在统计⼆进制数中1的个数时,通过不断将最右边的1变为0,可以显著优化计算的速度。这种⽅法被称为Brian Kernighan算法,⼴泛⽤于计算机科学的各种优化场景。
只保留⼆进制中最右边的1
有时候,我们需要将⼀个整数 x 的⼆进制表⽰中最右边的 1 保留下来,其他位都置为 0 ,那么 x & -x 就可以得到想要的数字。⽐如:将x= 0010110 0 2 00101100_{2} 001011002 的⼆进制表⽰中最右边的1保留,其余的⼆进制位全部置为 0 。只需要将x= 0010110 0 2 00101100_{2} 001011002 与 -x 的⼆进制补码,进⾏按位与的运算即可
x的⼆进制:00101100
-x的原码 :10101100
-x的反码 :11010011
-x的补码 :11010100
00101100(x)
& 11010100(-x)
---------------
00000100
也可以来解2的幂这个题⽬
bool isPowerOfTwo(int n)
{
return (n > 0) && (n & -n) == n;
}
因为 2 的幂次⽅的数字的⼆进制表⽰中只有 1 个 1 , n & -n 会将这个 1 保留下来,其实还是 n ,所有如果 (n & -n) == n , n 就是 2 幂次⽅数
应⽤场景
位图算法:快速查找最右边的1,假设我们有⼀个位图,使⽤每个位表⽰⼀个资源的状态(1表⽰正在使⽤,0表⽰空闲),我们希望快速查找到最右边的被占⽤的位置就可以使⽤
异或的巧⽤
异或运算符的特点:
- x ^ x = 0 ,两个相同的数字异或结果是0;
- 0 ^ x = x , 0 和 x 异或还是 x ;
- a ^ b ^ a == a ^ a ^ b 异或是⽀持交换律的;
交换两个整数的值
#include <iostream>
using namespace std;
int main()
{
int a = 0;
int b = 0;
cin >> a >> b;
cout << "交换前:a = " << a << " b = " << b << endl;
a = a ^ b; //a' = a ^ b
b = a ^ b; //b' = a' ^ b == a ^ b ^ b == a
a = a ^ b; //a = a' ^ b' == a ^ b ^ a == b
cout << "交换后:a = " << a << " b = " << b << endl;
return 0;
}
使⽤异或交换两个数的值,只能适⽤于整形类型,因为异或运算仅适⽤于整型类型
136. 只出现一次的数字 - 力扣(LeetCode)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int r = 0;
for (auto x : nums)
{
r ^= x;
}
return r;
}
};
268. 丢失的数字 - 力扣(LeetCode)
class Solution {
public:
int missingNumber(vector<int>& nums) {
int r = 0;
for (auto x : nums)
{
r ^= x;
}
for (int i = 0; i <= nums.size(); i++)
{
r ^= i;
}
return r;
}
};
操作符属性
C/C++语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序
优先级
优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的
结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符( = )
- 圆括号( () )
- ⾃增运算符( ++ ),⾃减运算符( – )
- 单⽬运算符( + 和 - )
- 乘法( * ),除法( / )
- 加法( + ),减法( - )
- 关系运算符( < 、 > 等)
- 赋值运算符( = )
由于圆括号的优先级最⾼,可以使⽤它改变其他运算符的优先级