位运算在算法的编写,程序的简化中有着极其重要的作用。
首先我们来了解以下什么是位运算(以C语言为例)
位运算简介
位运算是基于二进制来进行计算的,而一个数字以二进制在计算机进行存储的时候都会以补码的方式进行存储。此处涉及的原码 反码 补码 我就不做赘述了,读者可以自己去查查计算机基础类的书,一般都会有介绍。
接下来我们对位运算符进行介绍。
位运算分为 6 种,它们是:
按位与 按位或 按位异或 按位取反 左移运算 右移运算
& | ^ ~ << >>
下来我们对这几个运算符进行解释(位运算的操作数是整数类型或字符型.)
我们以char举例 char在内存中占一个字节 0000 0000 范围是 -128 – 127
我们做出如下定义
char a=5;
char b=6;
我们对这两个数分别做不同的位运算
5的二进制存储代码是 0000 0101
6的二进制存储代码是 0000 0110
0000 0101
0000 0110
& 进行运算时比较相同的位数 特点是全1为1 有0为0.
0000 0101
0000 0110
0000 0100 结果是4
| 进行运算时比较相同的位数 特点是有1为1 全0为0.
0000 0101
0000 0110
0000 0111 结果是7
^进行运算时比较相同的位数 特点是不同为1 相同为0
0000 0101
0000 0110
0000 0011 结果是3
~是单目运算符(单目意为只对一个对象进行操作)
~进行运算时会将二进制中的0变成1 ,1变成0
0000 0101
1111 1010
此处需要注意的一个坑是计算机中负数的存储 负数进行存储的时候会对原码取反加1
因此 1111 1010 这个数其实代表的二进制数是是多少呢?
我们需要对它进行减一取反
1111 1010
1111 1001 减一后的值
1000 0110 取反后的值
结果是-6
有人可能会疑惑为什么第一个1没有变呢 这个在这里就先不做解释了 感兴趣的同学可以自己去了解。
<<和>>运算符
左移运算将数对应的二进位全部向左移动若干位,高位丢弃,低位补 0。左移运算的运算符为 <<。举个例子,将数字 5 左移 1 位,其实是将数字 5 对应的二进制 0000 0101 中的二进位向左移动 1 位
右移运算将数对应的二进位全部向右移动若干位。对于左边的空位补的是符号位。右移运算的运算符为 >>。举个例子,将数字5右移 1 位,其实是将数字 80 对应的二进制 0000 0101 中的二进位向右移动 1 位
#include<stdio.h>
#include<stdlib.h>
int main()
{
char a = 5;
char b = 6;
printf("%d\n",a&b);
printf("%d\n",a|b);
printf("%d\n",a^b );
printf("%d\n",~a);
printf("%d\n",a<<1);
printf("%d\n",a>>1);
system("pause");
return 0;
}
运行结果
强化训练
1.编写函数:
unsigned int reverse_bit(unsigned int value);
这个函数的返回值是value的二进制位模式从左到右翻转后的值。
思路 我们可以创建两个变量 end进行保存 mid当作’中间人‘
利用中间人 从value的最低位进行保存 每保存一次 我们对value进行>> 对end<<
将mid赋初值为0,通过|运算来读取value末位是否为零 再通过|存入end
代码如下
#include<stdio.h>
#include<stdlib.h>
unsigned int reverse_bit(unsigned int value)
{
int mid = 0;
int end = 0;
for (int i=1;i<32;i++)
{
mid = value & 1;//取出末位的值!
end |= mid; // | 有一为一 ret = ret|a 为了留下a中的一 所以用了逻辑或运算符!
value = value >> 1;
end =end << 1;
}
return end;
}
int main()
{
int a = 34;
reverse_bit(a);
printf("%d\n",reverse_bit(a));
system("pause");
return 0;
}
2.输入一个整数数组,实现一个函数,
来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,
所有偶数位于数组的后半部分
思路 这其实是一种排序的 但是是对奇偶的一种排序
首先我们利用位运算进行奇偶的一个判断 a[i]&1 奇数得1 偶数得零
我们从第一个元素开始 如果是偶数我们将它与最后一个元素进行交换
然后对数组长度减一 (对定义出的长度变量减一)
继续从当前元素开始循环
直到某次循环没有调用15324
#include<stdio.h>
#include<stdlib.h>
void oddEven(int* a,int len)
{
int mid=len-1;
for (int i=0;i<mid;i++)
{
if ((a[i]&1)==0)//判断交换
{
int miid = 0;
miid = a[mid];
a[mid] = a[i];
a[i] = miid;
mid--;
if ((a[i]&1)==1)//优化减少运行次数
{
i++;
}
}
}
}
int main()
{
int a[] = { 1,2,3,4,5 };
int len = 5;
oddEven(a, len);
for (int i=0;i<len;i++)
{
printf("%d\n",a[i]);
}
system("pause");
return 0;
}
3.编程实现:
一组数据中只有一个数字出现了一次。其他所有数字都是成对出现的。
请找出这个数字。(使用位运算)
思路
相同为零 不同为一 这是异或的特点 因此一组数中有相同的数时会异或为零
因为不用考虑运算顺序 因此最后的返回值会是一个 只出现一次的值!
#include<stdio.h>
#include<stdlib.h>
int main() {
int a[] = { 1,2,3,5,1,2,3 };
int len = sizeof(a) / sizeof(a[0]);
int t = a[0];
for (int i = 1; i < len; i++)
{
t = t ^ a[i];
}
printf("%d\n", t);
system("pause");
return 0;
}