入门学习计算机第十五天—数据的存储
编译器:Microsoft Visual Studio 2010
前言
记录第十五天学习C语言的博客。
深度剖析数据在内存中的存储
数据类型介绍
内置类型:
char 字符数据类型
short 短整型
int 整型
long 长整型
long long 更长的整型
float 单精度浮点数
double 双精度浮点数
类型的意义:
1、使用这个类型开辟内存空间的大小。(大小决定了使用范围)
2、如何看待内存空间的视角。
如何理解2,例子:
int main()
{
int a = 10;
float f =10.0;
return 0;
}
调试状态下,调出内存窗口,取出a的地址
取出f的地址
int类型和float类型的存储值的方式不同。所以就是看待内存空间的视角不同。
整型家族:
char
unsigned char 无符号的char(范围是0 - 255)
signed char 有符号的char(范围是-128 - 127)
short
unsigned short [int] 无符号的short
signed short [int ] 有符号的short
int
unsigned int 无符号int
signed int 有符号int
long
unsigned long [int] 无符号long
signed long [int] 有符号long
浮点型家族
float
double
构造类型:
- 数组类型 eg:int [10] char[5]
- 结构体类型 struct
- 枚举类型 enum
- 联合类型 union
指针类型:
int* pi
char* pc
float* pf
void* pv
空类型:
void 表示空类型(无类型)
通常引用于函数的返回类型,函数的参数,指针类型
整型在内存中的存储
int a = 20;
int b =-10;
a分配了四个字节的空间,那如何存储?
b也分配了四个字节的空间
了解以下的概念:
计算机中的有符号数有三种表示方法,即原码,反码,补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。
有符号正数,无符号数原码,反码,补码相同。
原码
直接将二进制按照正负数的形式翻译成二进制就可以。
反码
将原码的符号为不变,其他位依次按位取反就可以得到。
补码
反码+1得到补码。
int a = 20;
//a是正整数,原反补相同
//00000000000000000000000000010100 - 原码
//00000000000000000000000000010100 - 反码
//00000000000000000000000000010100 - 补码
//二进制转化为十六进制,每四个二进制转换位一个十六进制位
//0000 0000 0000 0000 0000 0000 0001 0100
//0x00000014
int b = -10;
//10000000000000000000000000001010 - 原码
//11111111111111111111111111110101 - 反码
//111111111111111111111111 1111 0110 - 补码
//0xFFFFFFF6
对于整型来说,数据在内存中其实存放的是补码
为什么呢?
使用补码,可以将符号位和数值统一处理,同时加法和减法也可以统一处理(CPU只有加法器),此外补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
eg:好比电脑计算1-1,会先转化为1+(-1)
1的补码:00000000000000000000000000000001
-1的补码:11111111111111111111111111111111
相加:100000000000000000000000000000000,33位了,舍弃最高位。
结果是:00000000000000000000000000000000
了解到了a =20 存储在内存中是0x00000014,但是为什么倒着顺序放的?
再了解以下概念:
大小端:
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低址中。
小端(存储)模式:是指数据的地位保存在内存的低地址中,而数据的高位,保存在内存的高位中。
通过这样的概念,&a会发现
处于低位的14存储在了低地址处,所以是小端存储模式。
设置一个代码,告诉我们当前机器的字节序是什么?
int check_sys()
{
int a = 1;
return *(char*) &a;//只访问a的第一个地址,如果为1,则说明是小端,如果为0,则是大端。
}
int main()
{
int ret = check_sys();
if(ret == 1)
printf("小端");
else
printf("大端");
return 0;
}
以下代码输出的结果是?
int main()
{
char a =-1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d b=%d c=%d\n");
return 0;
}
输出的结果是-1,-1,255
为什么?
char a =-1;
//10000000000000000000000000000001 - 原码
//11111111111111111111111111111111 - 补码
//因为a只能存储8个字节
//11111111 当要输出a为整型时,a要进行整型提升,前面补符号位
//11111111111111111111111111111111 所以输出的是-1
signed char b =-1;//b与a同理
unsigned char c = -1;
//11111111111111111111111111111111 - 补码
//c也只能存储8个字节
//11111111 当要输出c为整型时,c要进行整型提升,但是c是无符号,所以补0
//00000000000000000000000011111111 - 无符号数,补码原码相同,所以答案是255。
2、以下代码输出的结果是?
int main()
{
char a = -128;
printf("%u",a);
return 0;
}
输出的结果是:
int main()
{
char a = -128;
//10000000000000000000000010000000 -原码
//11111111111111111111111101111111 -反码
//11111111111111111111111110000000 -补码
//10000000 char类型只能存储2个字节 8个bit
//11111111111111111111111110000000 整型提升,有符号数要补符号位
//但是输出的是十进制无符号的整型
printf("%u",a);
return 0;
}
所以就是上述结果。
!!!!!!要记住
当char a = 128时,char类型是不能存储128,但是可以把128看作127+1,等于-128
int main()
{
char a = 128;
printf("%u",a);
return 0;
}
所以输出的结果依然是:
还有一题:
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i+j);//按照补码的形式进行运算,最好转化为有符号整数
return 0;
}
//11111111111111111111111111101100 - 20的补码
//00000000000000000000000000001010 - 10的原码
//11111111111111111111111111110110 - 相加
//111111111111111111111111111110101 - 反码
//1000000000000000000000000000001010 - 原码
结果就是-10
又来一题
int main()
{
unsigned int i;
for(i = 9; i>=0; i--)
{
printf("%u\n",i);
}
return 0;
}
输出的结果是9,8,7,6,5,4,3,2,1,0,死循环。
因为i是unsigned int类型,永远>=0
居然还有题目:
int main()
{
char a [1000];
int i ;
for(i =0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d\n",strlen(a));
return 0;
}
输出的结果是255
需要结合上图:
当i =0时,结果是-1, i =1,结果是-2以此类推,到了i=127时,结果是-128, i = 129,结果会变成127
再依次类推到 3,2,1,0。strlen遇到’\0’就会停止,由于是char类型的数组,’\0’的ASCII码值为0,所以到0时,strlen就停止了计数。不算上0,一共是255个元素。
555555这个题做不完了:
以下输出的结果是什么:
int main()
{
unsigned char i =0;
for(i = 0; i<= 255; i++)
{
printf("hello\n");
}
return 0;
}
输出的结果是死循环的hello,unsigned char的取值范围是0~255,所以i<=255,恒成立。
浮点型在内存中存储
常见的浮点数:
3.1415 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("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
输出的结果
与想象中区别非常大。为什么呢?需要了解浮点数的存储数据的方式。
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数
M表示有效数字,大于等于1,小于2
2^E表示指数位
举例来说十进制9.0,先转化为二进制1001.0 ,再用科学计数法表示为1.001 * 2^3
为正数,所以是(-1)^0 * 1.001 * 2 ^ 3
S为0 ,M为1.001 , E为3
综合上面的存入浮点数方法可知,float a =9.0可以表示为:
0100 0001 0001 0000 0000 0000 0000 0000
转换为十六进制
0x41100000
因为当前机器为小端存储模式,低位数字存在低地址,高位数字存在高地址。
浮点数取出:
当有一个二进制数:0100 0001 0001 0000 0000 0000 0000 0000
当E不为全0或者全1时:
这时浮点数就采用下面的规则表示:即指数E的计算值减去127(或者1023),的到真实值,再将有效数字M前加上第一位的1。
E为全0时:
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxxxxxx的小数。这样做是为了表示±0,以及接近0的很小的数字。
E为全1时:
这时,如果有效数字M全为0,表示±无穷大(正负号取决于符号位S)
再次回到浮点数9.0的问题
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值位:%f\n",*pFloat);
*pFloat = 9.0;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
int a =9,在内存中二进制为
0 00000000 00000000000000000001001
E为全0,真实值为1-127=-126
转换为十进制数就为(-1)^0 * 0.00000000000000000001001 * 2^(-126),这个值无限接近于0,所以输出的时候为0.000000
因为浮点数9.0在内存中二进制为0100 0001 0001 0000 0000 0000 0000 0000,用十进制的方式输出就是