目录
1.3 有符号(signed)与无符号(unsigned)的区别
一、数据类型介绍
1.1 基本内置类型
前面我们已经接触了 C语言的基本的内置类型
char//字符数据类型 1字节short//短整型 2字节int//整形 4字节long//长整型 4/8字节longlong//更长的整形 8字节float//单精度浮点数 4字节double//双精度浮点数 8字节
注意:C语言没有字符串类型,long类型的字节数 >= int类型的字节数
这些类型的意义
- 使用这个类型开辟内存空间的大小(大小决定了使用范围)
- 如何看待内存空间的视角
----------------我是分割线---------------
1.2 类型的基本归类
(1)整形家族
charunsignedchar//无符号signedchar//有符号shortunsignedshort [int] signedshort [int] intunsignedintsignedintlongunsignedlong [int] signedlong [int] longlongunsignedlonglong [int] signedlonglong [int]
说明:char 类型的字符本质是 ASSCII码值,是整数,所以划分到整型家族
(2)浮点数家族
1. float 2. double
(3)构造类型
构造类型也叫自定义类型
数组类型结构体类型struct枚举类型enum联合类型union
(4)指针类型
int*pichar*pcfloat*pfvoid*pv
(5)空类型
1. void 表示空类型(无类型) 2. 通常应用于函数的返回类型、函数的参数、指针类型。
1.3 有符号(signed)与无符号(unsigned)的区别
首先在计算机中,有符号数是可以用来区分数值的正负,而无符号数仅有正值,没有负值
其次当一个数是无符号数时,它的最高位仅用来表示该数的大小。而当一个数是有符号数时,此时的最高位称为符号位;该符号位为1时表示该数为负值,为 0 时则表示为正值
最后有符号数和无符号数两者表示的范围不同,即同样长度的字节,有符号数比无符号数的最大值出现缩水
二者最明显的区别就是二者表示的范围不同:
- 无符号数中,所有的位都用于直接表示该值的大小
- 有符号数中最高位用于表示正负,所以,当为正值时,该数的最大值就会变小
例如:无符号的 char(unsigned char),char 占用一个字节,也就是 8 bit,无符号最高位仅用来表示该数的大小
000000000000000100000002... ... 1111111011111111
1111 1111 转换成十进制就是 255,所以无符号 char 的范围是 0~255
下面看有符号的 char(signed char),是有符号数时,此时的最高位称为符号位;该符号位为1时表示该数为负值,为 0 时则表示为正值
00000000(内存存的都是补码)0000000100000002... ... 011111111000000010000001... ... 1111111011111111
0111 1111 转换成十进制就是 127,1000 000 的最高位是符号位,1000 0000不会对它进行计算,计算机直接解析成 -128,1000 0001就是 -127,1111 1111 就是-1,所以有符号char 的范围是 -128~127
其他类型以此类推,可以计算出各种类型的大小
注:我们平常写的char、int...这些默认为有符号类型
----------------我是分割线---------------
说明:limits.h 定义了整型数的取值范围的相关信息,需要的可以到里面查看
二、整形在内存中的存储
之前讲过一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的
那接下来我们谈数据在所开辟内存中到底是如何存储的
例如:
int a = 20;
我们都知道为 a 开辟了四个字节的空间,那么 a 是如何存储的?
2.1 原码、反码、补码
原码、反码和补码,这个已经在操作符那里详细解释过了,这里就不再详细解释了,就简单介绍规则。
计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位负整数的三种表示方法各不相同
原码:直接根据数值写出的二进制序列就是原码(如果是32位平台,就是 32 位二进制序列)
反码:原码的符号位不变,其他位按位取反就是反码
补码:反码加1,就是补码
正数的原、反、补码都相同,负数的原码、反码、补码都不相同
对于整形来说:数据存放内存中其实存放的是补码
为什么以补码的形式储存呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理,同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
解释:
(1)加法和减法也可以统一处理,就是说减法也可以写成加法, 如1-1,可以写成 1+(-1)
1的补码:00000000000000000000000000000001-1的原码:10000000000000000000000000000001-1的反码:11111111111111111111111111111110-1的补码:11111111111111111111111111111111
进行相加
1的补码:00000000000000000000000000000001-1的补码:11111111111111111111111111111111——————————————————————————————————————————————————100000000000000000000000000000000
进行相加之后,超出了一位,舍弃,因为只有 32个比特位,在32位平台下,只保留 32个比特位
0000 0000 0000 0000 0000 0000 0000 0000
十进制也是 0(正数的原、反、补码都相同)
(2)补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路,如 -1
-1:-1的原码:10000000000000000000000000000001-1的反码:11111111111111111111111111111110-1的补码:11111111111111111111111111111111
补码反推回原码,我们也都会,把步骤逆过来就可以了
-1的补码:11111111111111111111111111111111-1的反码:11111111111111111111111111111110(补码-1)-1的原码:10000000000000000000000000000001(反码取反)
2.2 大小端
先看一段代码
intmain() { inta=20; return0; }
在调试模式下查看内存
20转换成十六进制是 0x00000014,但是图中的地址为什么倒着存储呢?
下面引进大小端的介绍
2.2.1 什么是大小端
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中
如图所示:
2.2.2 为什么有大端和小端
为什么会有大小端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit 的 long 型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式
例如:一个 32 bit 的 short 型 a ,在内存中的地址为 0x00000010 , a 的值为 0x00001122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,0x22 放在高地址中;小端模式,刚好相反
我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式
2.2.3 大小端的判断
写程序判断其实也是这样的思路:
想办法取出一个字节的内容,就可以知道是哪种存储方式。比如,图上,0x12345678 是4个字节,我们只要取出它的第一个字节内容,如果是78,则说明是小端存储;反之是大端
用指针的办法:把变量强制类型转换为char*,这样就可以每次取出一个字节的内容
代码如下:
intcheck_sys() { inta=1; //为了方便,以1为例,也可以使用其它数char*p= (char*)&a; return*p; } //int check_sys()//{// int a = 1;// return *(char*)&a;//简化//}//intmain() { intret=check_sys(); if (ret==1) { printf("小端\n"); } else { printf("大端\n"); } return0; }
还有另一个方法 联合(union) 的知识可以判断大小端,这里不介绍了
----------------我是分割线---------------
三、浮点型在内存中的存储
常见的浮点数:3.14159、1E10(科学计数法表示)
浮点数家族包括: float、double、long double 类型
浮点数表示的范围:float.h 中定义
3.1 例子
先看一个例子
intmain() { intn=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); return0; }
运行结果
后面进行解释,下面解释浮点数的存储规则
3.2 浮点数存储规则
上面的结果说明了整数和浮点数的存储不大相同,要理解这个结果,就要搞懂浮点数在计算机内部的表示方法
根据国际标准IEEE(电气和电子工程协会) 754标准,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s 表示符号位,当s=0,V 为正数;当 s=1,V 为负数
M 表示有效数字,大于等于 1,小于2(1 <= M < 2)
2^E 表示指数位
比如,一个浮点数为 V = 5.0,转换成 754标准:
V=5.0;5.0->110.0(二进制表示)->1.1*2^2(科学计数法表示)注:科学计数法类比十进制,比如123.45,科学计数法表示为:1.2345*10^21.1*2^2用754标准表示V=5.0;=1.1*2^2= (-1)^0*1.1*2^2S=0M=1.1E=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
再举栗一个:
说明:小数点后面的权重是从 -1 开始的,比如 0.abc,第一位的权重是 a,二进制表示为 2^(-1) ,第二位的权重是 b,二进制表示为 2^(-2) ,第三位的权重是 c,二进制表示为 2^(-3)
用二进制表示一些小数的时候,我们发现要用很多位二进制来表示那个小数,比如十进制的 0.999,小数部分用二进制表示就会很长,而32位下的机器浮点数只有 32个bit,完全放不下。
这时候就会发生精度丢失,把超过 32个bit 后面的二进制数舍弃,这样小数的精度就不准确了
float-4Byte-32bitdouble-8Byte-64bit
下面回归正题
看一个例子
intmain() { floatf=5.5; //5.5//101.1//1.011*2^2//s=0 m=1.011 e=2//0 10000001 01100000000000000000000 (32位下二进制表示)//(-1)^0 * 1.01100000000000000000000 * 2^2// // 0100 0000 1011 0000 0000 0000 0000 0000//0x40 b0 00 00(十六进制表示)return0; }
调试下查看
我们发现,浮点数存储确实是这样子
下面还有一些 754标准注意事项
IEEE 754对有效数字M和指数E,还有一些特别规定
M: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从内存中取出还可以再分成三种情况:
(1)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
(2)E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字
(3)E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
----------------我是分割线---------------
3.3 解释前面 3.1 的题目
为什么 0x00000009 还原成浮点数,就成了 0.000000 ?
1. int 9的补码二进制表示 2. 9 -> 0000 0000 0000 0000 0000 0000 0000 1001
首先,将 int 9 的补码二进制存储看成浮点数存储的,强制类型转换后的二进制表示,得到第一位符号位 s=0,后面8位的指数 E=00000000 ,最后23位的有效数字 M=000 0000 0000 0000 0000 1001
00000000000000000000000000001001
由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:V = (-1)^0 * 0.00000000000000000001001 * 2^(-126) = 1.001 * 2^(-146)
显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000
再看例题的第二部分,浮点数9.0,如何用二进制表示?还原成十进制又是多少?
首先,浮点数9.0等于二进制的1001.0,即1.001×2^3
9.0 -> 1001.0 ->(-1)^01.0012^3 -> s=0, M=1.001,E=3+127=130
那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010
所以,写成二进制形式,应该是s+E+M,即
0 10000010 001 0000 0000 0000 0000 0000
这个32位的二进制数,还原成十进制,正是 1091567616
第四个就不解释了
----------------我是分割线---------------
文章就到这里