位操作(Bit Operation)
位操作與邏輯操作
位操作不同於邏輯操作,邏輯操作是一種整體的操作,而位操作是針對內部數據位補碼的操作。邏輯操作的世界里只有真假(零與非零),而位操作的世界里按位論真假(1和0)。運算也不相同。
數據的二進制形式表示
8位二進制數據的補碼
eg:打印一個32位數據的二進制
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<<i))
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<<i))
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<<i))
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<<i))
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<<i))
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<<i))
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<<i))
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<<i))
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;
}
優先級
() > 成員運算 > (!) 算術 > 關系 > 邏輯 > 賦值>
() > 成員運算 > (~!) 算術 > 關系 > (>> <<) 位邏輯(& | ^) 邏輯 > 賦值>
循環移位
#include<stdio.h>
void dis32bin(int data)
{
int i = 32;
while(i--)
{
if(data & (1<<i))
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<<n) | (*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;
}
升級:
#include<stdio.h>
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;
}
}