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<<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-00110000-0000 0000-0000 0000-0000 0000-1011
---------------------------------------0000-0000 0000-0000 0000-0000 0000-0011c = 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-00110000-0000 0000-0000 0000-0000 0000-1001-----------------------------------------0000-0000 0000-0000 0000-0000 0000-1011a|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-01011111-1111 1111-1111 1111-1111 1010-10100000-0000 0000-0000 0000-0000 0000-00001111-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-01010000-0000 0000-0000 0000-0000 1111-11110000-0000 0000-0000 0000-0000 1010-1010=============================0000-0000 0000-0000 0000-0000 0101-01010000-0000 0000-0000 0000-0000 0000-00000000-0000 0000-0000 0000-0000 0101-0101=============================0000-0000 0000-0000 0000-0000 0101-01010000-0000 0000-0000 0000-0000 0000-11110000-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-00010000-0000 0000-0000 0000-0000 0000-00100000-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-00010000-0000 0000-0000 0000-0000 0000-00100000-0000 0000-0000 0000-0000 0000-0100a<<33 = 1a<<33 = 2a<<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-01010000-0000 0000-0000 0000-0000 0000-01010000-0000 0000-0000 0000-0000 0000-00010000-0000 0000-0000 0000-0000 0000-0000
=======================================1000-0000 0000-0000 0000-0000 1111-00001100-0000 0000-0000 0000-0000 0111-10001110-0000 0000-0000 0000-0000 0011-11001111-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-01010000-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-01011111-1111 1111-1111 1111-1111 1110-11110000-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-01010000-0000 0000-0000 0000-0000 0011-11000000-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; }}

- EOF -

(0)

相关推荐

  • C/C 指针详解之提高篇

    目录 一. 堆空间与指针的相爱相杀 1.1 堆上一维空间 1.1.1 返回值返回(一级指针) 1.1.2 参数返回(二级指针) 1.2 堆上二维空间 1.2.1 指针作返值输出 1.2.2 空间申请与 ...

  • 胎元命宫详解

    胎元命宫详解 胎元命宫 8.1 胎元 胎, 指人受精怀胎的月份. 其起法是: 人生月后紧接着这个月的天干与生月后第三个月的地支相配, 就为胎元. 如1998年八月生人, 八月为辛酉, 辛后一干是壬, ...

  • 批八字算婚姻详解

    批八字算婚姻详解 很多人喜欢在孩子一出生的时候就给他们算一下八字,因为他们相信孩子的八字和命运是相对注定了的,通过算命之后可以顺利的避免一些可能在生活中遇到的一些问题和坎坷,也可以顺利度过一些&quo ...

  • 电视选购12个重要参数详解,看完你就是专家,附:爆款推荐

    本内容来源于@什么值得买APP,观点仅代表作者本人 |作者:白云上的鱼 创作立场声明:分享电视选购知识,重要参数详解,轻松搞定电视选购. 目前电视的选择太多太多了,品牌百花齐放琳琅满目,各种高科技加成 ...

  • 倪海厦:病是问出来的|问诊十法详解

    倪海厦,美国经方中医,被喻为当代少见的"命.相.卜.山.医"五术兼备之旷世奇人. (倪师)中医的问诊十个法则 我们经方家的问诊非常重要,因此有必要为读者说明一下,如何找经方家看病, ...

  • 为何医生让他把氨氯地平换成缬沙坦?药师详解两类降压药的好与坏

    硝苯地平.氨氯地平.缬沙坦.氯沙坦等等,这些降压药都是高血压患者常用的降压药.从名字中也可以看出这些降压药属于两类不同的降压药,一种是地平类,即为钙离子拮抗剂(CCB),另外一种是沙坦类,即为血管紧张 ...

  • 几何探究类压轴题:精编20例及详解

    成才路上 初中精品学习资料 104篇原创内容 公众号 / END /

  • 高考物理11类重点题型全解析! 附经典例题&详解

    高考理科综合卷中,物理部分选择题有单项和双项选择题两种题型.从最近几年的试题看: 4道单项选择难度低,考查的考点相对稳定且相对单一,涉及的知识点主要有共点力平衡.热力学第一定律.气体状态方程.分子动理 ...

  • 【同步讲练】七年级下册:二元一次方程组七种典型例题详解,一次解决应用问题!

    【同步讲练】七年级下册:二元一次方程组七种典型例题详解,一次解决应用问题!

  • 行书基础笔法详解,以兰亭序为例,建议收藏学习

    学好行书 4篇原创内容 公众号 欢迎您查看行书名帖 在行书的书写中,我们一方面要注意其字形,另一方面更要注意笔画的写法,因为笔画是字的基本构成元素.因此,把握好每个笔画的写法是最为重要的一个学习环节. ...