一、二进制
二进制是计算技术中广泛采用的一种数制。二进制数据是用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
参看:二进制转换对照表
扩展:进制转换器
1、整数部分的二进制转换成十进制。
指数 | 十进制数 | 二进制数 |
20 | 1 | 0001 |
21 | 2 | 0010 |
22 | 4 | 0100 |
23 | 8 | 1000 |
24 | 16 | 0001 0000 |
25 | 32 | 0010 0000 |
26 | 64 | 0100 0000 |
27 | 128 | 1000 0000 |
28 | 256 | 0001 0000 0000 |
29 | 512 | 0010 0000 0000 |
210 | 1024 | 0100 0000 0000 |
211 | 2048 | 1000 0000 0000 |
212 | 4096 | 0001 0000 0000 0000 |
213 | 8192 | 0010 0000 0000 0000 |
214 | 16384 | 0100 0000 0000 0000 |
215 | 32768 | 1000 0000 0000 0000 |
216 | 65536 | 0001 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)
二进制 | 八进制 | 十进制 | 十六进制 |
0000 | 0 | 0 | 0 |
0001 | 1 | 1 | 1 |
0010 | 2 | 2 | 2 |
0011 | 3 | 3 | 3 |
0100 | 4 | 4 | 4 |
0101 | 5 | 5 | 5 |
0110 | 6 | 6 | 6 |
0111 | 7 | 7 | 7 |
1000 | 10 | 8 | 8 |
1001 | 11 | 9 | 9 |
1010 | 12 | 10 | A |
1011 | 13 | 11 | B |
1100 | 14 | 12 | C |
1101 | 15 | 13 | D |
1110 | 16 | 14 | E |
1111 | 17 | 15 | F |
四、位运算符
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语言中的位字段