C/C++ 位操作详解
来源:https://www.cnblogs.com/intelwisd/p/8424824.html
【导读】:本文详细讲解C/C++位操作的原理与实际应用,非常值得学习。
位操作(Bit Operation)
位操作与逻辑操作
位操作不同于逻辑操作,逻辑操作是一种整体的操作,而位操作是针对内部数据位补码的操作。逻辑操作的世界里只有真假(零与非零),而位操作的世界里按位论真假(1和0)。运算也不相同。
数据的二进制形式表示
8位二进制数据的补码
eg:打印一个32位数据的二进制
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
int a = 0xffffffff;//-1内存中的形式
dis32bin(a);
return 0;
}
按位&(与)
同1为1,否则为0。
非1跟1按位与保持不变,1跟1按位与为1,跟0按位与清零。
性质:用1&,在某些位保持不变的情况下,某些清零。
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
//int a = 0xffffffff;//-1内存中的形式
int a = 3;
int b = 11;
dis32bin(a);
dis32bin(b);
printf("\n------------------------------\n");
dis32bin(a&b);
int c = a&b;
printf("c = %d\n",c);
return 0;
}
/*
0000-0000 0000-0000 0000-0000 0000-0011
0000-0000 0000-0000 0000-0000 0000-1011
---------------------------------------
0000-0000 0000-0000 0000-0000 0000-0011
c = 3
*/
按位或|(或)
只有两个都为0时才为0,其余为1.
跟1按位或置1,非0跟0或保持不变,0跟0或为0.
性质:用0|,在某些位保持不变的情况下,某些置1.
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main() {
int a = 3;
int b = 9;
dis32bin(a);
dis32bin(b);
printf("\n-----------------------------------------\n");
dis32bin(a|b);
int c = a|b;
printf("a|b = %d\n",c);
}
/*
0000-0000 0000-0000 0000-0000 0000-0011
0000-0000 0000-0000 0000-0000 0000-1001
-----------------------------------------
0000-0000 0000-0000 0000-0000 0000-1011
a|b = 11
*/
位取反(~)
个各位反转,1->0,0->1
按位取反,用于间接的构造某些数据。
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
int a = 0x55;
dis32bin(a);
dis32bin(~a);//a本身并未发生变化
dis32bin(0);
dis32bin(~0);
return 0;
}
/*
0000-0000 0000-0000 0000-0000 0101-0101
1111-1111 1111-1111 1111-1111 1010-1010
0000-0000 0000-0000 0000-0000 0000-0000
1111-1111 1111-1111 1111-1111 1111-1111
*/
位异或(^)(相异者或)
相异者1,相同者0
跟1按位异或取反,跟0按位异或保持不变。
性质:用1^,某些位保持不变的情况下,某些取反
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
int a = 0x55;
int b = 0xff;
dis32bin(a);
dis32bin(b);
dis32bin(a^b);
printf("\n=============================\n");
int c = 0;
dis32bin(a);
dis32bin(c);
dis32bin(a^c);
printf("\n=============================\n");
int d = 0x0f;
dis32bin(a);
dis32bin(d);
dis32bin(a^d);
return 0;
}
/*
0000-0000 0000-0000 0000-0000 0101-0101
0000-0000 0000-0000 0000-0000 1111-1111
0000-0000 0000-0000 0000-0000 1010-1010
=============================
0000-0000 0000-0000 0000-0000 0101-0101
0000-0000 0000-0000 0000-0000 0000-0000
0000-0000 0000-0000 0000-0000 0101-0101
=============================
0000-0000 0000-0000 0000-0000 0101-0101
0000-0000 0000-0000 0000-0000 0000-1111
0000-0000 0000-0000 0000-0000 0101-1010
*/
左移(<<)和右移(>>)
规则:使操作数的各位左移,低位补0,高位溢出。
移位大于32位时,对32求模运算.
左移不溢出的情况下:数字左移相当于乘以2^几次幂
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
int a = 0x01;
dis32bin(a);
dis32bin(a<<1);
dis32bin( (a<<31)+1 << 1);
/*
0000-0000 0000-0000 0000-0000 0000-0001
0000-0000 0000-0000 0000-0000 0000-0010
0000-0000 0000-0000 0000-0000 0000-0010
*/
dis32bin(a<<32);
dis32bin(a<<33);
dis32bin(a<<34);
printf("a<<33 = %d\n",a<<32);
printf("a<<33 = %d\n",a<<33);
printf("a<<34 = %d\n",a<<34);
/*
0000-0000 0000-0000 0000-0000 0000-0001
0000-0000 0000-0000 0000-0000 0000-0010
0000-0000 0000-0000 0000-0000 0000-0100
a<<33 = 1
a<<33 = 2
a<<34 = 4
*/
return 0;
}
右移(>>)
规则:使操作数的各位右移,移除低位舍弃。
高位:
1.对无符号数和有符号中的整数补0;
2.有符号数中的负数,取决于所使用的系统;补0的称为逻辑右移,补1的称为算数右移
在不溢出的情况下,数字左移相当于除以2^几次幂
同样大于32时对32求模运算。
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
//第一种情况:无符号数和有符号整数,高位补0,低位舍弃
unsigned int a = 0x55;
dis32bin(a);
dis32bin(a>>4);
int b = 1;
dis32bin(b);
dis32bin(b>>1);
printf("\n===========================\n");
//第二种情况:有符号中的负数:高位补0逻辑右移,高位补1,算数右移。
int c = 0x800000f0;
dis32bin(c);
dis32bin(c>>1);
dis32bin(c>>2);
dis32bin(c>>3);
return 0;
}
/*
0000-0000 0000-0000 0000-0000 0101-0101
0000-0000 0000-0000 0000-0000 0000-0101
0000-0000 0000-0000 0000-0000 0000-0001
0000-0000 0000-0000 0000-0000 0000-0000
=======================================
1000-0000 0000-0000 0000-0000 1111-0000
1100-0000 0000-0000 0000-0000 0111-1000
1110-0000 0000-0000 0000-0000 0011-1100
1111-0000 0000-0000 0000-0000 0001-1110
*/
应用
掩码
用一个状态模拟8盏灯的状态操作。
0x55 = 0101 0101,1开 0关
需求在此基础上打开从右至左第四盏灯
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
int main()
{
//0101 0101,
int ch = 0x55;
int mask = 1<<3;//从本身位置开始移动
dis32bin(ch);
ch = ch | mask;
dis32bin(ch);
/*
0000-0000 0000-0000 0000-0000 0101-0101
0000-0000 0000-0000 0000-0000 0101-1101
*/
return 0;
}
需求2:将从左至右第五位关闭
int main()
{
// 0101 0101
// 1110 1111 求&运算即可
//~0001 0000
int ch = 0x55;
int mask = ~(1<<4);
dis32bin(ch);
dis32bin(mask);
ch = ch & mask;
dis32bin(ch);
return 0;
}
/*
0000-0000 0000-0000 0000-0000 0101-0101
1111-1111 1111-1111 1111-1111 1110-1111
0000-0000 0000-0000 0000-0000 0100-0101
*/
需求3:从左至右将第三位,第五位关闭
分析:
原有状态:0101 0101
假设状态:1110 1011
假设取反:0001 0100
只需完成假设取反的状态和原有状态取反即可
1左移4位:0001 0000
1左移2位:0000 0100
<=> (1<<4) | (1<<2)
int main()
{
int ch = 0x55;
int mask = ~ ( (1<<4) | (1<<3) );
//ch = ch & mask;
ch &= mask;
dis32bin(ch);
return 0;
//0000-0000 0000-0000 0000-0000 0100-0001
}
需求4:从左至右第三位到第六位反转
分析:
原有状态:0101 0101 ^异或运算
假设状态:0011 1100
目标状态:0110 1001
假设状态:0010 0000
假设状态:0001 0000
假设状态:0000 1000
假设状态:0000 0100
最终状态:0011 1100
int main()
{
int ch = 0x55;
int mask = (1<<5)|(1<<4)|(1<<3)|(1<<2);
dis32bin(ch);
ch ^= mask;
dis32bin(mask);
dis32bin(ch);
return 0;
/*
0000-0000 0000-0000 0000-0000 0101-0101
0000-0000 0000-0000 0000-0000 0011-1100
0000-0000 0000-0000 0000-0000 0110-1001
*/
}
查看某一位的状态
需求从左至右第五位的状态
分析:
原有状态:0101 0101
假设状态:0001 0000 求 & =1
int main()
{
int ch = 0x55;
int mask = 1<<4;
if(ch&mask)
printf("此位为1\n");
else
printf("此位为0\n");
return 0;
//此位为1
}
位操作的总结
1.你要操作的那几位
2.找到合适的掩码
3.找到合适的位运算
test:
从键盘输入一个整数,输出3-6位构成的数(从低位0号开始编号)
//先进行掩码的设置操作,之后在位移
int main()
{
//0101 0101
int a = 0x55;
int mask = a<<3|a<<4|a<<5|a<<6;
a &= mask;
a >>= 3;
printf("a = %d\n",a);
}
//先位移,在进行掩码的设置操作
int main()
{
int a = 0x55;
a >>= 3;
int mask = 0x0f;
a &= mask;
printf("a = %d\n",a);
return 0;
}
优先级
() > 成员运算 > (!) 算术 > 关系 > 逻辑 > 赋值>
() > 成员运算 > (~!) 算术 > 关系 > (>> <<) 位逻辑(& | ^) 逻辑 > 赋值>
循环移位
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<
printf("1");
else
printf("0");
if(i%4 == 0)
{
if(i%8 == 0)
printf(" ");
else
printf("-");
}
}
putchar(10);
}
//用无符号的类型,避免了右移补1的问题
void circleMove(unsigned int *pa,int n)
{
n %= 32;
if(n>0)//左移
*pa = (*pa<
>(sizeof(*pa)*8-n)); else//右移逻辑
*pa = (*pa>>(-n)) | (*pa<<(sizeof(*pa)*8-(-n)));
}
int main()
{
int a = 0x80000001;//1000***0001
circleMove(&a,1);
dis32bin(a);
return 0;
}
无参交换
int mySwap(int *pa,int *pb)
{
//引入第三者
int t = *pa;
*pa = *pb;
*pb = t;
}
//以知两者的和,可以求任何其中之一,有益处的弊端
int mySwap1(int *pa1,int *pb1)
{
*pa1 = *pa1 + *pb1;
*pb1 = *pa1 - *pb1;
*pa1 = *pa1 - *pb1;
}
//x,y,x^y,三者之间两两求异或运算即可得到第三者。和加法的思路一样。
int mySwap2(int *pa2,int *pb2)
{
*pa2 = *pa2 ^ *pb2;
*pb2 = *pa2 ^ *pb2;
*pa2 = *pa2 ^ *pb2;
/*
*pa2 ^= *pb2;
*pb2 ^= *pa2;
*pa2 ^= *pb2;
*/
}
int main() {
int a = 3;
int b = 5;
mySwap(&a,&b);
return 0;
}
异或加密(文本与二进制)
void encode(char *buf,char ch)
{
int len = strlen(buf);
for(int i = 0;i < len;i++)
{
buf[i] ^= ch;
}
}
void decode(char *buf,char ch)
{
int len = strlen(buf);
for(int i = 0;i < len;i++)
{
buf[i] ^= ch;
}
}
int main()
{
char buf[] = "I love C++";
printf("buf = %s\n",buf);
char ch = 'a';//这种只要传入相同的字符就会出错。
encode(buf,ch);
printf("buf = %s\n",buf);
decode(buf,ch);
printf("buf = %s\n",buf);
return 0;
}
int a;
a ^= a;
printf("a = %d\n";a); //自身异或清零。
改进:
void encode(char *buf,char ch)
{
int len = strlen(buf);
for(int i = 0;i < len;i++)
{
if(buf[i] == ch)
continue;
buf[i] ^= ch;
}
}
void decode(char *buf,char ch)
{
int len = strlen(buf);
for(int i = 0;i < len;i++)
{
if(buf[i] == ch)
continue;
buf[i] ^= ch;
}
}
int main()
{
char buf[] = "I love C++";
printf("buf = %s\n",buf);
char ch = 'a';//这种只要传入相同的字符就会出错。
encode(buf,ch);
printf("buf = %s\n",buf);
decode(buf,ch);
printf("buf = %s\n",buf);
return 0;
}
升级:
void encode(char *buf,char *px)
{
int len = strlen(buf);
int n = strlen(px);
int j = 0;
for(int i = 0;i < len;i++)
{
if(buf[i] == px[j])
j++;
else
{
buf[i] ^= px[j++];
if(j == n)
j = 0;
}
}
}
void decode(char *buf,char *px)
{
int len = strlen(buf);
int n = strlen(px);
int j = 0;
for(int i = 0;i < len;i++)
{
if(buf[i] == px[j])
j++;
else
{
buf[i] ^= px[j++];
if(j == n)
j = 0;
}
}
}
int main()
{
char buf[] = "i love you";
char xx[] = "19920415";
encode(buf,xx);
printf("buf = %s\n",buf);
char buf2[1024];
scanf("%s",buf2);
decode(buf,buf2);
printf("buf = %s\n",buf);
return 0;
}
二进制加密没有上述是否相等问题。
循环移位加密
二进制加密:
void encode(char *buf,int n);
void decode(char *buf,int n)
int main()
{
FILE *pfr = fopen("01.png","rb+");
if(pfr == NULL)
exit(-1);
FILE *pfw = fopen("02.png","wb+");
if(pfw == NULL)
exit(-1);
/* 解密
FILE *pfr = fopen("02.png","rb+");
if(pfr == NULL)
exit(-1);
FILE *pfw = fopen("03.png","wb+");
if(pfw == NULL)
exit(-1);
*/
char buf[1024];
int n;
while((n = fread(buf,1,1024,pfr)) > 0)
{
encode(buf,n);
//decode(buf,n);解密
fwrite(buf,1,n,pfw);
}
fclose(pfr);
fclose(pfw);
return 0;
}
void encode(char *buf,int n)
{
for(int i = 0;i < n;i++)
{
unsigned char ch = buf[i];
buf[i] = ch<<1 | ch>>7;
}
}
void decode(char *buf,int n)
{
for(int i = 0;i < n;i++)
{
unsigned char ch = buf[i];
buf[i] = ch>>1 | ch<<7;
}
}
- EOF -