char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
char
unsigned char //归为整型其中一种类型
signed char //内存大小为一个字节
//C语言规定中并未明确指出char的类型是有符号或无符号的
//但大部分编译器的环境中是有符号的
short
unsigned short [int] //较短的int 内存大小为两个字节
signed short [int]
int
unsigned int //内存大小为四个字节
signed int
long
unsigned long [int] //四个字节
signed long [int] // long long为八个字节(vs环境下)
float //四个字节
double //八个字节
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
int *pi;
char *pc;
float* pf;
void* pv;
//指针内存大小视平台而定
//32位平台4个字节 64位平台8个字节
void空类型(无类型),主要用于函数的返回类型、函数的参数、指针类型。
这里先看两个例子,在编译器中创建这两个整型并赋值,开始调试并调用内存监视窗口,看看这两个量是如何存储在内存中的。
int a = 20;
int b = -10;
在内存中存储的值是以二进制的,这里的监视窗口为了方便观察是转换成十六进制的,读者自行验证是可自行转换成二进制。首先我们可以看到a在内存中的值是0x00 00 00 14,转换成十进制恰好是20,那么数据在在内存中的存储是否就是像这样简简单单将值转换成二进制在进行存储的呢?然而当我们看下一个数字时,我们就会发现事情没简单。内存中的值是0xf6 ff ff ff,明显不是-10转换成十六进制的数字。而且这是负数的存储值出现了问题,说明负数在存储时别有一番讲究,而这个讲究就是我们马上要提到的原反补。
计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,符号位在最高位。
原码
直接将二进制按照正负数的形式翻译成二进制就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1就得到补码。
而在计算机系统中,数值一律用补码来表示和存储。因为cpu只有加法器,使用补码,加法和减法也可以统一处理,而使用补码还可以将符号位和数值域统一处理,此外补码与原码的相互转化的运算过程时一样的(补码转原码也可以原反补),设计上可以省去额外的硬件电路。
现在,再让我们看之前的两个内存中的值,a是20,正数,原反补相同,因此内存中存储的是0x00 00 00 14,没问题。再来计算b:
b=-10
原码:10000000000000000000000000001010
反码:11111111111111111111111111110101
补码:11111111111111111111111111111010
转换成十六进制:0xf6 ff ff ff
与内存中存储的数值相同
在刚刚的计算中其实我们一直忽视了一个件事,当我们在vs编译器上测试我们的数据时,我们通过内存监视可以看到数据的值事这样存储的:14 00 00 00,而我在讲解时写的标准十六进制表达是这样的:0x00 00 00 14,你会发现,vs编译器将高位数存储在高地址处,低位数存储在低地址处。而我们在书写一个数字时通常会遵从一种顺序,按书写习惯通常是左边为高位数,右边为低位数,古代文书纸币通常是上边是高位,下边是低位。
然而对于计算机来说,存储位置可没有上下左右之分,只有高低之分,并且,因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位 的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排高低位的问题。所以数据存储存在两种模式:
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
常见的浮点数:
3.14159
1E10(科学计数法)
浮点数家族包括: float、double、long double 类型。
浮点数表示的范围:float.h中定义(编译器的配置文件,可自行搜索)
下面通过一个例子讲讲浮点数的存储:
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
(建议在查看结果之前自行思考三五分钟,思考一下大概是什么值,想不出来没关系,只是为以下讲解做铺垫)
前面我们说过,类型决定看待内存中存储内容的角度,而这个例子中,开始,n的值是9,内存中的值是0x00 00 00 09;当以%d的形式打印时是以整型的角度来看待这块空间的,打印出来的内容就是9,而当以float的类型取出,并以%f的形式打印则以浮点型的角度看待这块空间,打印出来的值是0.000000,对于同样大小的空间,同样的内容以两个不同的角度看待,得到的是不一样的值
同样,当我们使用float的类型去改变n中存储的值,虽然是同样大小的数值,改变后的内容却是和原来完全不一样,当我们再次以%d的形式看待时会得出来一个非常大的奇怪的值,而当以%f的形式看待时却会得到刚刚赋值的9.0.
看完这些相信你一定会很好奇究竟浮点型是如何看待和使用内存的内容的。接下来就进行规则解释
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 754对有效数字M和指数E,还有一些特别规定。
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
然后,指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为
1.0*2^(-1),其阶码为-1+127=126,表示为01111110,
而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为: 0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。
好了,现在让我们回到开头讲的那个例子,对于内存中存的是0x00 00 00 09时,用浮点型的视角看,先看E,发现是全零,无穷小,所以打印成0.000000,对于改变内存中的值成9.0时,先计算一下ESV:
9.0
-> 1001.0
->(-1)^01.0012^3
-> s=0, M=1.001,E=3+127=130
写成二进制:0 10000010 001 0000 0000 0000 0000 0000
这个二进制数还原成十进制正好是之前提到的那个奇怪的数字。
4.结语
非常感谢各位读者能读完这篇文章,如果你觉得做的还不错的话,可以点赞收藏分享,让更多的朋友知道,当然,如果你觉得有什么问题的话也欢迎在评论区留言或私信告诉我哦!下期再会!