一、位与(&)、位或(|)、异或(^)
1.位与运算(&):两位同时为1,结果才为1,否则为0
参加运算的两个数据,按二进制位进行“&”运算。
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
例如:3&5 即 0000 0011& 0000 0101 = 00000001 因此,3&5的值得1。
“与&运算”的特殊用途(负数按补码形式参加按位与运算)
(1)清零:如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
(2)取一个数的指定位:找一个数,对应X要取的位,该数的对应位为1,其余位为零,此数与X进行“与运算”可以得到X中的指定位。
例:设X=10101110, 取X的低4位,用 X & 0000 1111 = 00001110 即可得到;
int main()
{
//有符号的十六进制转化为十进制数
int a = 0xFF9C;
//清除符号位
int b = a & 0x7FFF;
//反码
int c = ~b;
//清除左边多余位
int d = c & 0x7FFF;
//加1
d = d + 1;
//符号位
int e = d * -1;
printf("e=%d\n", e); //e = -100
}
2.位或运算(|):只要有一个为1,其值为1,否则为0
参加运算的两个对象,按二进制位进行“或”运算。
运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;
例如:3|5 即 00000011 | 0000 0101 = 00000111 因此,3|5的值得7。
“或|运算”特殊作用(负数按补码形式参加按位或运算)
常用来对一个数据的某些位置1:找到一个数,对应X要置1的位,该数的对应位为1,其余位为零。此数与X相或可使X中的某些位置1。
例:将X=10100000的低4位置1 ,用X | 0000 1111 = 1010 1111即可得到。
3.异或运算(^):异值为1,否则为0
参加运算的两个数据,按二进制位进行“异或”运算。
运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;
即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
例如:3^5 即 0000 0011 | 0000 0101 = 00000110 因此,3^5的值为6。
异或运算”的特殊作用:
(1)使特定位翻转找一个数,对应X要翻转的各位,该数的对应位为1,其余位为零,此数与X对应位异或即可。
例:X=10101110,使X低4位翻转,用X ^0000 1111 = 1010 0001即可得到。
(2)与0相异或,保留原值 ,X ^ 00000000 = 1010 1110。
下面重点说一下按位异或,异或其实就是不进位加法,如1+1=0,,0+0=0,1+0=1。
例:(1)1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现
一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空
间,能否设计一个算法实现?
解法一、显然已经有人提出了一个比较精彩的解法,将所有数加起来,减去1+2+...+1000的和。
这个算法已经足够完美了,相信出题者的标准答案也就是这个算法,唯一的问题是,如果数列过大,则可能会导致溢出。
解法二、异或就没有这个问题,并且性能更好。
将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。
(2)一系列数中,除两个数外其他数字都出现过两次,求这两个数字,并且按照从小到大的顺序输出.例如 2 2 1 1 3 4.最后输出的就是3 和4,见如下代码:
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
#define N 1000010
int a[N];
int main()
{
int t;
scanf("%d", &t);
while(t--) {
int n;
scanf("%d", &n);
int x = 0;
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]); x ^= a[i];
}
int num1 = 0, num2 = 0;
int tmp = 1;
while(!(tmp & x)) tmp <<= 1;
cout<<tmp<<endl;
for(int i = 1; i <= n; i++)
{
if(tmp & a[i]) num1 ^= a[i];
else num2 ^= a[i];
}
printf("%d %d\n", min(num1, num2), max(num1, num2));
}
return 0;
}
4、左移运算符(<<)
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
例:a = a<< 2将a的二进制位左移2位,右补0,
左移1位后a = a *2;
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
5、右移运算符(>>)
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
操作数每右移一位,相当于该数除以2。
例如:a = a>> 2 将a的二进制位右移2位,
左补0 or 补1得看被移数是正还是负。
二、学习原码、反码、补码前的补充
1.机器数和真值
在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念.
2.机器数
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011 。
那么,这里的 00000011 和 10000011 就是机器数。
2、真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。
所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1
三、 原码、反码,、补码的基础概念和计算方法.
在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念.对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式.
1. 原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001 [-1]原 = 1000 0001
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
[1111 1111 , 0111 1111]==>[-127 , 127]
2. 反码
反码的表示方法是:
(2-1)正数的反码是其本身;
(2-2)负数的反码是在其原码的基础上, 符号位不变,其余各个位取反。
[+1] = [00000001]原 = [00000001]反 [-1] = [10000001]原 = [11111110]反
3. 补码
补码的表示方法是:
(3-1)正数的补码就是其本身;
(3-2)负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1).
[+1] = [00000001]原 = [00000001]反 = [00000001]补 (正数三者相同) [-1] = [10000001]原 = [11111110]反 = [11111111]补 (负数三者不同)