总言
C语言:数据存储相关介绍。
文章目录
- 总言
- 1、基本数据类型介绍
- 1.1、整体介绍
- 1.2、各数据类型分别说明(整型、浮点型、构造、指针、空)
- 2、整型在内存中的存储
- 2.1、原码、反码、补码
- 2.1.1、总体介绍
- 2.1.2、char、short类型在内存中的存储范围
- 2.2、大小端字节序存储
- 2.3、相关练习
- 2.3.1、练习一
- 2.3.2、练习二
- 2.3.3、练习三
- 2.3.4、练习四
- 2.3.5、练习五
- 2.3.6、练习六
- 2.3.7、练习七
- 3、浮点型在内存中的存储
- 3.1、问题引入
- 3.2、浮点数存储规则
- 3.2.1、浮点数的表示形式(一):基本认知
- 3.2.2、浮点数的表示形式(二):如何将数据放入
- 3.2.3、浮点数的表示形式(三):如何将数据取出
- 3.3、3.1中问题说明
1、基本数据类型介绍
1.1、整体介绍
我们曾在初识C语言一章中简单介绍过C中内置类型以及它们的存储大小。
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整型
float //单精度浮点数
double //双精度浮点数
不同类型的意义:
1、 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
2、 如何看待内存空间的视角。
1.2、各数据类型分别说明(整型、浮点型、构造、指针、空)
1)、整型家族
char
unsigned char
signed char
short
unsigned short[int]
signed short[int]
int
unsigned int
signed int
long
unsigned long[int] //括号表示可以省略int简写
signed long[int]
说明:
1、整数的各个类型分为有符号和无符号。有符号无符号的区别: 最高位为符号位还是数据位。有符号类型最高位为符号为,0表示正数,1表示负数。
2、char
字符类型归类为整型的原因: 字符类型数据存储时的是字符的ASCII码值,ASCII码是整数。
3、一般情况下,默认整形为有符号整形。由此衍生了英文字母的一些简写。当在无符号类型的变量中存入负数时,会根据一定的规律转化为对应的无符号变量(此处不能单纯的认为打印出的数据为去掉负号后的值)
short == signed short ;
int == signed int ;
long int == signed long int == signed long ;
4、简写情况下,char
类型是有符号还是无符号: C语言并没有对char做明确规定,char默认类型取决于编译器,常见的编译器下为signed char
5、关于以有符号的%d解读无符号整数错误的原因: 解读补码的视角不同。
2)、浮点型家族
float
double
说明:
1、flaot
单精度浮点类型,占4个字节;double
双精度浮点类型,占8个字节 (32位)
3)、构造类型(自定义类型)
> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union
说明:
1、数组的类型是去掉数组名后剩余部分。如下述,为不同类型的两个数组。当数组元素类型、元素个数发生变化,能得到不同类型的数组。
int arr[10]; //arr类型为: int [10]
int arr2[4]; //arr2类型为: int [4]
int arr[ ] = { 0, 0, 0}; //arr类型为:int [3]
4)、指针类型
int *pi;
char *pc;
float* pf;
void* pv;
5)、空类型(无类型)
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。
应用举例:
int main()//参数为void
{
return 0;
}
void test(void)
{
}
void* p;
2、整型在内存中的存储
2.1、原码、反码、补码
2.1.1、总体介绍
计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”。
1)、原码、反码、补码基本介绍
原码: 直接将数据按照正负数的形式翻译成二进制。
反码: 原码的符号位不变,其他位依次按位取反。
补码: 反码+1就得到补码。
需要注意:
此处,反码按位取反的含义,其和按位取反运算符~
的含义不同,后者连带符号位也按位取反。
2)、整型数值中正数、负数的原码、反码、补码规则
1、按照上述1)
中描述可知,正数的原、反、补码都相同。负数需要一定转换。
2、对于整形来说:数据存放内存中其实存放的是补码。
3)、为什么整型数据在内存中存储的是补码?
2.1.2、char、short类型在内存中的存储范围
char
如下图:
short
如下图:
2.2、大小端字节序存储
1)、是什么
字节序: 以字节为单位,看其顺序。
大端字节序存储: 把数据的低位字节存放在内存的高地址处,高位字节存放在内存的低地址处
小端字节序存储: 把数据的低位字节存放在内存的低地址处,高位字节存放在内存的高地址处
2)、为什么
在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。
但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),等等。
对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
3)、相关练习:设计一个小程序来判断当前机器的字节序
代码1.0:
写成函数的形式,可以根据其返回值来判断大小端,优点在于得到一个返回值,而是否打印判断结果根据需求决定。
int check_sys()
{
int a = 1;//0X000001
char* p = (char*)&a;
if (1 == *p)
{
return 1;//小端
}
else
{
return 0;//大端
}
}
int main()
{
int ret = check_sys();
if (ret)
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
代码改进2.0:对上述内容做一定简化
int check_sys()
{
int a = 1;//0X000001
char* p = (char*)&a;
return *p;
}
int check_sys()
{
int a = 1;//0X000001
return *((char*)&a);
}
2.3、相关练习
2.3.1、练习一
以下输出结果是什么?
#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;
}
分析:
1、存储时:无论是char,unsigned char,signed char
,-1是整型,32位数据被截断,只留最低的八位:1111 1111
1000 0000 0000 0000 0000 0000 0000 0001(原)
1111 1111 1111 1111 1111 1111 1111 1110(反)
1111 1111 1111 1111 1111 1111 1111 1111(补)
2、%d打印时,将数据取出,要发生整型提升,无符号、有符号正数高位补0,有符号负数高位补1。
对a、b://a要看默认存储类型,此处以默认为有符号char考虑
1111 1111 1111 1111 1111 1111 1111 1111(补)
1111 1111 1111 1111 1111 1111 1111 1110(反)
1000 0000 0000 0000 0000 0000 0000 0001(原)
对c:
0000 0000 0000 0000 0000 0000 1111 1111(无符号原反补码相同)
2.3.2、练习二
以下输出结果是什么?
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
分析:
1、存储时,-128被截断:1000 0000
1000 0000 0000 0000 0000 0000 1000 0000 (原)
1111 1111 1111 1111 1111 1111 0111 1111 (反)
1111 1111 1111 1111 1111 1111 1000 0000 (补)
2、%u读取
①先发生整型提升,a是signed char
,高位补符号位
1111 1111 1111 1111 1111 1111 1000 0000 (补)
②%u将数据视为无符号读取,故原反补相同,故读到的是一个很大的值。
2.3.3、练习三
以下输出结果是什么?
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
分析:
1、存储时:128被截断,为1000 0000
0000 0000 0000 0000 0000 0000 1000 0000 (原、反、补相同)
2、%u打印,和练习二一致。
①先发生整型提升,a是signed char
,高位补符号位
1111 1111 1111 1111 1111 1111 1000 0000 (补)
②%u打印,将数据视为无符号读取,故原反补相同,故读到的是一个很大的值。
2.3.4、练习四
以下输出结果是什么?
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
//按照补码的形式进行运算,最后格式化成为有符号整数
return 0;
}
分析:
1、存储时:
对i:-20
1000 0000 0000 0000 0000 0000 0001 0100 (原)
1111 1111 1111 1111 1111 1111 1110 1011 (反)
1111 1111 1111 1111 1111 1111 1110 1100 (补)
对j:10
0000 0000 0000 0000 0000 0000 0000 1010 (原、反、补相同)
2、i+j计算时,发生隐式类型转换,将i视作无符号整型:
算术运算是计算补码。
1111 1111 1111 1111 1111 1111 1110 1100 (i)
0000 0000 0000 0000 0000 0000 0000 1010 (j)
1111 1111 1111 1111 1111 1111 1111 0110 (i+j)
3、%d,以有符号数打印
1111 1111 1111 1111 1111 1111 1111 0110 (补)
1111 1111 1111 1111 1111 1111 1111 0101 (反)
1000 0000 0000 0000 0000 0000 0000 1010 (原)
2.3.5、练习五
以下输出结果是什么?
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
分析:
i
为无符号型无负数。-1会转换为对应的无符号数,为一个很大的数字。
-1:
1000 0000 0000 0000 0000 0000 0000 0001(原)
1111 1111 1111 1111 1111 1111 1111 1110(反)
1111 1111 1111 1111 1111 1111 1111 1111(补)
无符号下:该串数字为正数,原码、反码、补码相同:4294967295
故循环从9、8、7……2、1、0、后,变为-1对应的无符号数,为正数,又开始循环。
(或者说无符号下无负数,循环不会终止)
2.3.6、练习六
以下输出结果是什么?
#include <stdio.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
分析:先理解代码,①char a[1000]
定义一个大小为1000的字符数组,②遍历放入数据:-1、-2、-3、-4、……、-1000
,③strlen(a)
计算该数组大小。
由上述2.1.2,我们可知,signed char
类型的数据范围 限制,上述数组中实际存入的是-1、-2、……-128、127、126……0,-1此类循环。
strlen
读取‘\0’前的字符数,‘\0’的ascll码值为0,即读取-1到-128共128个,127到1共127个,总共127+128=255个。
2.3.7、练习七
以下输出结果是什么?
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
分析:unsigned char
类型,取值范围是0~255,当i
超过255时,数值溢出后无符号范围内又回到0重新开始,故陷入无限循环。
说明:使用无符号类型做循环时需要慎重。
3、浮点型在内存中的存储
3.1、问题引入
1)、常见的浮点数类型
浮点数家族介绍:
float
double
long double //C99之后才拥有
浮点数举例:
3.14159 //字面浮点型
1E10 //浮点数的科学记数法形式,E表示底数为10,整体是1.0x1^(10)
2)、两个文件:浮点数范围、整型范围
float.h
:定义了浮点数的取值范围的相关信息
limits.h
:定义了整型取值范伟的相关信息
3)、问题引入:相关代码
int main()
{
int n = 9;//存储一个整型的数9
float* pFloat = (float*)&n;//将n值取地址并强制转换为浮点型指针
printf("n的值为:%d\n", n);//以整型数值打印n值
printf("*pFloat的值为:%f\n", *pFloat);//以浮点型数值打印n值
*pFloat = 9.0;//存储一个浮点型的数9.0
printf("num的值为:%d\n", n);//以整型的数值打印n
printf("*pFloat的值为:%f\n", *pFloat);//以浮点型的数值打印n
return 0;
}
演示结果如下:
推断:浮点型和整型在内存中存储方式不同。正因此,存入一个整型变量,以%f
浮点数的形式打印,该变量在内存中的补码会以浮点数存储的方式解读。同理,存入一个浮点数,以%d
整数的形式打印,原先以浮点数方式存储的变量,会被解读为整型的存储形式
3.2、浮点数存储规则
3.2.1、浮点数的表示形式(一):基本认知
1)、整体说明
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
⚪(-1)^S * M * 2^E
⚪(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
⚪M表示有效数字,大于等于1,小于2。
⚪2^E表示指数位。
2)、小数点后的二进制位
2
−
1
=
0.5
2^{-1}=0.5
2−1=0.5
2
−
2
=
0.25
2^{-2}=0.25
2−2=0.25
2
−
3
=
0.125
2^{-3}=0.125
2−3=0.125
2
−
4
=
0.0625
2^{-4}=0.0625
2−4=0.0625
3)、举例说明
演示例子一: 十进制的浮点数5.5
step1:
对于小数点前,将其转换为二进制可得:101
对于小数点后,将其转换为二进制可得:1
那么5.5转换为二进制后,得:101.1
(PS:对于小数点后的二进制转换,不能照虎画猫生搬小数点前的规则,将5.5
解读为101.101
)
step2:
将101.1
写为科学计数法,注意是二进制:
1.011
×
2
2
1.011×2^{2}
1.011×22
step3:
则由上述规则可得,
(
−
1
)
0
×
1.011
×
2
2
(-1)^0×1.011×2^{2}
(−1)0×1.011×22
S
=
0
S=0
S=0,
M
=
1.011
M=1.011
M=1.011,
E
=
2
E=2
E=2
演示例子二: 十进制的浮点数9.0
step1: 9.0转换为二进制得,1001.0
step2: 写成科学计数法得,
1.001
×
2
3
1.001×2^{3}
1.001×23
step3: 是正数,故最终结果为,
(
−
1
)
0
×
1.001
×
2
3
(-1)^{0}×1.001×2^{3}
(−1)0×1.001×23
故
S
=
0
S=0
S=0,
M
=
1.001
M=1.001
M=1.001,
E
=
3
E=3
E=3.
3.2.2、浮点数的表示形式(二):如何将数据放入
1)、对32、64存储
IEEE 754规定:
对于32位的浮点数,最高的==1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
2)、对M
1
≤
M
<
2
1≤M<2
1≤M<2 ,也就是说,
M
M
M可以写成 1.xxxxxx
的形式,其中xxxxxx
表示小数部分。IEEE 754规定,在计算机内部保存
M
M
M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx
部分。
比如保存
1.01
1.01
1.01的时候,只保存01
,等到读取的时候,再把第一位的1
加上去。这样做的目的,是节省一位有效数字。
例如,32位浮点数,留给
M
M
M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。相当于提高精度。
3)、对E
step1:
需要明确,
E
E
E为一个无符号整数(unsigned int)。意味着:
E为8位:取值范围为0~255
E为11位:取值范围为0~2047
但是,科学计数法中的E是可以出现负数的。
例如:
0.5
0.5
0.5二进制为
0.1
0.1
0.1,即
1.0
×
2
−
1
1.0×2^{-1}
1.0×2−1,其中
E
=
1
E =1
E=1,不符合E为无符号数的需求。
所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数:
对于8位的E:中间数是127
对于11位的E:这个中间数是1023
比如,
2
10
2^{10}
210的
E
E
E是
10
10
10,所以保存成32位浮点数时,必须保存成
10
+
127
=
137
10+127=137
10+127=137,即10001001
。
4)、举例一:
float f = 5.5
根据之前分析可知,其二进制为:
101.1
101.1
101.1,转换后得:
(
−
1
)
0
×
1.011
×
2
2
(-1)^{0}×1.011×2^{2}
(−1)0×1.011×22。
32位下,
S
=
0
S=0
S=0,
M
=
1.001
M=1.001
M=1.001,
E
=
2
+
127
=
129
E=2+127=129
E=2+127=129,二进制是10000001
,故有:
00000000000000000000000000000000//32位
1位符号位:0
8位E值:1000 0001
23位M值:011 0000 0000 0000 0000 0000 //不够的后面添0
结果得:
01000000101100000000000000000000
0100 0000 1011 0000 0000 0000 0000 0000 -0X40b00000
4)、举例二:
float f = -0.5
根据分析可知,其二进制为:
0.1
0.1
0.1,转换后得:
(
−
1
)
1
×
1.0
×
2
−
1
(-1)^{1}×1.0×2^{-1}
(−1)1×1.0×2−1。
32位下,
S
=
1
S=1
S=1,
M
=
1.0
M=1.0
M=1.0,
E
=
−
1
+
127
=
126
E=-1+127=126
E=−1+127=126,二进制是01111110
,故有:
00000000000000000000000000000000//32位
1位符号位:1
8位E值:0111 1110
23位M值:000 0000 0000 0000 0000 0000 //不够的后面添0
结果得:
10111111000000000000000000000000
1011 1111 0000 0000 0000 0000 0000 0000 -0Xbf00000
3.2.3、浮点数的表示形式(三):如何将数据取出
取出的情况和存入相反,只是对于E的处理需要分情况讨论:
情形一:E不全为0或不全为1
对于指数E:内存空间中取出的 计算值-127(或1023)
,得到真实值。
对于M:内存空间中取出的有效数字M前加上第一位的1
E全为0
对于E:指数E = 1-127 =126
(或1-1023 =1022
)即为真实值。
对于M:有效数字M
不再加上第一位的1,而是还原为0.xxxxxx的小数
。这样做是为了表示±0,以及接近于0的很小的数字。
例如:
±
0.
a
a
a
a
×
2
−
126
≈
0
±0.aaaa×2^{-126}≈0
±0.aaaa×2−126≈0、
±
0.
a
a
a
a
×
2
−
1022
≈
0
±0.aaaa×2^{-1022}≈0
±0.aaaa×2−1022≈0
E全为1
如果有效数字M全为0
,表示±无穷大
(正负取决于符号位s)
3.3、3.1中问题说明
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;
}
分析:
int n = 9;
0000 0000 0000 0000 0000 0000 0000 1001(原反补同)
以%d
的形式打印,理解起来并无问题。
以%f
的形式打印,那么视上述这串二进制读取到的为浮点数。32位平台下,1bit为S,8bit为E,23bit为M,则有:
S = 0
E = 0000 0000 全0,E=1-127=-126;
M = 0000 0000 0000 0000 1001 ,不加小数点前1,M=0.0000 0000 0000 0000 1001
则有:
(
−
1
)
0
×
0.00000000000000001001
×
2
−
126
≈
0.0
(-1)^{0}×0.0000 0000 0000 0000 1001×2^{-126} ≈0.0
(−1)0×0.00000000000000001001×2−126≈0.0
*pFloat = 9.0;
,将9.0以浮点数的形式放入内存空间中:
9.0
9.0
9.0转换为二进制:
1001.0
1001.0
1001.0 ,即
(
−
1
)
0
×
1.001
×
2
3
(-1)^{0}×1.001×2^{3}
(−1)0×1.001×23
S = 0
E = 3 + 127 = 130 ,8bit, 1000 0010
M = 1.001,去除第一位1得,011,23位,不够后面补0:011 0000 0000 0000 0000 0000
故内存中为:
0 1000 0010 011 0000 0000 0000 0000 0000
0100 0001 0011 0000 0000 0000 0000 0000
以%d
的形式打印,视为无符号整型,正数,原、反、补相同
0100 0001 0011 0000 0000 0000 0000 0000 //0X41300000
十进制下读取即为:1093664768