1、数据类型的介绍
- 类型的意义:
- 使用对应类型能开辟对应内存空间的大小(使用范围)
- 还有C语言对待不同类型,会采用不同的内存空间视角来看待一个数据
- C语言中类型的基本归类:
- 整型(内置类型)
- 浮点型(内置类型)
- 空类型(void)
- 指针型
- 构造型
- 数组类型
- 结构体类型struct
- 枚举类型enum
- 联合类型union
2、整型在内存中的存储
(1)原码、反码、补码
①原码的概念
将整数表示为一个二进制数(注意负数最前面的符号位为1),即可得到原码
②反码的概念
原码的符号位不变,其他位0->1,1->0,即可得到反码
③补码的概念
在反码的二进制数基础上+1,即可得到补码
- 三种表示方法均有符号位和数值位之分
- 其中符号位0表示“正”,1表示“负”
- 正整数的原、反、补码相同;负整数的原、反、补码不同
- 先对原码进行反码再补码<=>先对原码进行补码再反码
④整型以补码的形式存储在内存中
- 在计算机中,整数类型数值统一用补码表示和存储,原因在于可以将符号位和数值位统一处理
- 加法和减法也可以统一处理(CPU中只有加法器)
- 补码和原码相互转化,其运算过程是相同的,不需要额外的硬件电路
- 注意在内存窗口查看的时候会发现不是二进制而是十六进制,这个只是方便我们查看而已
(2)大小端的概念
①在内存中查看存储的数值
会发现00 00 00 64是被倒着放置的(小段模式)
②大端模式和小端模式
- 大端字节序存储模式:数据低位字节存在内存的高地址,数据高位字节存在内存的低地址
- 小端字节序存储模式:数据低位字节存在内存的低地址,数据高位字节存在内存的高地址
③出现大小端的原因
对于位数大于8位的处理器(如16位或者32位的处理器),由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式的出现。
(3)检验当前机器的大小端
//利用指针类型的转化可以检验大小端模式
int check_sys(void)
{
int i = 1;
return (*((char*)&i));
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
//另外一种写法,以后学习也可
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;
}
(4)一些练习代码
①第一题
//输出什么?
#include <stdio.h>
int main()
{
char a= -1;
signed char b=-1;
unsigned char c=-1;
printf("a = %d,b = %d,c = %d", a, b, c);
return 0;
}
这里就涉及到char的取值范围:
1、有符号char的取值范围[0 ~ 127]或者[ -128 ~ -1]即[-128 ~ 127],这里要注意-128的情况要理解成9位,不要把最高位理解成符号位,因为char只有8位被挤掉了……
2、无符号char的取值范围[0 ~ 255]
②第二题
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
- 原码:1000 0000 0000 0000 0000 0000 1000 0000
- 反码:1111 1111 1111 1111 1111 1111 0111 1111
- 补码:1111 1111 1111 1111 1111 1111 1000 0000
- 截断:1000 0000
- 提升:1111 1111 1111 1111 1111 1111 1000 0000
- 打印4294967168
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
- 原码:0000 0000 0000 0000 0000 0000 1000 0000
- 反码:0111 1111 1111 1111 1111 1111 0111 1111
- 补码:0111 1111 1111 1111 1111 1111 1000 0000
- 截断:1000 0000
- 提升:1111 1111 1111 1111 1111 1111 1000 0000
- 还是打印4294967168
③第三题
int i= -20;
unsigned int j = 10;
printf("%d\n", i + j);//这里即使是i是int类型的会被提升为无符号int,但是还是存得下,只不过无符号的int会把符号位当成有效位,%d输出的时候依旧是会把这个位当成符号位来看
//按照补码的形式进行运算,最后格式化成为有符号整数
- 原码:1000 0000 0000 0000 0000 0000 0001 0100(-20)
- 反码:1111 1111 1111 1111 1111 1111 1110 1011
- 补码:1111 1111 1111 1111 1111 1111 1110 1100
- 截断:1111 1111 1111 1111 1111 1111 1110 1100(int)
- 原、补、反码:0000 0000 0000 0000 0000 0000 0000 1010(10)
- 相加后就是:1111 1111 1111 1111 1111 1111 1111 0110
- 转化为补码:1111 1111 1111 1111 1111 1111 1111 0101
- 转化为原码:0000 0000 0000 0000 0000 0000 0000 1010
- 故打印出-10
④第四题
unsigned int i;
for(i = 9; i >= 0; i--)//由于无符号是没有负数的,就会导致unsigned int的类型是不存在负数的
{
printf("%u\n",i);
}
⑤第五题
int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d", strlen(a));
return 0;
}//会出现停止的情况,不会出现a[i]=-1000的情况,在a数组中只有值-1、-2、-3…-128、127、126…0、-1…而strlen会找到\0(即0)停止下来
3、浮点数在内存中的存储
(1)浮点型表示的范围和精度被定义在<float.h>中
(2)演示浮点数的存储例子
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
//*pFloat的值为:0.000000
//num的值为:1091567616
//*pFloat的值为:9.000000
//这一行代码揭示了浮点类型和整形的存储是完全不同的
(3)浮点数的存储
①先得到SME的真实值
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示为下面的形式:
- V= (-1)S * M * 2E
- (-1)S表示符号位,S=0,V为正数;S=1,V为负数。
- M表示有效数字,1<=M<2
- 2E表示指数位
对于例子5.5来说,写成二进制为101.1,相当于1.011*22,相当于(-1)0*1.011*22,即:S=0,M=1.011,E=2
②再得到SME的计算值
①对于存储32位浮点数的话就会1bit存储S,8bit存储E,23bit存储M(即1+8+23=32)
②对于存储64位浮点数的话就会1bit存储S,11bit存储E,52bit存储M(1+11+52=64)
因此双精度浮点数比单精度浮点数要来的准确。
- S:只用一个bit位存储,比较简单,直接进行存储
- M:由于1<=M<2,因此M总是写成1.xxxxxxx的形式(二进制没有2,只有1和0),所以这个1可以被忽略舍去,只保存后面的小数位即可,等到读取的时候,再把这个1加上去。这样做的目的:一是节省1位有效数字,可以保存24位(原本23位),多了一位就提高了精度。另外,如果M填不完就会在后面补0(同理64位也是这么操作的)
- E:而E的存储情况会比较复杂
- 首先E是一个无符号十进制整数,它的bit位为8时,取值0 ~ 255;它的bit为11位时,取值为0 ~ 2047,然而实际上科学计数法是有负指数的情况出现的(存在E<0的情况),因此需要设置一个中间数
- 用8bit存储E时,中间数为127(32位);用11bit存储E时,中间数位1023(64位)。比如210的E是10,存储进浮点数后就会变成E=10+127=137再存放到计算机里(如果值太小,加上127还是为负数,这就已经不是float能表示得了)
- 再将十进制的计算值的E转化为二进制
- 将S、M、E的值按照SEM的顺序连接存储起来即可
(4)浮点数的取出
浮点数被存储进了内存,现在计算机要把这个浮点数重新取出来,而取出来的话,主要的难点还是体现在E的问题上
- 当计算值E不全为0或不全为1
- 把E的计算值减去127(或者1023)得到原来的真实值
- 再将有效数字M前加上被忽略的1
- 当计算值E全为0
- 说明原本的E真实值是-127(或者-1023),这个数实在是太小了。因此直接定义这时E的真实值就是1-127(或者1-1023)
- 有效数字M也不再还回1,而是直接还原为0.xxxxxxx这样的小数
- 以上做法是为了表示+/-0,以及接近于0的很小的数字
- 当计算值E全为1
- 说明原本的E真实值是128(或者1024),这个数就是+/-无穷大了
(5)利用规则解释前面的例子
- 下面,让我们回到一开始的问题:为什么9还原成浮点数,就成了 0.000000 呢?
- ①首先,将9转化为二进制进行拆分,在32位0 00000000 000000000000000 00001001得到第一位符号位s=0,后面8位的指数 E=00000000,
最后23位的有效数字M=000 0000 0000 0000 0000 1001 - ②由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:V=(-1)0×0.00000000000000000001001×2(-126)=1.001×2(-146)
- ③显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000
- ①首先,将9转化为二进制进行拆分,在32位0 00000000 000000000000000 00001001得到第一位符号位s=0,后面8位的指数 E=00000000,
- 再看例题的第二部分。请问浮点数9.0,如何用二进制表示?还原成十进制又是多少?
- ①首先,浮点数9.0等于二进制的1001.0,即1.001×23。9.0 -> 1001.0 ->(-1)0×1.00123 -> 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 00100000000000000000000
- ④这个32位的二进制数,还原成十进制,正是 1091567616
噢对了,要注意补码的问题。我就老是忘记呢……上面的9是正数,原码就是其补码,故讨论起来少了转化成补码的一步。如果是负数,就一定要先转化成补码再讨论