🧮前言
经过前面博客的介绍,我们的C语言初阶已经学完了。现在我们可以进入更深层次的C语言世界了,而本文是我们进阶的首篇文章,主要是介绍各种数据在内存中的存储情况,比如有符号char的最大值是多少、整型数据与浮点型数据在内存的存储方式有何不同等,学会这些知识能增加我们的内功,真正做到了然于心。🚀🚀🚀
🧮正文
我们C语言中的有七种基本数据类型,可以分为三种:整型、实型、字符型。
C语言中的基本数据类型
short |
短整型 |
%hd |
2字节 |
int |
整型 |
%d |
4字节 |
long |
长整型 |
%ld |
4-8字节 |
long long |
更长整型 |
%lld |
8字节 |
float |
单精度浮点型 |
%f |
4字节 |
double |
双精度浮点型 |
%lf |
8字节 |
char |
字符型 |
%c |
1字节 |
💻数据分类
根据各数据的特点,可将数据分为以下几类:
🖥️整型家族
char
当我们选择char时,是否带有符号是由编译器决定的,有符号和无符号 char 的取值范围不同
char 大小为1字节=8比特,因此在 char 中至多有八个可用位。
signed char 有符号 char
unsigned char 无符号 char
short
short比 char 大1字节,因此所表示范围值会比 char 大很多,最大值同样是无符号表示
signed short
unsigned short
int
int为标准的4字节,32比特位,有无符号的 int 最大值差别依旧很大
signed int
unsigned int
long
long的大小是4~8字节,这里我们取8字节,相当于long long,作为最大的数据类型,long在使用时几乎很难造成溢出,因为比较无符号long最大值就为922京(9*10^18)
signed long
unsigned long
🖥️浮点型家族
浮点型家族就两个:float与double,float是4字节,double则是8字节,可表示的范围也是非常大,由于浮点型在内存中存储时比较复杂,不再依靠原反补这套系统,而是拥有属于自己的存储方式。有关浮点型数据在内存中的存储情况,将会专门在后面解释。
🖥️构造家族
构造家族外部依赖性强,有以下四种:
数组类型 |
arr[ ] |
结构体类型 |
struct |
枚举类型 |
enum |
联合类型 |
union |
构造家族成员都需要依靠外部定义的数据,比如数组,需要定义大小;结构体,需要声明内部的变量成员;枚举类型则需要根据变量数来确定枚举值等
🖥️指针家族
指针家族中包括了各种类型的指针变量,比如常用的有
int* |
pi |
整型指针,指向整型数据 |
float* |
pf |
浮点型指针,指向浮点型数据 |
char* |
pc |
字符型指针,指向字符型数据 |
void* |
pv |
空指针,能指向所有的数据,但无法进行操作,作临时指针 |
🖥️空类型家族
空类型(void)指没有具体的数据类型,通常用于函数返回值、函数参数、临时指针中。
💻整型数据在内存中的存储
整型数据有三种状态:原码、反码、补码,原码就是将原数据转换为二进制后的序列,序列中的最高位为符号位(0为正数,1为负数),反码则是将原码除符号位外全部取反(0变为1,1变为0),补码则是在反码的基础上+1,整型数据在内存中都是以补码的形式存储的,因为正数原码、反码、补码均相同,因此我们只有遇到负数时才刻意去求补码。
🖥️原码、反码、补码
原码 |
将数值向二进制进行转换,要注意符号位,0为正数,1为负数 |
反码 |
将原码除符号位外全部取反,比如10000001,取反为11111110 |
补码 |
再反码的基础上进行+1,比如11111110,+1后变成11111111 |
至于为什么需要补码这个概念?
因为CPU中只有加法器,在执行减法操作时会将被减数转换为一个负数,然后再进行相加
补码的产生使得加法转换为减法后的计算结果依旧正确,而且因为转码的运算过程是相同的,不需要借助额外的硬件电路,因此计算速度上几乎无差异。
🖥️大小端字节序
在我们的内存中存在两种不同的存放标准,一种是大端存储,另一种则是小端存储,不同编译器所支持的存储顺序有所不同,比如我们的VS2019,使用的就是小端字节序存储数据。
大端存储 |
指将数据高位次存放在内存的低地址中,而低位次则是存放在内存的高地址中 当为大端存储时,十六进制会正着显示 |
小端存储 |
指将数据高位次存放在内存的高地址中,而低位次则是存放在内存的低地址中 当为小端存储时,十六进制会倒着显示 |
大小端相关笔试题(百度 2015 笔试题)
//判断大小端 int main() { int a = 1;//十六进制表示为 00 00 00 01 char* pa = (char*)&a;//利用字符型指针访问一字节 if (*pa == 1) printf("小端存储\n"); else printf("大端存储\n"); }
💻浮点型数据在内存中的存储
🖥️存入
浮点数在内存中表示时比较复杂,于是电气和电子工程协会(IEEE)754标准便这样规定了浮点数在内存中存储规则:任何一个浮点数V都可以写成 V=(-1)^S*M*2^E ,其中S控制符号位,为1时V为负数,为0时V为正数;M为有效数字,在1~2这个区间内;2^E则表示指数位。
由此可见浮点数在内存中的储存与整型完全不一样,也就是说如果在输入(输出)时格式匹配错误,那么数据肯定就是有问题的!!!
单精度浮点型(float)有32比特位,规则在上面,而双精度浮点型(double)有64位,规则跟32位几乎一致,不过在空间分配和指数E的中间值上略有差异
🖥️取出
存入很复杂,取出也很复杂,光是取出的情况就有三种:
1.指数E非全0或非全1时,常规取出,如果存的时候加了中间值127(或1023)取的时候就要减去中间值127(或1023),这是比较常见的取出形式。
2.指数E为全0时,若指数E为全0,说明初始E为-127,可以看出原浮点数是一个非常小的数,无限接近于0,因此这个数字取出来将会是一个很小很小的数。
3.指数E为全1时,若有效数字M全为0,表示正负无穷大。
🖥️例题
模拟将整型存入浮点型,将浮点型存入整型的场景
//模拟 int main() { int a = 9; float* pa = &a; printf("%d\n", a); printf("%f\n", *pa); *pa = 9.0; printf("%d\n", a); printf("%f\n", *pa); return 0; }
🧮总结
数据类型算是C语言中的底层部分了,不同数据类型的数值轮回也是合情合理,在做题时注意看是否有符号,这样在进行计算时就可以判断出是否接近边界;不同类型的数据都有其应用场景,只有做到场景匹配了,程序效率才会最大化。总的来说,无论是反码相加还是浮点数的存储,都是非常巧妙的设计,是无数前辈绞尽脑汁的最优解,正是因为有了这些规则,今天我们才能看到如此完善的C语言体系。今天,我们站在巨人的肩膀上,明天,我们也许就能实现巨人们远大的理想!🌌
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正