作为一位刚刚入门的小菜狗,我经常好奇位操作符有些什么用呢??
通过一段时间的学习,我发现在一些问题上如果采用位操作符,处理问题的能力可以得到大幅提升。下面是我小小总结的,希望可以帮助大家更好的理解这些操作符,如果大家对于文章有更好的建议的话,也欢迎提出。
这是位操作运算符的基本定义,那我接下来给大家带来我的理解。希望对大家的理解有所帮助。(需要注意的是,这些操作符都是在二进制的形式下进行的)
操作符 &
它的功能是 1 &1 =1,1& 0 = 0,0 & 1 = 0,0 & 0 = 0.可以这样理解,有0 则0 ,无0 则1 .或者把它与操作符&&联系起来进行记忆。1代表真,0表示假,全真则真,有假则假。
操作符 |
它的功能是 1| 0 =1,1|1=1,0|0=0,0 | 1 = 1;可以这样理解,有一则1 ,无一则0 ,或者同样的办法把它与操作符|| 联系起来进行记忆,1代表真,0表示假,有真则真,全假则假
操作符^
它的功能是1 ^1 = 0,0^ 0=0,1^ 0 =1.相同为0 ,相异为一。
这个特性决定了,如果两个相同的数字进行异或,由于每个二进制位上的数字都相同,所以两者异或之后所有的二进制位的数全变为了0,故a^a = 0;如果a^0= 0,因为a对应位置上的1^0 = 1,0^0=0;我们发现在这个过程中,a的二进制位上的数字都没有发生改变。
同时它拥有交换律
结合律
(a^b)^c =a^(b^c);
a^a=0。
操作符~
就是0变为1 ,1变为0.
左移操作符<<
可能会有同学疑惑了,如果一个数字左移之后,这个数字本身是否会发生改变,答案是否定的,可以举个通俗的例子,如果 int i = 1; i+1的情况下只要没有赋值给它本身,则这个值就不会发生改变。
右移操作符>>
逻辑移位
算数移位
位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。
学完之后,想了半天,不知道它们有什么实际意义。
接下来是我通过一段时间的学习,发现的代码学习过程中一些妙用。这些东西是我在学习过程中,自己进行经验总结的,可能存在不足,望大家多多指正。
接下来的前面一部分是单个运用实例,后面是组合运用实例。
先来看看操作符 & 。
-
由于它的特点,我们可以利用这个特性,可以实现屏蔽某些特定位置的二进制位。(保留某些二进制位)
-
判断一个数据的某一位是否为1或者0,我们可以利用这个特点实现对一个数据是奇偶性,
-
任何一个正数在二进制的情况下减一,我们会发现这个数(二进制的情况)最右边的1向右移动了一位,如若我们这时进行操作 n= n&(n-1),我们发现除了只有两个相对应的位置数据不同,其余位置的数据还保持一样,我们通过画图分析,发现这个这个语句的功能去除一个数字(二进制)的最右边的一个一。所以这个语句有什么作用,我们可以利用这个语句来计算一个数字的二进制中的一。
如一些题 ,给出一个正数字,统计其二进制中一的个数。
- 先来观察第一个
- 然后第二个
int main()
{
int a = 0;
scanf("%d", &a);
int i = a & 1;
if (i == 1)
{
printf("奇数\n");
}
else
{
printf("偶数\n");
}
return 0;
}
- 第三个
具体的分析就放在了代码注释中,下同。
int main()
{
int a = 1235;
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)
{
if (a % 2 == 1)
{
count++;
}
a = a / 2;
if (a == 0)
{
break;
}
}
printf("%d\n", count);
return 0;
}
//常规方法
// 新方法
int main()
{
int a = 1235;
int i = 0;
while (a != 0)
{
a = a & (a - 1);
i++;
}
printf("%d\n", i);
return 0;
}
再来看看操作符 | 。
- 我们可以将某个数据的特定位置的数字置为1.
接下来是操作符~
我们在一些题目中,经常要实现多组输入,那我们通常可是通过这种方法进行实现。
我们先来了解一下这scanf函数,大家可能表示这个不就是输入函数,那我们看一下scanf的文档,发现它是有返回类型的,那我们在这里稍微跑一下题,带大家了解一下这个函数,
发现如果返回值为 EOF(end-of-file)时,这个函数就结束了,如果读取成功,则返回读取成功元素的个数。
回到正题,EOF的值其实为 -1 ,
故我们可以这样实现多组输入
int main()
{
int i = 0;
while(~scanf("%d", &i))//while (scanf("%d", &i) != EOF)
{
printf("%d\n", i);
}
return 0;
}
接下来是操作符^
-
还有啥作用呢??如果一个题要求我们不准创建临时变量,来交换两个数的值,你会有多少种方法。
目前的我有两种方法,后续有新的方法,我会更新给大家的。
第一种方法,
a= a+b;b = a - b;a= a-b;
第二种
利用操作符异或,我们发现两个可以利用异或进行操作。
-
我们也可以寻找一个数组中的数字,这个数组有什么特点呢?这个数组里面存储的数,只有一个数在数组中的个数为奇数个。其他的数在数组中的个数为偶数个。例如[1,1,2,2,,3]
或者[1,1,1,1,2,2,3],我们的目标就是找出那个为奇数个数的数字(这个可以理解位寻找单个数)。就可以利用这个操作符。
- 第一个
int main()
{
int a = 10;
int b = 20;
//交换数值第一种方式
//1.最常用的方法
/*int tmp = a;
a = b;
b = tmp;*/
//不创建临时变量
//1.第一种方法
/*a = a + b;
b = a - b;
a = a - b;*/
//2.第二种方法
a = a ^ b;//1.
b = a ^ b;//2.
a = a ^ b;//3.
//我在这里给大家分析一下这个方法
//进行1.之后 a = a ^ b ,进行第二步(2.)操作(进行替换),b = a ^ b = (a ^ b) ^b = a ^ b ^ b = a ^ 0 = a;
//第三步(3.)也是同理,a = a ^ b = (a ^ b) ^ a = a ^ a ^ b = 0 ^ b = b
//这里的思想主要是替换,大家要明白a b 在什么时候代表的是什么?
//利用异或方法,大家可以利用一些简单的数字进行计算 ,印象更深刻
return 0;
}
- 第二个
int main()
{
int a[5] = { 1,1,2,2,3 };
int sum = 0;
for (int i = 0; i < 5; i++)
{
sum = sum ^ a[i];
}
printf("%d\n", sum);
}
接下来是操作符左移和右移。
我们发现左移是*2的效果,右移的话分为两种情况。就具体而看。
接下来进行复合运算。
我们需要将一个int类型的数据的奇数位和偶数位进行一个交换。大家可以自行敲一下,然后在看一下我的代码。常规方法我就不写了哈。(思路在代码中)
int main()
{
int i = 0;
scanf("%d", &i);
int sum = ((i >> 1)&0x 00 00 55 55) + ((i << 1)&0x aa aa aa aa);
//假设 i = 00000000 00000000 11001011 01101010(二进制表示)
// i>>1 针对的是 i 的偶数位 ,右移后需要利用&操作符将原来的偶数位进行置零,
// 00000000 00000000 01100101 10110101 右移之后
// 01010101 01010101 01010101 01010101 然后与此数进行& 此数为 0x 00 00 55 55
// 00000000 00000000 01000101 00010101 通过观察可以发现i的偶数位移动到了奇数位,并且使奇数位 置为0
// 00000000 00000001 10010110 11010100 左移之后
// 10101010 10101010 10101010 10101010 然后于此数 此数为 0x aa aa aa aa
// 00000000 00000000 10000010 10000000 通过观察可以发现i的奇数位移动到了奇数位,并且使偶数位 置为0
//然后把奇数位与偶数位
printf("%d\n", sum);
return 0;
}
然后呢,我们在来解决寻找单个数的plus版,数组中有两个数在数组中的个数为奇数个。具体分析过程我就写入代码中了。(注释部分)
int main()
{
int arr[10] = { 1,1,2,2,3,3,4,4,5,6 };
//以这个数组为例,我们发现 5 6这两个数字是我们所需要的。
//我们发现这次有两个单个的,那我们这个怎么进行往后进行呢??
//我们可不可以将这个数组分为两组,将两个单个的数字分开在两个组里面,
// 当然对这个数组还有啥要求呢??
//成对的数字应该被分到同一组里面,不然无法进行消去。
//就以这个数组为例,可以分为
// {1,1,3,3,5} {2,2,4,4,6}或者
// {1,1,5} {2,2,3,3,4,4,6}都可以
// 只要满足我们的两个条件 1. 两个单个的数字分到两组, 2.成对的数字就放在一组
//那么我们应该怎样将这两个单个的数字分到两个数组呢
//那我们就要想到我们的操作符,哪一个操作符可以将两个不同的数字分开
//没错就是我们的异或
//先将这两个数字异或,找到二进制位上不同的数
// 例如异或之后的结果为 1010 ,我们根据这个结果可以推断出 ,这两个数字(二进制表示下)第二位与第四位不同
// 可以作为将两个数字分开的一个依据。
// 但是成对的数字怎样分到两组呢???数字相同,二进制位也相同。
// 我们需要注意的是,我们只是需要将成对的数字分在两个数组里,至于每个数字里面分多少,我们不太关注,因为最后通过运算消去了,
// 只需要保证成对的数字分在一起。那怎么分呢???什么作为分组的依据呢???随缘分法吗???
// 其实我们可以依据前面 单独的数字的分组依据, 将这个作为依据进行分组。然后这就是大概思路
//
//
//
// 来来,我们整理一下思绪 ,然后上手,
// 1.找到二进制位中的不同的位数
// 2. 按照这个不同的位数,将整个数组分成两个数组。
// 需要注意的是,在2. 这个过程中,那两个 单的数字 已经分到两个边,顺带也将成对的数字分组,虽然不能保证两边数组元素
// 个数相同,但是能保证成对的数字都放在了一边。
//寻找不同位数
int i = 0;
int sum = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
sum ^= arr[i];
}
//上面的话 已经将所有的数字已经异或。
//接下来寻找sum中的 1 的位置 ,找到一个就可以。因为异或的话,1表示两个元素不一样 ,可以作为我们分组的依据。
i = 0;
while (((sum >> i) & 1) != 1)
{
i++;
}
//假如sum为下
// sum = 00000000 00000000 00000000 00101000
// 1 00000000 00000000 00000000 00000001
// 00000000 00000000 00000000 00000000
//两者就进行&运算 ,当运算结果为0 时,证明此时两个数字这个二进制位相同,则将sum右移一位,
// 依次类推
//当 ((sum >> i) & 1) = 1 ,此时sum右移了i位,证明两个数字的第i位数据不同 ,可以将此位作为一个分组的依据。
// 5 101
// 6 110
// 011
int arr1[10] = { 0 }, a1 = 0;//由于我们不知道两个数组每个数组分配多少元素,所以呢,尽可能的创建大的数组
int arr2[10] = { 0 }, a2 = 0;//
int a = 0;
for (a = 0; a < sz; a++)
{
if ((arr[a] >> i) & 1)
{
arr1[a1] = arr[a];
a1++;
}
else
{
arr2[a2] = arr[a];
a2++;
}
}
//通过上面的分类,已将数字分到了两个数组,接下来分别进行异或
i = 0;
int x = 0;
for (i = 0; i < a1; i++)
{
x = x ^ arr1[i];
}
printf("%d\n", x);
i = 0;
x = 0;
for (i = 0; i < a2; i++)
{
x = x ^ arr2[i];
}
printf("%d\n", x);
return 0;
}