NO.37十六届蓝桥杯备战|位运算的应用|保留获取指定位|指定位设置位1或0|反转指定位|最右边的1变为0|只保留最右边的1|异或|运算符优先级结合性(C++)

判断奇数和偶数

所有偶数的 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表⽰没有执⾏权限
    ![[Pasted image 20250313104811.png]]

假设我们有⼀个权限标志位,存储在⼀个变量中,我们可以通过按位与操作来检查某个特定位是否被设置。

#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

![[Pasted image 20250313105349.png]]

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:保留
    ![[Pasted image 20250313152941.png]]

可以提取状态代码和错误标志来判断传感器的状态

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);
    }
};
应⽤场景
  1. 位图算法:
    分配和释放资源
    位图(bitmap)是操作系统、⽂件系统等经常使⽤的数据结构,⽤于⾼效管理资源,如内存块、磁盘块等。通过将最右边的1变为0,可以快速释放资源。假设位图⽤于管理内存块,每个位表⽰⼀个内存单元的状态:1表⽰已分配,0表⽰未分配。要释放最右侧已分配的块,只需将最右边的1变为0。
  2. 计数算法:
    统计⼆进制数中的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表⽰空闲),我们希望快速查找到最右边的被占⽤的位置就可以使⽤

异或的巧⽤

异或运算符的特点:

  1. x ^ x = 0 ,两个相同的数字异或结果是0;
  2. 0 ^ x = x , 0 和 x 异或还是 x ;
  3. 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个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序

优先级

优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的

结合性

如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符( = )

  • 圆括号( () )
  • ⾃增运算符( ++ ),⾃减运算符( – )
  • 单⽬运算符( + 和 - )
  • 乘法( * ),除法( / )
  • 加法( + ),减法( - )
  • 关系运算符( < 、 > 等)
  • 赋值运算符( = )
    由于圆括号的优先级最⾼,可以使⽤它改变其他运算符的优先级

![[Pasted image 20250313205056.png]]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值