一、二进制数、位、字节
1、二进制整数
C语言使用byte存储系统字符集所需要的大小,可以是8位,16位等;
描述存储器芯片和传输速率所用的字节:8位字节;
1字节(byte)=8位(bit);
二进制数:以2为基地表示的数字;
例如,二进制数:1101 可表示为:1*2^3+1*2^2+0*2^1+1*2^0
假设这里是1byte=8bit,需要从左往右依次编号为7~0;
高阶位(high-order bit):是编号7,低阶位(low-order bit):是编号0;
该字节最小二进制数:00000000,最大二进制数:11111111;
因此,1byte可存储0~255范围内的数字,共256个值;
选用不同方式解释为组合(bit pattern):
unsigned char :(1byte)0~255;
signed char:(1byte)-128~+127;
表示有符号整数(signed int)取决于硬件,表示方法;
1)符号量表示法
高阶位存储符号,只剩下7位表示数字本身;
10000001表示:-1
000000001表示:1
因此,表示范围为-127~+127;
此方法缺点:有-0,+0两个0;
2)二进制补码
用1byte中后7位表示0~127,高阶位设置为0;
高阶位:1,表示值为负
二进制补码与符号量表示法区别不同在于如何确负值
9位组合100000000(256的二进制形式)- 负数的位组合=该负值的量
例如:
无符号字节:10000000,该组合表示128,是一个负值的位组合
作为有符号值:是第7位表示1:1000000
100000000-1000000=1000000(128)
总的来说,使用二进制补码表示负数,可以通过正数:反转每一位(0取1,1取0),然后+1得到对应的相反数
例如,00000001是1,那么-1就是11111110+1就是11111111
3)二进制反码
正数与原码相同,负数是原码取反
这种方法也会有一个-0;
00000001是1,11111110是-1;
该方法能表示-127~+127;
二、其他进制数
1、二进制浮点数
浮点数存储分为两部分存储:二进制小数部分,二进制指数部分
普通十进制:0.527
5/10+2/100+7/1000
二进制小数.101
1/2+0/4+1/8
注意,二进制表示法只能精准表示1/2幂的和
根据以上规律可得到,数字的实际值是由二进制小数乘以 2的指定次幂组成;
就有,一个浮点数乘以4,那么二进制小数不变,其指数乘以2,二进制分数不变。
2、八进制(octal)
该系统基于8的幂,用0~7表示 数字(正如十进制用0~9表示数字一样);
每个八进制位对应3个二进制位;
3、十六进制(hex)
该系统基于16 的幂,用0~15表示数字;单独数表示0~9,字母A~F来表示10~15;
每个十六进制位都对应一个4位的二进制数(即4个二进制位),那么两 个十六进制位恰好对应一个8位字节。
三、C按位运算符
1、按位逻辑运算符
四个按位逻辑运算符都用于整型数据,包括char,针对每一个位进行;
1)按位取反~
该运算符不会改变原有的值,即
newval = ~val;(val的值不变)
2)按位与&
二元运算符&通过逐位比较两个运算对象,生成一个新值;
拓展:按位与和赋值结合的运算符:&=
val &= 0377;
val = val & 0377;
3)按位或|
二元运算符 | 通过逐位比较两个运算对象,生成一个新值;
拓展:按位或和赋值结合的运算符:|=
val |= 0377;
val = val | 0377;
4)按位异或^
二元运算符^逐位比较两个运算对象
a、b异或的比较结果:
a | b | result |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
0 | 0 | 0 |
拓展:按位异或和赋值结合的运算符:^=
val ^= 0377;
val = val ^ 0377;
2、移位运算符
1)左移<<
左移运算符(<<)将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数
左侧运算对象移出左末端位的值丢失,用0填充空出的位置。
(10001010) << 2 // 表达式
(00101000) // 结果值
2)右移>>
右移运算符(>>)将其左侧运算对象每一位的值向右移动其右侧运算 对象指定的位数。
对于无符号类型,用 0 填充空出的位置;对于有符号类型,其结果取决于机器。
特别的,移位运算符针对2的幂提供快速有效的乘法和除法:
number << n number乘以2的n次幂
number >> n 如果number为非负,则用number除以2的n次幂
3、常见用法
1)掩码(&)
指一些设置位开或关(1、0)的位组合:掩盖其他位,只显示特定位;
例如,MASK=2(00000010);
1号位为1,其他位为0;
flags = flags & MASK
作用:掩盖flags除1号位以外的位,都会为0,对于1号位,根据flags本身值决定;
常见:ch&=0xff;或者ch&= 0377
0xff是1111 1111;0377是000 011 111 111
这个操作会保持ch的最后8位保持不变
2)打开位(|)
值保持其他位不变,设置一个特定位
根据特性,任何位与1组合,为1(MASK可以有很多种可能);
例如,设置1号位为1,其他位保持不变;
MASK=0000 0010(0110 1111), flags= 0110 1101
(MASK设为1的位,flags对应设为1,MASK设为0的位,flags保持不变)
flags = flags | MASK;
结果为:0110 1111
3)关闭位(&~)
在不影响其他位的情况,关闭指定的位
当要关闭flags中的1号位,且MASK只有1号位为1,则有
flags = flags & ~MASK;
MASK 0000 0010
~MASK=1111 1101
结果 0000 1101
根据&~特性可以设计其他MASK的值
例如,
flags是0000 1111,MASK是1011 100。
flags = flags & ~MASK;
~MASK=0100 1101
结果:0000 1101
4)切换位(^)
可用于:打开关闭的位,关闭打开的位
将flag的与MASK为1的位对应切换,则有
例如,假设flags是0000 1111,MASK是1011 0110。表达式:
flags ^= MASK;
结果:1011 1001
flags中与MASK为1的位相对应的位都被切换了,MASK为0的位相对应 的位不变。
5)检查位的值
有时,需要检查某位的值。但又不能直接比较
常作用:覆盖flags中其他位,只用1号位做比较
if ((flags & MASK) == MASK)
四、位字段
操控位的第2种方法是位字段(bit field);
位字段是一个signed int/unsigned int类型变量中的一组相邻的位;
(C99和C11新增了_Bool类型的位字段);
位字段通过一个结构声明来建立,提供标签和宽度;
struct {
unsigned int autfd : 1;
unsigned int bldfc : 1;
unsigned int undln : 1;
unsigned int itals : 1;
} prnt;
可以通过普通的结构成员 运算符(.)单独给这些字段赋值:
prnt.itals = 0;
prnt.undln = 1;
带有位字段的结构提供一种记录设置的方便途径。许多设置(如,字体 的粗体或斜体)就是简单的二选一。例如,开或关、真或假。如果只需要使用 1 位,就不需要使用整个变量;
内含位字段的结构允许在一个存储单元中储存多个设置;
注意,如果声明的总位数超过了一个unsigned int类型,会用到下 一个unsigned int类型的存储位置;
一个字段不允许跨越两个unsigned int之间的边界;
编译器会自动移动跨界字段,并与边界对齐,那么第一个unsigned int会留下一个未命名的”洞“;
可以用未命名的字段宽度“填充”未命名的“洞;
struct {
unsigned int field1 : 1 ;
unsigned int : 2 ;
unsigned int field2 : 1 ;
unsigned int : 0 ;
unsigned int field3 : 1 ;
} stuff;
这里,在stuff.field1和stuff.field2之间,有一个2位的空隙;
另外,switch语句中也可以使用位字段成员,甚至还可以把位字段成员用作数组的下标: