C语言位操作详解(全网最全)

​ 例如: 一个二进制数 1101 1001,我只想要它的后四位,怎么办呢?

​ 只需要进行如下操作:1101 1001 & 0000 1111即可。

​ 其实该方法是屏蔽和读取的结合,&0保证消除无用位,&1保证有用数据的完整性。

​ 总结:对于原二进制数来说,&0是屏蔽,&1是不变。

按位或(OR):|

  1. 定义:只要参与运算的双方其中有一个是1,结果就是1.同0才为0.

0 | 0 = 0;

0 | 1 = 1;

1 | 0 = 1;

1 | 1 = 1;

不同大小的数据位操作的原则,低位对齐,高位补零。
2. 将某些特定位置1.

例如:1010 0000 | 0000 1111.

结果为 1010 1111.

总结:对于原二进制数来说,|0是不变,|1是置1.

按位异或:^

  1. 只要参与运算的双方互异,结果就为1,否则为0.

0 ^ 1 = 1;

1 ^ 0 = 1;

1 ^ 1 = 0;

0 ^ 0 = 0;

不同大小的数据位操作的原则,低位对齐,高位补零。
2. 用法

可以通过上面的定义看到,一个数1的话就会0变成1,1变成0,而0则不对原数进行改变。所以根据此特性可以对特定位进行0 1 反转。

例如: 1100 1100 ^ 0000 1100

结果为 1100 0000.

同样的,如果对一个数进行^0,代表保留原值。

取反(~)

​ 对一个二进制数进行取反。1变0,0变1.

​ 唯一需要注意的一点是,~的优先级是逻辑运算符中最高的,必须优先计算。

左移<<

  1. 用法

对运算符<<左边的运算量的每一位全部左移右边运算量表示的位数,右边空出的位补0。

左移<<的原则是高位舍弃,低位补零。

例:char a=0x21;

则a<<2的过程 0010 0001〈〈2 = 1000 0100;即 a<<2的值为0x84。

左移1位相当于该数乘以2,左移n位相当于该数乘以2n。

右移>>

  1. 用法

运算规则:对运算符>>左边的运算量的每一位全部右移右边运算量表示的位数,右边低位被移出去舍弃掉,空出的高位补0还是补1,分两种情况:

(1)对无符号数进行右移时,空出的高位补0。这种右移称为逻辑右移。

(2)对带符号数进行右移时,空出的高位全部以符号位填补。即正数补0,负数补1。这种右移称为算术右移。

右移1位相当于除以2,同样,右移n位相当于除以2n。

除法运算转化成位运算 (在不产生溢出的情况下)

a / (2^n) 等价于 a>> n

取模运算转化成位运算 (在不产生溢出的情况下)

a % (2^n) 等价于 a & (2^n - 1)

循环移位的实现。

如将一个无符号整数x的各位进行循环左移4位的运算,即把移出的高位填补在空出的低位处。

可以用以下步骤实现:

(1)将x左移4位,空出的低4位补0,可通过表达式x<<4实现。

(2)将x的左端高4位右移到右端低4位,可通过表达式x>>(16-4)实现。由于x为无符号整数,故空出的左端补0。

(3)将上述两个表达式的值进行按位或运算,即:

y=(x<<4) | (x>>(16-4));
x 0010 1111 0010 0001
x<<4 1111 0010 0001 0000
x>>(16-4) 0000 0000 0000 0010
y 1111 0010 0001 0010

unsigned rol ( unsigned a,int n)
{ unsigned b ;
b=(a<<n) | (a>>(16-n)) ;
return(b);}

​ 计算绝对值

int abs( int x )
{ int y ;
y = x >> 31 ;//二进制最高位
return (x^y)-y ; //or: (x+y)^y
}

综合灵活运用

在对6种位操作的使用熟悉了之后,我们怎么将其结合起来来获得更加高级和复杂的使用方法呢。

注意,位的索引是从0开始的,最低位为第0位。例如int的位从0-31,0位为最低位,31为最高位。

  1. 指定某一位数为1

宏 #define setbit(x,y) x|=(1<<y)

比如将x = 0000 0000 0000 0101的第y = 4位变成1.

0000 0000 0000 0001<<4 = 0000 0000 0001 0000

x = x | 0000 0000 0001 0000 = 0000 0000 0000 0101 | 0000 0000 0001 0000 = **0000 0000 0001 0101 **
2. 指定的某一位数置0

宏 #define clrbit(x,y) x&=~(1<<y)

比如将x = 0000 0000 0000 0101的第y = 2位变成0.

0000 0000 0000 0001<<2 = 0000 0000 0000 0100

~(0000 0000 0000 0100) = 1111 1111 1111 1011

x = x & 1111 1111 1111 1011 = 0000 0000 0000 0101 & 1111 1111 1111 1011= 0000 0000 0000 0001
3. 指定的某一位数取反

宏 #define reversebit(x,y) x^=(1<<y)

比如将x = 0000 0000 0000 0101的第y = 2位由1变成0.

0000 0000 0000 0001<<2 = 0000 0000 0000 0100

x = x ^ 0000 0000 0000 0100 = 0000 0000 0000 0101 ^ 0000 0000 0000 0100 = 0000 0000 0000 0001
4. 获取的某一位的值

宏 #define getbit(x,y) ((x) >> (y)&1) 或者 #define GET_BIT(x, y) ((x & (1 << y)) >> y)

比如获取x = 0000 0000 0000 0101的第y = 2位的值.

0000 0000 0000 0101 >> 2 = 0000 0000 0000 0001

0000 0000 0000 0001 & 0000 0000 0000 0001 = 0000 0000 0000 0001 = 1
5. 判断某几位连续位的值

/* 获取第[n:m]位的值 */
#define BIT_M_TO_N(x, m, n) ((unsigned int)(x << (31-(n))) >> ((31 - (n)) + (m)))

这是一个查询连续状态位的例子,因为有些情况不止有0、1两种状态,可能会有多种状态,这种情况下就可以用这种方法来取出状态位,再去执行相应操作。
6. 获取单字节:

一个32bit数据的位、字节读取操作

#define GET_LOW_BYTE0(x) ((x >> 0) & 0x000000ff) /* 获取第0个字节 */
#define GET_LOW_BYTE1(x) ((x >> 8) & 0x000000ff) /* 获取第1个字节 */
#define GET_LOW_BYTE2(x) ((x >> 16) & 0x000000ff) /* 获取第2个字节 */
#define GET_LOW_BYTE3(x) ((x >> 24) & 0x000000ff) /* 获取第3个字节 */

  1. 清零某个字节:

#define CLEAR_LOW_BYTE0(x) (x &= 0xffffff00) /* 清零第0个字节 */
#define CLEAR_LOW_BYTE1(x) (x &= 0xffff00ff) /* 清零第1个字节 */
#define CLEAR_LOW_BYTE2(x) (x &= 0xff00ffff) /* 清零第2个字节 */
#define CLEAR_LOW_BYTE3(x) (x &= 0x00ffffff) /* 清零第3个字节 */

  1. 置某个字节为1:

#define SET_LOW_BYTE0(x) (x |= 0x000000ff) /* 第0个字节置1 */
#define SET_LOW_BYTE1(x) (x |= 0x0000ff00) /* 第1个字节置1 */
#define SET_LOW_BYTE2(x) (x |= 0x00ff0000) /* 第2个字节置1 */
#define SET_LOW_BYTE3(x) (x |= 0xff000000) /* 第3个字节置1 */

  1. 高低位交换

对一个字节数据,逐个交换其高低位,例如11010001,经过0-7,1-6,2-5,3-4对应位的交换,变成10001011 。

对于该问题,我们最先想到的是对原字节通过移位操作来逐位处理,使用另一个变量来存储交换后的结果。这种解决方案处理起来思路清晰,编写代码应该不难。

unsigned char shift_fun1(unsigned char data)
{
unsigned char i;
unsigned char tmp=0x00;
for(i=0;i<8;i++)
{
tmp=((data>>i)&0x01)|tmp;
if(i<7)
tmp=tmp<<1;
}
printf(" after shift fun1 data=%x \n",tmp);
return tmp;
}

上述代码实现起来不难,而且效率还是比较高的。但是还有比这更简洁的解决方法,在嵌入式开发中遇到交换字节位的问题时通常使用蝶式交换法和查表法来实现。查表法顾名思义即将一些值存到内存中,需要计算时查表即可,但是也会占用额外的存储空间。这里主要再介绍一下蝶式交换法。

所谓的蝶式交换是这样的:

data=(data<<4)|(data>>4);
data=((data<<2)&0xcc)|((data>>2)&0x33);
data=((data<<1)&0xaa)|((data>>1)&0x55);

我们可以做一下执行演算:
假设原始位序列为 0 1 0 1 1 0 0 1

data=(data<<4)|(data>>4);之后序列为 1 0 0 1 0 1 0 1

data=((data<<2)&0xcc)|((data>>2)&0x33); 之后序列为 0 1 1 0 0 1 0 1
data=((data<<1)&0xaa)|((data>>1)&0x55); 之后序列为 1 0 0 1 1 0 1 0

更抽象的来说 原始位为 1 2 3 4 5 6 7 8

data=(data<<4)|(data>>4); 之后位序为 5 6 7 8 1 2 3 4
data=((data<<2)&0xcc)|((data>>2)&0x33); 之后位序为 7 8 5 6 3 4 1 2

data=((data<<1)&0xaa)|((data>>1)&0x55); 之后位序为 8 7 6 5 4 3 2 1

由此完成了整个位的逆序转换,下面是具体的实现代码:

unsigned char shift_fun2(unsigned char data)
{
data=(data<<4)|(data>>4);
data=((data<<2)&0xcc)|((data>>2)&0x33);
data=((data<<1)&0xaa)|((data>>1)&0x55);
printf(" after shift fun2 data=%x \n",data);
return data;
}

  1. 判断奇偶数

对于除0以外的任意数x,使用x&1==1作为逻辑判断即可
11. 判断一个数是不是 2 的指数

bool isPowerOfTwo(int n) {
if (n <= 0) return false;
return (n & (n - 1)) == 0;
}

  1. 取余,(除数为2的n次方)

//得到余数
int Yu(int num,int n)
{
int i = 1 << n;
return num&(i-1);
}

  1. 统计二进制中 1 的个数

利用x=x&(x-1),会将x用二进制表示时最右边的一个1变为0,因为x-1会将该位变为0.

int Count(int x)
{ int sum=0;
while(x)
{ sum++;
x=x&(x-1);
}
return sum;
}

  1. 生成组合编码,进行状态压缩

当把二进制当作集合使用时,可以用or操作来增加元素。合并编码 在对字节码进行加密时,加密后的两段bit需要重新合并成一个字节,这时就需要使用or操作。
15. 求一个数的二进制表达中0的个数

int Grial(int x)
{
int count = 0;
while (x + 1)
{
count++;
x |= (x + 1);
}
return count;
}

  1. 两个整数交换变量名

void swap(int &a, int &b) {
a ^= b;
b ^= a;
a ^= b;
}

  1. 判断两个数是否异号

int x = -1, y = 2;
bool f = ((x ^ y) < 0); // true
int x = 3, y = 2;
bool f = ((x ^ y) < 0); // false

  1. 数据加密

将需要加密的内容看做A,密钥看做B,A ^ B=加密后的内容C。而解密时只需要将C ^ 密钥B=原内容A。如果没有密钥,就不能解密!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define KEY 0x86
int main()
{
char p_data[16] = {“Hello World!”};
char Encrypt[16]={0},Decode[16]={0};
int i;

for(i = 0; i < strlen(p_data); i++)
{
Encrypt[i] = p_data[i] ^ KEY;
}

for(i = 0; i < strlen(Encrypt); i++)
{
Decode[i] = Encrypt[i] ^ KEY;
}

printf(“Initial date: %s\n”,p_data);
printf(“Encrypt date: %s\n”,Encrypt);
printf(“Decode date: %s\n”,Decode);

return 0;
}

  1. 求2的N次方

1<<n

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

链图片转存中…(img-yt7olkki-1712864392850)]
[外链图片转存中…(img-cW6SOrWw-1712864392851)]
[外链图片转存中…(img-0eOOUlKW-1712864392851)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-stkDlFmu-1712864392852)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值