数据的存储 详解
1 .数据类型的介绍
1.1类型的基本归类
--------------------------------------------------------------------------------------------------------------------------------
基本的数据类型:
char //字符数据类型(整形家族)
short //短整型
int //整型
long //长整型
long long //长长(更长的)整型 (c99)
float //单精度浮点数
double //双精度浮点数
printf("%dn", sizeof(char)); //1个字节
printf("%dn", sizeof(short)); //2个字节
printf("%dn", sizeof(int)); //4个字节 (32位机器或64位机器)早期为(16位机器)2个 字节
printf("%dn", sizeof(long)); //4个字节
printf("%dn", sizeof(long long)); //8个字节
printf("%dn", sizeof(float)); //4个字节
printf("%dn", sizeof(double)); //8个字节
---------------------------------------------------------------------------------------------------------------------------------
这 是 一 些 扩 展 大 家 可 以 看 看
有没有字符串类型呢?
答案是 有的
字符串是字符的序列,基本就是一组单词,是Phthon中常用的数据类型,可以用单引号(' ')和双引号(" ")来定义 (创建)字符串。
//比如可以这样
str = "My name is 9527 ";
str1 = 'HELLO WORLD!';
str2 ="""because of you""";//对了也可以用三引号(""" """)和(''' ''')
------------------------------------------------------------
print("%s" % str); //输出为 My name is 9527
print("%s" % str1);//输出为 HELLO WORLD!
print("%s" % str2);//输出为 because of you
不同的数据类型内会导致存储时和访问时有不同的差异 比如:浮点型就是一个典例(后面会给大家讲到 嘿嘿)
做点小扩展(摘自 cppreference.com)
- 浮点类型
- 实际浮点类型:float , double ,long double
|
(自 C23 起) |
|
(自C99起) |
可以简单了解一下 嘻嘻嘻
其次就是 布尔类型 (_Bool) 是C语言中专门用来表示 真 / 假 的一种类型(c++中也有哦)
c99标准才开始引入原因是因为 在早期 C语言都是用0表示假 非0为真 后期才开始有了这样
给大家演示一下:
//c99才开始引入
#include <stdio.h>
#include <stdbool.h>//这里需要引用头文件
int main()
{
_Bool flag = true;//true为真false为假
if (flag)
{
printf("Oh yeah! , you do it!");
}
return 0;
}
_Bool 没有什么好神奇的 本质是对 int 的一种重命名 你看这就很好的说明了 并且平常我们看到的代码也很少有用到_Bool类型的 即使 c99 中引入了 引用得不是很广泛有些编译器甚至不支持 当然你想用也是可以的 哈哈哈哈
#define bool _Bool
#define false 0 //类似于int false = 0;
#define true 1//类似于int true = 0;
---------------------------------------------------------------------------------------------------------------------------------
“ 家族 ”类型
1. 接下来是我们的整型家族
char :
unsigned char
signed char
short :
unsigned short //short等价于signed short
signed short
int:
unsigned int //int等价于signed int
signed int
long:
unsigned long //long等价于signed long
signed long
char类型 之所以归于整型家族中是因为其字符有与其相对应的 ASCLL码值
对于 char类型 是不一样的 char 到底等不等同于 signed char 是取决于编译器的不同的
但是一般来讲是 等同 的
需要注意 有符号 无符号 在打印是也要求的
//注意:%d 是按照有符号数来打印的
//%u 才是按照无符号来打印的
unsigned int num = -10;
printf("%d", num); //打印为 -10
int num1 = -10;
printf("%u", num1); //打印出来为4294967286
2 浮点数家族
float
double
除了这些类型之外还有 构造类型 (就是自己构造的类型)
- 派生类型
(自C11起) |
对于上面列出的每个类型,可能存在其类型的几个限定版本,对应于一个,两个或全部三个const,volatile和strict限定符的组合(在限定符的语义允许的情况下)。(摘自 cppreference.com)
可以提的一点就是还有一个 void类型 他表示一种空类型(应用于 函数的返回的类型、函数的参数、函数的类型)。
void function(void)
{
void *tmp;
}
2. 整型在数据中的存储
变量的创建是需要在栈区创建空间的 而不同的类型决定了创建的空间的大小
2.1 原码、反码、补码
在了解 原 反 补 之间的关系前 我们要知道一个点就是整数在内存存储的补码
正整数 的 原 反 补 是相同的
负整数 的 原 反 补 之间的关系可以这样解释
int a= -12;
//1 0000000 00000000 00000000 00001100 (原码)一个字节八个bit位
//1 1111111 11111111 11111111 11110011 (反码)按位取反符号位不变
//1 1111111 11111111 11111111 11110100 (补码)取反后加一
//前面的1为符号位 1表示其为负数 0表示其为正数
int b= 12;
//0 0000000 00000000 00000000 00001100(原 反 补)
在计算机系统中,数值一律用补码来表示和存储。原因在于使用,可以将符号位和数值域统一处理
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换其运算过程是相同的,不需要额外的硬件电路。
用补码有什么效果好处是什么?
大家可以这样看看
int a = 1;
int b =-1;
//00000000 00000000 00000000 00000001 (a的原码,反码,补码)
//10000000 00000000 00000000 00000001 (b的原码)
//11111111 11111111 11111111 11111110 (b的反码)
//11111111 11111111 11111111 11111111 (b的补码)
//如果此时我们计算 a+b 使用原码
//得到的结果就是这样的
//11111111 11111111 11111111 11111110 显然这个是不等于0
//如果我们用到补码来计算情况就是这样的了
//1 00000000 00000000 00000000 00000000此时前面的1就溢出了 最后得到就是
//00000000 00000000 00000000 00000000 此时得到结果就是0了
2.2 大小端
大段 (存储) 模式 数据的低位保存在内存的高地址,而数据的高位,保存在内存的低地址中
小段 (存储)模式 数据的低位保存在内存的低地址,而数据的高位,保存在内存的高地址中
其实通俗来讲的话 可以这样理解: 小段(存储)模式下内存存储的字节序是相反的,大段就是与之相反的。
大家来看:
// 大家看
// 10的补码 00000000 00000000 00000000 00001100
//4个字节为1个16进制
//0000 0000 | 0000 0000 | 0000 0000 | 0000 1100
// 0 0 | 0 0 | 0 0 | 0 a(10)
高字节 <----------------------------低字节
//所以按照 小端存储 的话 就是反着存的
//拿进去就是这样的
//0a 00 00 00
这就是小段的存储 大家看懂了吗?大段就是与之相反的 嘻嘻
最后补一句 只要一个数值在内存存储的大小超过一个字节 那么他在内存中的字节存储就有顺序
就好像char类型就没有字节序(大小端)的区别
除此之外 大家还需要注意数据在进行赋值时的一些原理 (因为他们很可能会发生 整型提升 和 算数转换 )
就好像是这样:
char x = -1; //大家注意看这里 -1为一个整型(32个bit位)现在要放在一个字符型(8bit
位)的变量中那么就需要发生截断
//11111111 11111111 11111111 11111111
// 11111111(截断后就是这样了前面的1就没有了)
signed char y = -1; //在VS中char等同于signed char 所以截断和第一个一样的
unsigned char z =-1; //这里也是一样的道理 无符号并不会有影响 他依旧只能存储8个bit位
整型提升内就是以他的符号位的数字往前补1或0(这里就不做细讲了)
//如果不理解的话大家可以看看这样
//在printf("%d %d %d",x,y,z); 这个时候之前的char 、signed char 、unsigned char就开始发挥他们的作用了
//之前不是 x 被 char 截断的后变成了 11111111
%d是要以有符号的整型打印出来前面也提到过了
所以呢这个时候 就会发生整型提升:
//11111111
//11111111 11111111 11111111 11111111(因为截断后的x是有符号的并且为 1 所以就补 1 )
这个时候内 这还是补码 要转换成原码来打印出来
因为-1的补码为全1 (大家可以记一下)所以这个时候打印出来就是 -1
unsigned char截断的z就会在前面补0(因为它是无符号的就补0)
//00000000 00000000 00000000 11111111
%d打印是以有符号的方式打印出来 所以它(%d)就会认为0 是他的符号位 因为0为符号位为正数
原、反、补码相同 此时的虽为补码 原反补相同就直接按照
//00000000 00000000 00000000 11111111
打印出来 就是这样的 嘿嘿
3. 浮点数在内存中的存储(与整数是不一样的哦)
浮点数家族:float 、double 、long double
其中大家可以了解一下这个 1E6 这也是一个浮点数 1E6 =1*10^6
(浮点数后边不加 f 默认为double类型 加后改为float类型)
---------------------------------------------------------------------------------------------------------------------------------
大家来看一下浮点数的存储规则:
根据国际标准IEEE(电气和电子工程协会)754,任何一个二进制浮点数 V 可以表示成下面的形式
(-1)^S*M*2*E
1. (-1)^S表示的是符号位,当S为0时 V 为正数 当S为1,V为负数
2. M表示有效数字,大于等于1,小于2
3. 2^E表示指数位
---------------------------------------------------------------------------------------------------------------------------------
//大家来这样看
好比 6.5-->110.1(二进制) 0.5=2^-1所以 0.5-->0.1(二进制)
// 110.1=1.101*2^2 这里乘以二的二次方 就好比 9527=9.527*10^3 科学计数法
//6.5为正数 所以 (-1)^S 这里的S就等于0;
//有效数字M就为1.101
//指数E就是2
接下来 就是这些数应该如何放进去:(画得不太好 嘻嘻)
1. S 的存储即1或者0放进去(浮点数不存在原反补的概念)2
2. 因为之前 M 说到 1<= M <2 所以 M 可以表示成 1点几 (1.??????) 所以在存储 M 时就只会存储小数点后面的数 这样的话拿出来使用只需要在 M 前面加 1 就可以了 并且这样还能节省一个空间也就可以多存储一位了 怎么样 是不是很 女少
3. 存储 E时情况就比较复杂了 分为以下几种:
首先大家要知道E为无符号整型(unsigned int)
如果计算出来 E 的值需要加一个中间数 ,8位的情况下加的是127,11位的情况下加的是1023
好比 :现在 E 计算出来为-1 那么存进去时就应该是 -1+127=126 存进去
10的话 就是10+127=137 大家明白了吗 (还有一点 8位情况下 E的取值范围是0~255 这里的范围是计算的 E 加上中间值127后的范围 这样说大家理解嘛?)
在取出E时又分为以下的几种情况:
1 .E不全为1或不全为0的时候:
这个时候就只需要把 E 的值取出来减去一个中间值即可
2. E全为0时:
E为0就意味着之前计算出来的 E 为-127 大家想想一个指数-127 (2^-127)得多小啊
所以这个时候 E 直接拿出来就等于 1-127 或 1-1023 此时有效数字 M 就不再加1了而是直接变成0.????(大家可以了解一下)
(-1)^S*1.???*2^-127
//这样的数不就是一个趋近于0的很小很小的数了嘛
3.E为全1时:
// 1111 1111 这个计算出来为255 说明计算出来的 E 就应该为 128
// 想想一个数 2^128是不一个很大的数目
//到这里大家就已经看见 E 为全0,E 为全1 就是两个相对的极端
给大家演示一下下:
int n =12;
float *p=(float*)&n;
printf("%fn",*p); //注意这里是以整型放进去 然后以浮点数的形式打印出来
//00000000 00000000 00000000 00001100
%f去识别时
//0 00000000 00000000000000000001100
S E M
//这时E全为0 所以M就不用加上1了所以这个拿出来就是
//(-1)^0 * 0.00000000000000000001100 * 2^-126 得到就是一个很小很小的数
%f %lf 都是默认为输出小数点后六 这里显然后六位 全为0 输出自然而然就是 0.000000
//再给大家演示一下 存储的过程
延续刚刚的代码
*p = 12.0;
printf("%f",*p); //这里就是浮点数放进去浮点数拿出来了
printf("%d",*p);
12.0-->十进制 S=0
1100.0-->二进制
1.100*2^3-->E=3,M=0.100
//0 10000010 100 00000000000000000000(后面不够补0)
//完整来看 0 10000010 10000000000000000000000
存储在内存中时 大家这样来看
// 0100 0001 0100 0000 0000 0000 0000 0000
// 4 1 4 0 0 0 0 0 大家想想浮点数有大小端之分嘛
很明显是 有的
图中
printf("%d",n); //这里的n打印出来是 1094713344 为什么内 我给大家解释一下啊
此时n的地址中的内容已经发生了改变
// 0100 0001 0100 0000 0000 0000 0000 0000 如果把这个放到计算器中
大家看 是不是一样的 嘿嘿
---------------------------------------------------------------------------------------------------------------------------------
到这里内 数据的存储 就讲完啦 感谢大家看到这里 谢谢啦 (第一篇博客 有些不好的地方麻烦各位大牛指正)
那就这样吧! 祝大家都能进入自己喜欢的大厂! 加油