目录
-
数据类型介绍
前边我们已经学习了一些基本的数据类型:int 、char、double……
按照其数据类型进行归类:
整形家族:
char:unsigned char、signed char
short:unsigned short、signed short
int:unsigned int、signed int
long(long int):unsigned long、signed long
long long:unsigned long long、signed long long
浮点数家族:
float、double、long double
构造类型:
数组类型
结构体类型(struct)
枚举类型(enum)
联合类型(union)
指针类型:
char*pc、int*pi、float*pf、void*pv……
空类型(void):
常应用于函数的返回类型、函数参数、指针类型。
对于整形数据要注意其存储数据的范围
首先要知道signed、unsigned的区别:
对于有符号数据(signed xxxx):二进制最高位仅表示数据的正负(0表示正、1表示负)
对于无符号数据(unsigned xxxx):二进制所有位都参与表示数据大小
以char类型为例:
char类型数据二进制表示:0000 0000、0000 0001、0000 0010、……、0111 1111、1000 0000、1000 0001、……、1111 1111
对于signed char:因为最高位仅表示正负,则从0000 0000到0111 1111表示正数(十进制0~127),从1000 0000到1111 1111表示负数,要先转化原码再求值(十进制-128~-1,理论上1000 0000应该表示-0,为了避免浪费,规定1000 0000表示-128),如果1111 1111再加1就会变成0000 0000(十进制0),即signed char表示的数据范围为-128~127;
对于unsigned char:因为最高位参与表示数据大小,则从0000 0000到0111 1111表示正数(十进制0~127),从1000 0000到1111 1111表示负数(十进制128~255),如果1111 1111再加1就会变成0000 0000(十进制0),即unsigned char表示的数据范围为0~255;
可以发现他们的都可以形成一个循环,画图表示出来;
-
整形数据在内存中的存储:
首先要了解原码、反码、补码的概念:
原码:
直接将数字按正负数形式翻译成二进制。
反码:
原码符号位不变,其余各位安位取反。
补码:
反码+1。
对于正数:原码=反码=补码;
对于负数:补码=原码取反+1、原码=补码取反+1、原码=(补码-1)再取反。(符号位不能变,负数原码是通过求其对应正数原码,再把最高位变为1得到)
对于整型数据来说:数据在内存中以补码形式存储(可以将符号位和数值域统一处理,同时加减法也可以统一处理)。
以int型1(二进制补码00000000 00000000 00000000 00000001-->十六进制补码00000001)、-1(二进制补码11111111 11111111 11111111 11111111-->十六进制补码FF FF FF FF(ff ff ff ff))为例:
可以发现-1在内存中确实是存储FF FF FF FF(ff ff ff ff),但是1在内存中的存储与事先推出的以 00 00 00 01形式存储有所不同,这是为什么呢?
这是因为大、小端的存在,导致数据存储时,会与预先推测不同。
-
大、小端介绍:
大、小端(大、小端字节序)指的是数据在电脑上存储的字节顺序,顾名思义,其是以字节为单位存储(具体存储方式与电脑本身有关)。
大端存储:把数据的低字节内容存放到高地址,高字节内容存放在低地址处;
小端存储:把数据的高字节内容存放到高地址,低字节内容存放在低地址处。
知道了大小端的存在,那么前边1在存储时出现的问题就迎刃而解了,可以看出我用的电脑是小端存储。
因为-1十六进制补码全为f,所以无论是大端存储还是小端存储其显示出来都一样,但是1的十六进制补码为00 00 00 01,按小端存储应为01 00 00 00。
我们不妨再用20测试一下,20的二进制为00000000 00000000 00000000 00010100,十六进制为00 00 00 14,那么按照小端存储应为14 00 00 00
可以发现与推测结果相同。
那么该如何判断自己的电脑是什么存储方式呢?
可通过编写一段代码来实现:
int b_s()
{
int i = 1;
return (*(char*)&i);//利用强制类型循转换,只截取第一个字节的内容
}
int main()
{
int a = b_s();
if (a == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
创建一个int型数据 i =1,然后通过强制类型转换把 &i 转换成char*型,再解引用访问 i 在内存中存储的第一个字节的数据,若该位为1,则为小端存储,否则为大端存储。
-
浮点型数据在内存中的存储:
在了解浮点数的存储前,我们先来看一个例子:
#include<stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
printf("n=%d ", n);
printf("*p=%f\n", *p);
*p = 9;
printf("n=%d ", n);
printf("*p=%f\n", *p);
return 0;
}
会输出什么呢?
运行结果:
为什么会出现这样的结果呢?
首先我们知道浮点数肯定也是以二进制形式存储的,但他具体是如何存储的?是和整型数据的存储一样吗?
浮点数转换为二进制表示:
根据国际标准IEEE754,任意二进制浮点数N可以表示为以下形式:
- N=(-1)^S*M*2^E
- (-1)^S表示符号位,当S=0时,表示N为正数;当S=1时,表示N为负数
- M表示有效数字,一般情况下1<=M<2
- 2^E表示指数位
以9.0为例来说:9.0-->1001.0-->1.001*2^3-->(-1)^0*1.001*2^3 则S=0、M=1.001、E=3。
再以-5.5为例:首先要知道小数点后的计算权重为2^(-n),n表示第几位小数
-5.5-->-101.1-->-1.011*2^2-->(-1)^1*1.011*2^2 则 S=1、M=1.011、E=2。
可以发现其转换方式与十进制的科学计数法类似。
说完浮点数如何转化为二进制,再来说说他是如何存储的
浮点数的存储:
IEEE754规定:
对于32位浮点数,最高1位存储符号位S,接着8位存储指数E,剩下23位存储有效数字M
对于64位浮点数,最高1位存储符号位S,接着11位存储指数E,剩下52位存储有效数字M
在实际存储时,IEEE754对M和E还有一些特别的规定。
对于有效数字M:
前边说过1<=M<2,所以M可以写成1.xxxxxx的形式,在实际存储时,默认M第一位为1,因此直接舍去,只存储小数部分xxxxxxx,读取数据时取出小数部分再加上1即可。例如对于9.0(M=1.001),存储M时只存储001,后边空位补0,即001 0000 0000 0000 0000,这样做可以使23位(52位)的M可以多存储一位有效数字。
对于指数E:
存储E时:E为一个无符号数字。
E的取值范围0~255(0~2047),如果不加以处理,E只能为正数,无法表示负数,但是实际情况中存在E为负数的情况,因此就要在存储时对E进行处理。
IEEE754规定:在存储E时必须加上一个中间值127(1023)。例如9.0(E=3),存储时存储3+127=130,即1000 0010
读取E时:
- E不全为0或不全为1:
取出指数E的值减去127(1023)得到真实值E,然后取出M再加上1,求出M*2^E后再看符号位确定符号即可得到最后结果。
- E全为0:
此时浮点数E的真实值为1-127=-126(1-1023=-1022),有效数字不再加上1,直接还原为0.xxxxx
这样处理是为了表示±0(正负号由S决定),以及很接近0的数
- E全为1:
此时如果有效数字M全为0,表示±∞(正负号由S决定),如果M不全为0,表示这个数不是一个数(NaN)。
现在来解释解释为啥会出现下边的情况
int型9在内存中存储为00000000 00000000 00000000 00001001,当把它强制转换为浮点型后,存储的存储的二进制形式不变,但是各位的意义发生了改变
符合E取出规则的第二种,则转换后为(-1)^0*0.00000000000000000001001*2^(-126)=1.001*2^(-146),显然是一个及其接近0的数,而float只能精确到小数点后6位,所以打印出来为0.000000。
下边通过指针把n里边的内容修改为了一个浮点数9.0((-1)^0*1.001*2^3),那么存储的应该为 0 10000010 0010000 00000000 00000000,当把其按照整型还原时应为2^30+2^24+2^20=1091567616,所以以整型打印n时打印出1091567616。
因此平常练习时,要注意数据的类型。