一,位操作与逻辑操作
位操作不同于逻辑操作。逻辑操作是一种整体操作,而位操作是针对内部数据位补码的操作。逻辑操作只有真假,位操作只有0和1。
运算符如下:
二,数据的二进制表示
1,八位二进制的补码
2,二进制打印
功能:显示二进制补码
三,位操作
1,位于(&)
x & 1 = x; x & 0 = 0;
- 1
参考用途:在某些位保持不变的情况下,其余位置为0。
2,位或(|)
x | 1 = 1; x | 0 = x;
- 1
参考用途:某些位置不变,其余位为1。
3,位取反(~)
参考用途:间接的构造某个特别的数(如最大有符号正数),以增强程序的可读性。
4,位异或(^)
参考用途:某些位保持不变,其余位取反。
5,位左移(<<)
用法:x << n (n表示左移的位数)
规则:使操作数的各位左移,低位补0,高位溢出。
例:
5<<2=20
0101 <<2 = 010100
6,位右移(>>)
用法:x >> n
规则:
使操作数的各位右移,移出的低位被舍弃。
对于高位而言,当是无符号数或有符号正数时,高位补0,当是负数时,则取决于所使用的系统:补0的为“逻辑右移”,补1的为“算术右移”。
例:
5 >> 2 =1
0101 -> 0001
20 >> 2 =5
10100 -> 0101
四,应用
1,掩码
掩码就是掩盖一些东西,留下一些东西。
2,功能
MASK=1<<1;
flag=0x96;
MASK -> 0000 0010
flag -> 1001 0110
& -> 0000 0010
打开位(使某位置1)flag |= MASK
- 关闭位(使某位置0)
flag &= ~MASK
- 转置位(位反转)
flag ^= MASK
- 查看某一位的值
(flag & MASK) == MASK ?1:0;
3,遮罩码的生成
int mask = 0;
//假设要生成一个第3-6位的遮罩码;
int mask = (1<<6) | (1<<5) | (1<<4) | (1<<3) ;
/*真实过程如下:
0100 0000
| 0010 0000
| 0001 0000
| 0000 1000
= 0111 1000
*/
//////////////////////////////////////////////
int mask = 0;
for(int i=6; i>2; i–)
{
mask |= (1<<i);
}
4,练习
题目1:从键盘上输入 1 个正整数给 int 变量 num,输出由 3~6 位构成的数(从低0号开始编号)
基本思路:
1.截取 3~6 位的数,位移到 0~3 位
a)构建 3~6 位上为 1 其余为 0 的数
b)位与输入数
c)得到的结果右移 3 位
2.先将 3~6 位移到 0~3 位,截取 0~3 位
a)输入数右移 3 位
b)构建 0~3 位为 1 其余为 0 的数
c)位与,得到结果
//假设num=0xaa55;
//思路1:
int num=0xaa55;
int mask=0;
for(int i=6;i>=3;--1)
mask |=(1<<i);
num &= mask;
num = num>>3;
//思路2:
num = num>>3;
int mask=0;
for(int i=3;i>=0;--i)
mask |= (1<<i);
num &= mask;
题目2:实现循环移位
void circleMove(int *data ,int n);
当 n>0 的时候左移,n<0 的时候循环右移。
void circleMove(int *data,int n)//unsigned int *pdata
{
int m;
m = n>0?n:-n;
unsigned int mask = 0;
while(m--)
mask |=(1<<m);
if(n>0)//左循环n位
*pdata = (*pdata << n) | (mask & *data >> sizeof(*data)*8-n);
else//右循环n位
*pdata = ( (*pata >> -n) & (mask << sizeof(*data*8-(-n)) | (*data << sizeof(*data)*8-(-n));
}
题目3:反转一个数据的最后n位。
void reverse(int *data, int n)
{
int mask=0;
while(n--)
mask |=(1<<n);
mask = mask << sizeof(*data)*8-n;
*data = *data^mask;
}
题目4:.判断一个数是不是 2 的幂数。
我们观察发现:若一个数是2的幂数,则其补码中只有一位为1,其他全部为0。因此,我们可以推出:若n为2的幂数,且其补码为0100 0000 ,则n-1的补码为0011 1111 ,所以,n&n-1=0。
我们可以根据这个特点,来判断一个数是不是2的幂数。
void check(int *a)
{
return !( n & (n-1) );
}
5,提高
1)交换
(1)有参交换
void swap(int *a, int *b)
{
int t;
t=*a;
*a=*b;
*b=t;
}
缺点:使用了第三个变量。
void swap(int *a, int *b)
{
*a=*a+*b;
*b=*a-*b;
*a=*a-*b;
}
缺点:若a、b较大,有溢出的风险。
(2)无参交换
由异或的真值表可知,a,b,以及a^b(假设为c),知道a,b,c中的任意两个,将其进行异或运算,得到的就是第三个。
因此,我们可以运用这个原理,来实现两个数的交换。
void swap(int *a, int *b)
{
*a=*a^*b;
*b=*a^*b;
*a=*a^*b;
}
总结:无溢出,是交换数据的最高境界。
2)异或加密
函数的参数应有两个:一个是要加密的明文,另一个是密钥。
加密过程:
void encrypt(char *secret, char *key)
{
int kn = strlen(key);
int i = 0;
while(*secret != '\0')
{
if(*secret == key[i])
{
secret++;
i++;
}
else
{
*secret++ ^= key[i];
i++;
}
if(i%kn == 0)
i=0;
}
}
解密过程:
void de_encrypt(char *secret, char *key)
{
int kn = strlen(key);
int i=0;
while(*secret != '\0')
{
if(*secret == key[i])
{
secret++;
i++;
}
else
{
*secret ^= key[i];
i++;
}
if(i%kn==0)
i=0;
}
}
3)循环移位加密
位运算的加密应用,才是真正意义上的加密的开始。解决了加减法加密溢出的问题。
加密过程:
void encode(char *secret)
{
int n=strlen(secret);
unsigned char ch;
for(int n=0; i<n; i++)
{
ch = secret[i];
ch = (ch << 1) | (ch >> 7);
secret[i] = ch;
}
}
解密过程:
void decode(char *secret)
{
int n=strlen(secret);
unsigned char ch;
for(int n=0; i<n; i++)
{
ch = secret[i];
ch = (ch >> 1) | (ch << 7);
secret[i] = ch;
}
}