C语言再学习 -- 位操作

一、二进制

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。

位:"位(bit)"是电子计算机中最小的数据单位。每一位的状态只能是0或1。
字节:8个二进制位构成1个"字节(Byte)",它是存储空间的基本计量单位。1个字节可以储存1个英文字母或者半个汉字,换句话说,1个汉字占据2个字节的存储空间。
字:"字"由若干个字节构成,字的位数叫做字长,不同档次的机器有不同的字长。例如一台8位机,它的1个字就等于1个字节,字长为8位。如果是一台16位机,那么,它的1个字就由2个字节构成,字长为16位。字是计算机进行数据处理和运算的单位。 
二进制没有占位符

二进制1011表示:

1x2^3 + 0x2^2 + 1x2^1 + 1x2^0 = 11


-5  二进制为 1111 1011            十进制数之间转二进制 关系是  取反加一
5的  二进制   0000 0101
取反          1111 1010
加一          1111 1011

有符号整数参看:C语言再学习 -- 负数

二进制浮点数参看:C语言再学习 -- 浮点数


二、八进制

把二进制从右向左每三个数位分成一组,每一组单独转换成十进制结果一定在0到7之间。把所有组的转换结果按顺序书写就得到数字的八进制表示方式。

例如,八进制数451(在C中写为0451)表示:

4x8^2 + 5x8^1 + 1x8^0 = 297

可以在程序中用八进制方式表示数字必须以0作为开头,采用%o作为占位符可以把一个整数的八进制表示方式打印在屏幕上


三、十六进制

把二进制数字从右向左每四个数位分成一组,每组单独转换成十进制一定在0到15之间,如果转换结果在10到15之间用英文字母a到f分别表示,把所有转换结果按顺序书写就得到数字的十六进制表示方式
例 0110 1010 转换成十六进制为 6a  
例 0010 1011  43  053   2b

在程序中使用十六进制方式表示数字,必须以0x开头,采用%x或者%X作为占位符可以把数字的十六进制表示方式打印在屏幕,上打印结果中不包含0x。
%x做占位符的时候打印结果中的英文字母都是小写的
%X做占位符的时候打印结果中的英文字母都是大写的

//占位符
#include <stdio.h>
int main() {
	printf("0%o %d\n",0152,0152);   //八进制 记得前面加个 0
	printf("0x%X 0x%x 0%o %d\n",0xcb,0xcb,0xcb,0xcb);  // 十六进制记得前面加 0x
	return 0;
}
输出结果:
0152 106
0xCB 0xcb 0313 203

参看:C语言再学习 -- printf、scanf占位符


参看:二进制转换对照表

扩展:进制转换器

1、整数部分的二进制转换成十进制。

指数十进制数二进制数
2010001
2120010
2240100
2381000
24160001 0000
25320010 0000
26640100 0000
271281000 0000
282560001 0000 0000
295120010 0000 0000
21010240100 0000 0000
21120481000 0000 0000
21240960001 0000 0000 0000
21381920010 0000 0000 0000
214163840100 0000 0000 0000
215327681000 0000 0000 0000
216655360001 0000 0000 0000 0000

2、小数部分的二进制转换成十进制。

记到小数点后六位就够了,如果再向后,你可以继续除2,不过这题目可就有些变态了。

指数 分数二进制十进制
 2-1 1/21 .1 .5
 2-2 1/22 .01 .25
 2-3 1/23 .001 .125
 2-4 1/24 .0001 .0625




  2-5 1/25 .0000 1 .03125
  2-6 1/26 .0000 01 .015625

3、二进制(B,Binary),八进制(O,Octal) 十进制(D,Decimalist),十六进制(H,Hex)

二进制八进制十进制十六进制
0000000
0001111
0010222
0011333
0100444
0101555
0110666
0111777
10001088
10011199
10101210A
10111311B
11001412C
11011513D
11101614E
11111715F

四、位运算符

1、二进制反码或按位取反:~

需要确认该值是否为unsigned类型,如果是有符号则,正数为原来的数取反、加1

例如:~6 

0000 0110

1111 1001 =473 = -0000 0111 ==-7


2、&(按位与)(串联)

只有对应数位上都是1的时候结果才是1
3 二进制       0000 0011    
5 二进制       0000 0101
按位与  & 为 0000 0001
结果是 1

C也有一个组合的位与赋值运算符:&=。下面两个语句产生相同的最后结果:

val &= 0377;
val = val & 0377


3、|(按位或)(并联)

只要对应数位中有1则结果就是1

3 二进制      0000 0011   
5 二进制      0000 0101
按位或  |  为 0000 0111
结果为 7

C也有一个组合的位或赋值运算符:|=。下面两个语句产生相同的最后结果:

val 1= 0377;
val = val | 0377


4、^(按位异或)

如果对应数位内容一样则结果是0,否则结果为1

3 二进制       0000 0011   
5 二进制       0000 0101
按异或  ^ 为 0000 0110
结果位 6

C也有一个组合的位异或赋值运算符:^=。下面两个语句产生相同的最后结果:

val ^= 0377;
val = val ^0377


5、移位运算符

移动操作符可以把数字中每个二进制数位统一想左或者向右移动n个位置,移动操作会得到一个新数字,不会修改原来的数字。

1) <<表示向左移动操作

向左移动是右边空出来的位置上一定补充0
例如 二进制  (0000 0011) << 2  向左移动两位结果为  0000 1100
3    0000 0011     3 x 2^2 =12
      0000 1100

再如:

-5 << 2 = -20

也可以这么理解,向左移动n位相当于乘以2的n次方。


2) >>表示向右移动操作

对于unsigned类型,右移时使用0填充左端空出的位。对于有符号类型,结果依赖于机器。空出的位可能用0填充,或者使用符号(最左端的)位的副本填充。

例如 二进制 (0000 1100) >> 2 向右移动两位结果为 0000 0011

12    0000 1100       12 / 2^2 = 3

         0000 0011

也可以这么理解,相对于unsigned类型而言向右移动n位相当于除以2的n次方


注意:

应避免使用 a << -5 这种类型的移位,因为它们的效果是不可预测的,使用类型移位的程序时不可移植的。

编译器会出现,警告: 左移次数为负 [默认启用]


再来看看下面的例子:

0x01 << 2 + 3; 结果是多少?

#include <stdio.h>

int main (void)
{
	printf ("%d\n", 0x01 << 2 + 3);
	return 0;
}
输出结果:
32
因为 "+" 号的优先级比移位运算符的优先级高


如果再把例子改写一下:

0x01 << 2 + 30;  或者 0x01 << 2 -3;  这样行吗?

#include <stdio.h>

int main (void)
{
	printf ("%d\n", 0x01 << 2 + 30);
	return 0;
}
输出结果:
警告: 左移次数大于或等于类型宽度 [默认启用]
#include <stdio.h>

int main (void)
{
	printf ("%d\n", 0x01 << 2 - 3);
	return 0;
}
输出结果:
警告: 左移次数为负 [默认启用]
首先考虑的还是运算符优先级,然后看,一个整型数长度为 32 位,左移 32 位则会溢出。左移 -1 位也是不对的。

所以说,左移和右移的位数不能大于数据的长度,不能小于 0。

五、位运算符用法

1、掩码

flags &= MASK;

例如:flags二进制为 1001 0110   MASK二进制为 0000 0010 即:

flags &= 0x02;

flags = 0000 0010

这个语句将导致flags的除位 1之外,所有位都被设为 0


2、打开位

flags |= MASK;

例如:flags二进制为 1001 0100   MASK二进制为 0000 0010 即:

flags |= 0x02;

flags = 1001 0110

这个语句将flags中的位1设为1并保留其他所有位不变


结合移位运算符

flags |= MASK << n;       

例如:flags二进制为 1001 0110   MASK二进制为 0000 0001  n为4即:

flags |= 0x01<<4;    (高电平)

flags = 1001 1110

这个语句将flags的位 3 设为1,并保留其他所有位不变。


3、关闭位

flags &= ~MASK;

例如:flags二进制为 1001 0110   MASK二进制为 0000 0010 即:

flags &= ~0x02;

flags = 1001 0100

这个语句将flags除位1设为0以外,保留其他所有位不变


结合移位运算符

flags &= ~(MASK << n);  

例如:flags二进制为 1001 0110   MASK二进制为 0000 0001  n为3即:

flags &= ~(0x01<<3);    (低电平)

flags = 1001 0010

这个语句将flags的位 2 设为0,并保留其他所有位不变    


4、转置位

flags ^= MASK;

例如:flags二进制为 1001 0110   MASK二进制为 0000 0010 即:

flags ^= 0x02;

flags = 1001 0100

转置一个位表示如果该位打开,则关闭该位,如果该位关闭,则打开该位。


5、查看一位的值

if ((flags & MASK) == MASK)

puts ("Wow);

例如:flags二进制为 1001 0110   MASK二进制为 0000 0010 即:

if ((flags & 0x02) == 0x02)

puts ("Wow);

这个语句可判断flags位1是否为1,由于位运算符的优先级低于==,因此需要在flags & MASK的两侧加上圆括号。


六、位字段

扩展:C语言中的位字段

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聚优致成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值