目录
1)位运算中“与”、“或”会造成信息丢失,他们是单向运算,不可逆运算。
一:按位与运算(&)
1.概念和运算规则
按位与运算是计算机中的一种基本的位运算,它对两个二进制数的相应位执行逻辑与操作。按位与运算的本质在于执行逻辑与(AND)操作。其规则是对于每一对相应的位,如果两个位都是1,则结果位为1;否则,结果位为0。比如:
A: 1 0 1 0 1 1 0 1
B: 0 1 1 0 1 0 1 1
--------------------
A&B: 0 0 1 0 1 0 0 1
2.按位与的优点和应用
1)优点
位运算是最靠近计算机底层的,所以计算效率特别高,尤其在数据量超大的情况下,优势很明显。
2)应用
①掩码操作
通过按位与运算,可以使用掩码来提取或保留一个数中的特定位。
比如:假设一个二进制数X= 1010 1011
a.如果我们想要提取该数的低四位,则可以与上一个二进制数 Y=0000 1111,即X&Y=1010 1011 & 0000 1111=0000 1011.
b.如果我们想要提取该数的第4位(从低位向高位开始数,即从右边开始数),则我们可以给X与上一个二进制的数0000 1000,即X&Y=0000 1000.
这个二进制数Y呢,也常被称作掩码。程序举例:
#include <stdio.h>
// 自定义函数,以二进制形式输出整数
void printBinary(unsigned short num) {
for (int i = sizeof(num) * 8 - 1; i >= 0; i--) {
printf("%d", (num >> i) & 1);
}
}
int main() {
unsigned short num = 0b1101101001011011; // 16位二进制数
unsigned short mask = 0b0000000011111111; // 用于提取低8位的掩码
unsigned short result = num & mask; // 按位与运算,提取低8位
printf("原始数: ");
printBinary(num);
printf("\n提取低8位的结果: ");
printBinary(result);
printf("\n");
return 0;
}
运行输出:
原始数: 1101101001011011
提取低8位的结果: 0000000001011011
②清零特定位
比如:假设一个二进制数X= 1010 1011
如果我们要想要将其第2位和第6位清零,则可将X与上一个二进制Y=1101 1101. 即X&Y=1000 1001 。
③判断奇偶性
任何一个数位与1,则1会在前面补上相应个0,然后和那个数进行位与计算,所以结果不是0就是1,因此,我们常常用一个数&1来判断一个数是奇数或偶数。
例如:1010 0110 & 1=1010 0110 & 0000 0001=0,则说明该数为偶数;
1011 1111 & 1=1011 1111&0000 0001 =1,说明该二进制数的最低位为1,则该数一定为奇数。
二:按位或运算(|)
1.概念和运算规则
按位或运算是对两个二进制数的每一位执行或运算,其结果的每一位是两个操作数对应位上值的逻辑或。这个运算符用于整数的每一位,从最低位(最右边)到最高位(最左边)。
让我们以一个简单的例子来说明按位或运算,假设有两个二进制数:
A 1010 (表示为 10 的二进制形式)
B 1100 (表示为 12 的二进制形式)
----------------------------------
A|B 1110 (表示为 14 的二进制形式)
2.按位或优点和应用
1)优点
靠近计算机底层的,所以计算效率特别高。还可以用二进制来标记某些符号。
2)应用
①设置特定位
可以使用按位或运算来将特定的二进制位设置为1,而保持其他位不变。这是通过将相应的位设置为1,其余位设置为0来实现的
②合并标志位
例如,如果有两个bool值的标志 flag1
和 flag2
,可以使用按位或运算将它们合并成一个标志
bool flag1 = true;
bool flag2 = false;
bool result = flag1 | flag2; // 合并成一个标志
通常,可以会和移位运算符号"<<",">>"来搭配使用,还没学移位运算符号的先看看后面。
例子1:
#include <iostream>
int main()
{
bool flag1 = true;
bool flag2 = false;
// 将两个标志合并成一个整数
int combinedFlags = (flag1 << 1) | flag2;
// 输出合并后的整数
std::cout << "Combined Flags: " << combinedFlags << std::endl;
return 0;
}
这个过程的具体步骤如下:
1.flag1 << 1: 左移一位,将 flag1 的值移到第二位。
2.(flag1 << 1) | flag2: 执行按位或运算,将 flag1 的值放入第二位,同时保留 flag2 的值在第一位。
例子2: leetcode上面的P2506. 统计相似字符串对的数目
这是一道简单题,但是很好的说明了位运算的优点
由于单词只由小写字母构成,所以可以把单词中字符是否出现的状态用int的低26位表示,不同单词经过位运算操作后得到相同的值,那么就可以说明这两个单词由相同类型的字符组成。
例如字符串word=“abcdef”,int型的数据是32位,则该字符串可以表示为
0000 0000 0000 0000 0000 0000 0011 1111
从最低位(最右边)到最高位(最左边),依次对应'a','b','c','d'......
#include<iostream>
#include<string>
using namespace std;
// 自定义函数,以二进制形式输出整数
void printBinary(unsigned int num) {
int cnt = 0;
for (int i = sizeof(num) * 8 - 1; i >= 0; i--) {
cout << ((num >> i) & 1);
cnt++;
if (cnt % 4 == 0 && cnt != 0)
{
cout<<" ";
}
}
}
int main()
{
string word = "abcdef";
unsigned int bit = 0;
for (auto& ch : word) bit |= (1 << (ch - 'a'));
printBinary(bit);
return 0;
}
程序输出:
0000 0000 0000 0000 0000 0000 0011 1111
三:左移 (<<
)和右移(>>)
1.概念和规则
将一个二进制数的所有位向左移动指定的位数,右侧用零填充。比如
1001<<1=0001 0010
1001<<2=0010 0100
1001<<3=0100 1000
1001>>1=0100
1001>>2=0010
1001>>3=0001
1001>>4=0000
2.移位运算的优点和应用
1)优点
①性能
移位运算通常比乘除法和其他数学运算更快。在底层硬件中,移位操作是一种基本的操作,执行速度相对较快。
②代码清晰度
移位运算符能够清晰地表达对二进制位的操作,使得代码更易读、更易理解。对于某些位级别的操作,使用移位运算可以提高代码的可读性。
③节省空间
在一些情况下,移位运算可以节省内存空间。例如,左移一位相当于乘以2,右移一位相当于除以2(对于奇数,最低位被舍去,比如3>>1=0001,不会有小数),这在某些算法和数据结构中可以用于优化空间利用。
2)应用
①乘除法的优化
移位运算可以用来优化乘法和除法操作。左移一位相当于乘以2的幂,右移一位相当于除以2的幂。这在需要对数字进行倍增或减小时是很有用的。在一些需要高性能的场景下,可以使用移位运算来替代乘法和除法,以提高代码执行效率。
②位操作
移位运算是进行位操作的一种有效手段。通过左移、右移和按位与、按位或、按位异或等操作,可以方便地对二进制数据的特定位进行设置、清除、检查等操作。
③位掩码
移位运算在创建位掩码(bitmask)时非常有用。通过将1左移至特定位置,可以创建一个具有特定位设置的二进制掩码。
④图形处理和位图操作
在图形处理和图像处理中,移位运算常用于像素值的处理和位图操作。例如,通过位移和按位操作,可以实现图像的缩放、旋转等操作。
四:按位取反(~)
对一个二进制数的每一位执行取反操作,将0变为1,将1变为0。
int a = 5; // 二进制表示:0101
int result = ~a; // 结果二进制表示:1010,即十进制的-6
五:按位异或(^)
1.概念和规则
按位异或 (^
) 是一种位运算符,它对两个二进制数的相应位执行异或运算。异或运算的规则如下:
1.相同位的异或结果为0
0 ^ 0 = 0
1 ^ 1 = 0
2.不同位的异或结果为1
0 ^ 1 = 1
1 ^ 0 = 1
举个栗子
10101010
^ 01010101
-----------
11111111
2.按位或的应用
1)交换两个变量的值
使用按位异或可以在不使用额外变量的情况下交换两个变量的值。这是因为按位异或具有自反性,即 a ^ b ^ b 等于 a。对任何数 a,与 0 进行按位异或运算的结果都是 a。位运算满足结合律。结合律指的是在一个运算符连续作用于三个操作数时,无论怎样加括号,得到的结果都是相同的。(a op b) op c 等于 a op (b op c),其中 op 代表任意位运算符。
int a = 5;
int b = 10;
a = a ^ b;
b = a ^ b;
a = a ^ b;
2)查找缺失的数字
在一组数字中,如果除了一个数字之外,其他数字都出现两次,可以使用按位异或来找到缺失的数字。这是因为相同的数字异或结果为0。
int findMissingNumber(vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num;
}
return result;
}
3)加密和解密
异或运算在加密和解密算法中也有广泛应用。在某些简单的加密算法中,通过异或运算可以实现简单的加密和解密操作。
六:位运算的性质和一些技巧
1.运算符的优先级
位运算符的优先级是相对较低的,低于算术运算符和关系运算符。以下是位运算符的一般优先级(从高到低):
①按位取反 ~
②左移 <<,右移 >>
③按位与 &
④按位异或 ^
⑤按位或 |
在编写代码时,为了确保表达式的清晰和正确,最好使用括号来明确运算顺序,提高代码可读性。
2.位运算的单向性
1)位运算中“与”、“或”会造成信息丢失,他们是单向运算,不可逆运算。
1 & x = y;
知道x 可以求出y,
知道y 不可能求出x。
当y = 0, x 可能等于0,2,6……
2) "左移"“右移”在数据溢出时也会信息丢失
3) “异或”不会造成信息丢失,而且具有很好的性质
正因为异或不会造成信息丢失,异或可以进行算杂的交换运算
比如:x ^ a = y,当a确定时,只需知道x,y两者中的一个,就可求出另外一个。
等式两边同时异或上a,则等式为x ^ a ^ a =y ^ a ,即 x=y ^ a;
3.运算规律
① 交换律
按位与 (&):
a & b 等于 b & a。
按位或 (|):
a | b 等于 b | a。
按位异或 (^):
a ^ b 等于 b ^ a
②结合律
按位与 (&):
(a & b) & c 等于 a & (b & c)。
按位或 (|):
(a | b) | c 等于 a | (b | c)。
按位异或 (^):
(a ^ b) ^ c 等于 a ^ (b ^ c)
4.一些技巧
1)输出int的最大值,最小值
int 最大值 (1<<31)-1 ,但是不能这么写,在C++里会溢出,要写出 (1ll<<31)-1,表示以long long类型的左移操作
int 最小值1<<31
2) 取int的绝对值
a ^ (a>>31) - (a>>31)
①右移位运算 (a >> 31):
对 a 进行算术右移31位,取得 a 的符号位,将其扩展到整个32位。
②按位异或运算 a ^ (a >> 31):
如果 a 是正数或零,这个操作会将所有位保持不变(因为右移出来的符号位是0,异或0得到原数);
如果 a 是负数,这个操作会将 a 的所有位取反。
③减去 (a >> 31):
对于正数或零,这个操作相当于减去0;
对于负数,由于之前异或操作的影响,相当于加上 a 的绝对值。
综合起来,这个表达式的效果是将带符号整数 a 转换为它的绝对值。这是通过先将 a 取反(如果是负数),然后加上 1(如果是负数)来实现的。
值得注意的是,这个表达式在负溢出的情况下可能会产生未定义的行为,因为右移负数的行为取决于具体的编译器实现。在实际应用中,通常会使用更加清晰和可靠的方式来取绝对值,比如使用条件表达式:a < 0 ? -a : a。
3)比较两个数是否相等
a ^ b ==0
4)判断符号是否相同
a ^ b >= 0
5)取出第i+1位
a & (1<<i)
6)i+1位 置1
a |=1<<i
7)i+1位 置0
a &=~(1<<i)
8)i+1位 反转
a ^ (1<<i)
9) 在对应i+1位,插入b的对应位
a |=1<<i; (a的bit位置1)
a & (b & 1<<i) (与b的bit位相与)
(置1后相与=置0后相或)
10) 保留最后i-1位
a & ((1<<i)-1)
11) 清零最后i-1位
a & ~((1<<i)-1)
12) 删除最后的1
a& (a-1) (可用于判断2的幂数)
13)其他
a & a = a
a | a = a
a ^ a = 0
a & 0 = 0
a | 0 = a
a ^ 0 = a
今天的学习笔记就写到这,最后,希望大家多多指教,文章有什么错误可在评论区说明。