正文:
1.数据类型介绍
前面我们已经了解到基本的内置类型:
char //字符数据类型 1个字节
short //短整型 2个字节
int //整型 4个字节
long //长整型 4或8个字节
long long //更长的整型 8个字节
float //单精度浮点数 4个字节
double //双精度浮点数 8个字节
类型的意义是:
(1)使用这个类型开辟内存空间的大小(大小决定了使用范围);
(2)决定了看待内存空间的视角;
1.1 类型的基本归类
(1)整型:
char //char虽然是字符类型,但是字符类型存储的时候,存储的字符的assic码值是整数unsigned char //无符号的charsigned char //有符号的char
//C语言未规定char c1时有无符号,取决于编译器。大部分编译器都默认char等价于signed char;shortunsigned short [int]signed short [int]
//规定short s1等价于signed short s1;intunsigned intsigned int
//规定int s1等价于signed int s1;longunsigned long [int]signed long [int]
//规定long s1等价于signed long s1;
(2)浮点数:
float
double
//在C99标准中,也有了long double类型
(3)构造类型(自定义类型):
//数组类型
//结构体类型 struct
//枚举类型 enum
//联合类型 union
(4)指针类型:
int* pi;
char* pc;
float* pf;
void*pv;
//指针变量就是用来存放地址的
(5)空类型
void表示空类型(无类型,通常应用于函数的返回类型、函数参数、指针类型)
2.整型在内存中的存储
2.1 原码、反码、补码
计算机中的整数有三种表示方法:原码、反码、补码。
三种表示方法都有符号位和数值位两部分,符号位0正1负,而数值位直接进行表示。
(1)负整数的三种表示方法各不相同:
原码:直接将数值按照正负数的形式翻译成二进制;
反码:符号位不变,其余位直接取反;
补码:反码+1;
(2)正整数原码、反码、补码都相同;
int main()
{int a = 10;//原码:00000000000000000000000000001010//反码:00000000000000000000000000001010//补码:00000000000000000000000000001010int b = -10;//原码:10000000000000000000000000001010//反码:11111111111111111111111111110101//补码:11111111111111111111111111110110return 0;
}
对于整型数据来说,内存中存放的是数据的补码。
原因:
比如我们计算1-1,CPU只有加法器,只能进行加法运算,故而转化为计算1+(-1),如果内存中存放的是数据的原码,则计算的是:
000000000000000000000000000000000001+100000000000000000000000000000000001=
100000000000000000000000000000000010即-2
故而用原码计算是错误的。
如果内存中存放的是数据的补码:
000000000000000000000000000000000001+1111111111111111111111111111111111111111=
100000000000000000000000000000000,此时最高位的1无处可存而丢失,答案为1,正确。
可以联想char类型:
再举一例:
2.2 大小端
大小端全称叫大小端字节序存储。
int a=0x11223344;
在VS中,采取小端字节序存储。
调试查看内存:
原因:
我们也可以写函数来判断大小端
例题:
例1:
解析:
-1原码为10000000000000000000000000000001
反码为1111111111111111111111111111111111110
补码为1111111111111111111111111111111111111
补码存在有符号char类型的a中去,char类型一个字节8个比特位,故而a中实际存的是11111111(低八位),而在打印中,%d打印的是有符号的整型,也就是四个字节,位数不够,故而需要整型提升,补的是原来符号的符号位,故而提升为1111111111111111111111111111111111111,打印有符号数,提升后仍未补码,故而求原码,先-1再保持符号位不变,其他位取反,最终结果为-1;
在VS中,char等同于signed char ,故而输出仍未-1;
而对于unsigned char 类型来说,得到全1的-1的补码,取低八位的11111111,%d打印有符号的整型,故而需要按照原来符号的符号位进行整型提升,原来符号类型为unsigned int,故而默认为内存中存放的是无符号数,高位直接补0,故而整型提升为00000000000000000000000011111111,提升后仍为补码,故而求原码,最高位为0为整数,原反补码相同,故而原码仍为该数,输出结果为255.
例2:
解析:
例3:
类似于例2易得运行结果,此处不再做详细解释。
例4:
解析:
例5:
注:代码运行速度过快,故而采用了Sleep
例6:
例7:
经过几道例题的演示,我们容易发现无符号类型如unsigned char、unsigned int 非常容易出现死循环,所以在我们写代码的时候,尽可能避开定义无符号类型。
3.浮点数在内存中的存储
浮点数包括float、double、long double等。
首先介绍两个文件:
(1)float.h:定义了浮点数取值范围的相关信息;
(2)limits.h定义了整型数据取值范围的相关信息:如:
举一例:
接下来详细阐述浮点数在内存中的存储:
根据国际标准IEE754,任意一个二进制浮点数V可以表示成下面的形式:(-1)^S*M*2^E
(-1)^S表示符号位,当S=0时,V为整数;当S=1,V为负数 ;M表示有效数字,M∈(1,2);2^E表示指数位
比如十进制浮点数5.5表示为二进制:小数点前5表示为101,小数点后位依次为2^-1,2^-2......故而5.5表示为101.1,化作标准格式为:(-1)^0*1.011*2^2
比如十进制的9.0表示为二进制为1001.1,转化为标准格式为:(-1)^0*1.001*2^3;
而并非所有的浮点数都能在内存中精确保存,可能需要小数点后很多位才能接近原本的浮点数,其实浮点数在内存中的存储是存在误差的。
在内存中存储浮点数时,存储的并不是完整的二进制序列,而是存储的SME。
IEEE规定,对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接下来的11位是整数E,剩下的52位为有效数字M。
(此处双精度浮点数存储模型不再做详细表示)
IEEE对于有效数字M和E还有一些特殊规定:
对于M,易知M首位总是1,故而在存储时可以舍弃首位的1,直接保存后面的数字,比如保存1.01时,就可以舍弃1,只保存后面的01,等到读取的时候,再把前面的1加上去。这样就可以节省1位有效数字。
对于E,
(1)存放E时:
E作为一个无符号整数,如果E是8位,则取值范围是0~255,如果E是11位,则取值范围是0~2047,但是我们知道科学计数法中的E可以是负数,所以存入内存时E的真实值必须加上一个中间数,对于8位的E,中间数为127,对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存为32位浮点数时,保存为127+10=137,即10001001。
我们可以进行验证:
调试查看内存:
(2)取出E时:
第一种情况:当E不全为0或不全为1时,内存中存的E减去127或1023,得到真实值,再将M前的有效数字1加上,就是初始值;
第二种情况:E全为0时,此时浮点数E的值等于1-127(或1-1023)即为真实值,有效数字M还原也不再加上1,直接还原储存的小数,这样做是为了表示±0以及无限接近0的数字。
第三种情况:E全为1时,此时如果数字M全为0,表示±∞,正负号取决于S。
在对整型和浮点数在内存中的存储有了基本了解后,现在来解决前文遗留的一个问题:
解析:
注意:当两个浮点数在内存中没有精确保存,那么这两个浮点数是不能直接用等号比较相等的。
比如:
int main()
{float f = 0.0001;if (f == 0.0){}return 0;
}
在上述代码中,f==0.0语句是不正确的,因为该浮点数在内存中的存储可能存在误差导致错误,在这种情况下,我们通常进行相减,判断差值是否在允许的范围内;