文章目录
位运算简介
C语言中的各种运算都是以字节的形式进行,在编写很多系统程序时,如驱动程序、磁盘文件管理程序等,常要求将数据按位(bit)进行运算或者处理。计算机中的数据(不管是整型,浮点型,还是字符型)在内存中都是以二进制的形式进行存储的,位运算就是直接对内存中的二进制数进行操作,位运算比一般算数运算要快,如果要考虑开发高效率的程序,位运算是必不可少的
常见的位运算符(运算符优先级按表下到上依次增高,如~为最高优先级)
运算符 | 含义 | 功能 |
---|---|---|
~ | 按位取反 | 对一个二进制数按位取反,0变为1,1变为0 |
<< | 左移 | 不管是有符号数还会无符号数,左移时右侧补0 |
>> | 右移 | 对于无符号数,右移时左侧补0;对于有符号数,对于有符号数,右移时左侧补符号位(如果是正数补0,负数就补1,也叫算数移位) |
& | 按位与 | 两个相应二进制位都为1,则相应结果二进制位为1,否则为0 |
^ | 按位异或 | 两个相应二进制位相异,则相应结果二进制位为1;如相同,则为0 |
| | 按位或 | 两个相应二进制位都为0,则相应结果二进制位为0,否则为1 |
位运算实例
1、给定一个整数a,设置a的二进制表示中bit5为1,其它位不变
#include <stdio.h>
int main(void)
{
//十进制形式
int d=195;//C语言中不能直接使用二进制,可以用十进制或者十六进制、八进制赋值
d|=1<<5;
printf("d=%#d\n",d);//#为输出相应进制前缀
//八进制形式
int o=0303; //八进制必须以0开头
o|=1<<5;
printf("o=%#o\n",o);
//十六进制形式
int h=0XC3;
h|=1<<5;
printf("h=%#X\n",h);
return 0;
}
输出:
代码释疑:
这里的变量d,o,h在数值上完全相等,只是以不同的数制进行表示,它们在内存中的二进制形式,都为1100 0011,设置bit5为1后都为1110 0011。
1<<5完全可以用0b100000(0b或0B是二进制数前缀)代替,两者等价。
按位或的用法:
经常被用于实现将一个数的某些位设置成1。将mask(上述代码为100000)中指定位设置成1,其它位设置成0,则s|=mask。使得s的指定位置设置为1。
1-2、给定一个整数a,设置a的bit5~bit10为1,其它位不变
#include <stdio.h>
int main(void)
{
//十六进制形式
int a=0XC3;//0000 0000 1100 0011
a|=0x3F<<5;//十六进制数0x3F对应二进制0000 0000 0011 1111,左移5位后为0000 0111 1110 0000
printf("a=%#X\n",a);//最终二进制结果为0000 0111 1110 0011,对应十六进制数0X7E3
return 0;
}
输出:
2、给定一个整形数a,它的bit5清除0,其它位保持不变
#include <stdio.h>
int main(void)
{
int a=0xff30;//对应二进制数为1111 1111 0011 0000
a&=~(1<<15);//对应a=a&0111 1111 1111 1111
printf("a=%#x\n",a);//最终a=0111 1111 0011 0000(bit15清零了,注意,下标从bit0开始)
return 0;
}
输出:
代码释疑:
二进制0111 1111 0011 0000对应十六进制0x7f30。
font color=red>按位与的用法:
经常被用于将特定位清0,将mask中指定位设置为0,s&=mask,实现了s指定位清0
由以上四个例子得知,在位运算中:置1用“位或1”;清0用“位与0”
2-2、给定一个整数a,设置a的bit5~bit10为0,其它位不变
#include <stdio.h>
int main(void)
{
int a=0xff30;//对应二进制数为1111 1111 0011 0000
a&=~(0X3F<<5);//构造一个bit5~bit10为0的二进制数为,1111 1000 0001 1111
printf("a=%#x\n",a);//最终a=1111 1000 0001 0000(bit5~bit10清零了,注意,下标从bit0开始)
return 0;
}
输出:
3、给定一个整数a,求a得bit3~bit8对应的整数
问题分析:
- 首先构造一个bit3~bit8都为1,其余位数都为0的二进制数,然后将这个数与a进行“位与”操作
- 接着再将操作结果进行右移3位,就可得到bit0~bit5为原来a的bit3~bit8的值
#include <stdio.h>
int main(void)
{
int a=0xFBA;//a对应二进制为0000 1111 1011 1010
a&=0x3F<<3;//0x3F对应的二进制为0000 0000 0011 1111,左移三位后为0000 0001 1111 1000
printf("测试输出:a=%#X\n",a);//测试输出0000 0001 1011 1000对应的十六进制0X1B8
a>>=3;//要输出其对应整数,还要右移回3位
printf("a的bit3~bit8对应整数:%#X\n",a);//输出a对应的二进制为0000 0000 0011 0111的十六进制数0X37
return 0;
}
输出:
4、用C语言给一个寄存器(32位)的bit7~bit17赋值937(其余位不受影响)
问题分析:
嵌入式程序中应用比较多,操作寄存器离不开位运算。寄存器的特点是按位进行规划和使用,但是寄存器的读写确实整体32位一起进行的,也就是说只想修改bit7~bit17是不行的,必须整体32bit全部写入,寄存器操作要求就是,在设定特定位时不能影响其它位。所以,当想改变一个寄存器中某些特定位时,不会直接去写寄存器,而是先从寄存器中把整体32位二进制数读取出来,然后在此基础上修改特定的值,最后再将修改后的32位二进制数整体写入寄存器。
- 第一步:将bit7~bit17清0,不能影响其他值
- 第二步:将937写入bit7~bit17
#include <stdio.h>
int main(void)
{
int a=0XACF71;//0000 0000 0000 1010 1100 1111 0111 0001
a&=~(0X7FF<<7);//0X7FF对应二进制0111 1111 1111,左移7位再取反,即可构造一个bit7~bit17为0,其余位为1的二进制数
//再进行位与运算,即可将a的bit7~bit17清0
a|=937<<7;//实现将a的bit7~bit17赋值937整数,其余位不变
printf("a=%#X\n",a);
return 0;
}
输出:
4-2、用C语言给一个寄存器的bit7~bit17赋值937,同时给bit21~bit25赋值17
int a=0XACF71;
a&=~((0X7FF)<<7|(0X1F<<7));//将a的bit7~bit17以及bit21~bit25清0
a|=(937<<7)|(17<<21);//0X229D4F1
代码释疑:
a=0XACF71,对应二进制数0000 0000 1010 1100 1111 0111 0001,绿色部分分别对应bit21~bit25,bit7~bit17,分别替换成17(1 0001),937(011 1010 1001),最后a对应的二进制数为10 0010 1001 1101 0100 1111 0001,对应十六进制为0X229D4F1
5、用C语言实现将一个寄存器的bit7~bit17的值加17(其余位不受影响)
具体步骤如下:
- 第一步:读出寄存器的bit7~bit17的值
- 第二步:将第一步读出的值加上17
- 第三步:将寄存器的bit7~bit17清0
- 第四步:将第二步算的值写入bit7~bit17
#include <stdio.h>
int main(void)
{
int a=0XACF71;
unsigned tmp;//用于存放寄存器bit7~bit17原来的值以及增加17后的值
tmp=a&(0X7FF<<7);//读出a的bit7~bit17值,并存放到tmp变量
tmp>>=7;//获取a的bit7~bit17的值
tmp+=17;//将获取的值加上17
a&=~(0X7FF<<7);//将a的bit7~bit17值清0,其余位不变
a|=tmp<<7;//将a的bit7~bit17赋值tmp整数,其余位不变
printf("a=%#X\n",a);
return 0;
}
输出:
代码释疑:
int a=0XACF71对应的二进制位为1010 1100 1111 0111 0001,绿色部分为bit7~bit17,这部分数再加上17(1 0001)变为10 1101 0111 1,a的bit7~bit17整体清零在赋值tmp,所以最后a对应的二进制数为1010 1101 0111 1111 0001,对应十六进制数为0XAD7F1
由上述代码得出按位与(&)的用法:
- 保留特定位,将mask的特定位设置为1,其它位设置为0,则s&=mask,实现了s的特定位被保留
tmp=a&(0X7FF<<7);//读出a的bit7~bit17值,并存放到tmp变量
- 特定位清0,将mask的特定位设置成0,其它位设置成1,则s&=mask,实现了s的特定位清0
a&=~(0X7FF<<7);//将a的bit7~bit17值清0,其余位不变
6、使用位运算判断一个整数是奇数还是偶数
判断一个整数是奇数还是偶数,可以用对2求余的思想判断,如果这是一个小整数还可以,如果是一个大整数,那么求余思想就会很低,我们还有一个特有方法:根据这个整数对应的二进制形式的末位为0表示偶数,为1表示奇数的特点,即能判断它是奇数还是偶数。
#include <stdio.h>
int main(void)
{
int n;
printf("请输入一个整数:\n");
scanf("%d",&n);
if((n&1)==1)
{
printf("%d是奇数\n",n);
}
else
{
printf("%d是偶数\n",n);
}
return 0;
}
奇数 | 偶数 |
---|---|
1(0001) | 2(0010) |
3(0011) | 4 (0100) |
5(0101) | 6(0110) |
7、使用位运算计算一个int整数的二进制数中有多少个1
#include <stdio.h>
int CountOne(int a);
int main(void)
{
int a;
printf("输入一个整数:");
scanf("%d",&a);
printf("%d的二进制表示中1的个数是%d\n",a,CountOne(a));
return 0;
}
int CountOne(int a)
{
int c=0;//记录二进制表示1的个数
while(a)
{
c++;
a&=a-1;
}
return c;
}
输出:
8、使用位运算的“异或”运算符完成两个变量值的交换
按位异或(^)两个相应二进制位相异,则相应结果二进制位为1;如相同,则为0,因为“异或”具有交换律,可完成两个变量值的交换。其中异或有两个规律:
规律一 | 规律二 |
---|---|
两个相同的数据进行“异或”运算结果为0 | 一个数与0进行“异或”运算结果仍为原数 |
#include <stdio.h>
int main(void)
{
int a,b;
a=7,b=5;//初始a=7,b=5
a=a^b;
b=b^a;//此时完成b的值变为a的值
a=a^b;//此时完成a的值变为b的值
printf("a=%d b=%d\n",a,b);
return 0;
}
输出:
代码释疑:
- 代码第6行、第7行:
a=a^b;
b=b^a;//此时完成b的值变为a的值
相当:
b=b^a=b^a^b=b^b^a=a
红色部分利用规律一,红色与绿色部分用到规律二。
- 代码第8行:
同理可完成a的值变为b的值