1.数据类型详细介绍
2.整型在内存中的存储:原码,反码,补码
3.大小端字节序介绍及判断
4.浮点型在内存中的存储解析
1.数据类型详细介绍
上面这些是内置类型,是c语言本身就具有的类型
类型的意义:
为什么char字符类型也属于整型家族呢? -- 这是因为字符在计算机中存储的本质是存在计算机中的ascii码值,而这个码值是整数
unsigned -- 无符号 -- 只能是正数
signed -- 有符号 -- 既可以是正数,也可以是负数
补充: int ,short ,long , long long这些类型c语言默认它有符号
但是c语言没有规定char是否有符号,这个是由编译器来规定的,大部分的编译器都认为是有符号
short 短整型 -- 开辟的内存空间比整型小的整型
long 和 long long 则是开辟的越来越大的意思
浮点数家族就只有两个 : float 和 double
构造类型又称为自定义类型,其中数组类型是什么呢?
当我们建立一个数组的时候,如 int a[ 10 ] 我们规定这个数组的类型是 int [ 10 ]
而 int a[ 5 ] 的类型就是 int [ 5 ]
综上就不难看出其实数组的类型也是我们自定义,所以数组类型也是构造类型
而空类型就是 void
空类型的应用场景:
1. 函数不需要返回类型时,我们就让函数的返回类型为一个空类型 void test ()
2.调用函数时,想让函数无参,那这时候我们就可以让函数的参数为 void 此时就能实现函数无参的功能。
3.建立一个无类型指针:void* pv -- 要注意无类型指针和空指针是不同的,无类型指针可以指向任何类型的数据,而空指针则是任何类型的数据都不指向
类型总分类
2.整型在内存中的存储:原码,反码,补码
在调试窗口的内存中如果要监视a的内存空间的话,直接在索引处输入&a就可以了
1.数据在内存中以二进制的形式存储
对于整数来说:整数的二进制表示有三种形式:原码反码补码
对于正整数来说:它的原码反码补码都相同
对于负整数来说:原码反码补码之间存在算术转换
那么原码是什么?
原码是我们根据给定的数值直接写出它的二进制形式 --- 考虑符号位的情况下
如-10 就是 1000000.....1010(32位计算机,32个比特位)
其中正数的原码反码补码都相等,所以此处不作讨论,我们来讨论负数的原码反码补码
根据上面的方式我们求出了负数原码(正数一样),接着我们开始求其反码:
求解方式是:原码的符号位不变,其它位按位取反
得到反码后,反码加1得到的就是补码
计算机中整数的存储存的都是补码!!!!
但是计算机输出时都是输出原码对应的值
内存中的地址都是用16进制码来显示的,否则用二进制来显示的话就会导致地址太长,不方便人们阅读
cpu上只有加法器,它是没有减法的,它都是通过加法来模拟减法,如 1-1 --> 用加法模拟就是
1+(-1)
看着这上面的地址有一个现象需要我们去解释。
这个现象就是我们实际推出来的16进制地址在内存中是倒着显示的,比如10的16进制数是
0000000a 而在内存中改地址的显示确实 0a 00 00 00,那么为什么内存会倒着显示地址呢?
这就涉及到大小端这个知识点了
3.大小端字节序介绍及判断
大小端字节序就是用来规定在存储数据到多个内存单元时的数据排序的顺序
1.内存单元的大小是一个字节
2.内存中内存单元的排列顺序是从左往右,地址由低到高变化
3.在16进制地址与内存空间的对应中,我们常用一个16进制数表示此处有一个字节的空间被占
4.字节序即一个字节的空间(内存单元)的地址排列顺序
5.大端小端分别是两种不同的字节序
6.对于32位机,其16进制内存地址共有八位,而对于64位机,其16进制共有12位,
8位16进制数的地址是4个字节32个bit大小的内存空间的地址
12位16进制数的地址是8个字节64个bit大小的内存空间的地址
16进制数作为地址显示的时候是按照两两一对儿的顺序显示的
7.一个计算机中多少位就表示了这个计算机最大能够生成和存储的二进制数有多少位,比如32位机最大能够生成和存储的二进制数是32位,而一个二进制数占一个1个bit,所以32位机的总内存大小就是它能够生成所有二进制数可能 = 2^32 bit (一个字节是八个bit)
8.每一个内存单元都有独属于自己的地址,其地址的内容是32位二进制数,顺序是从32个0到32个1排列,为了简化表达内存单元的地址,我们用对应的8位16进制数来替代32位的二进制数,12位16进制数替换64位二进制数
9.同时8/12位16进制数地址的前面往往都要加一个 0x,目的是为了告诉计算机这是16进制数
顺着地址排的是大端 --- 高位在低地址,低位在高地址
我们输入一个数,计算机转为二进制数,如果是32位机的话转为32位二进制数,64位的话转为64位二进制数。已知存进去的二进制数,如果我们要判断它在内存空间中的字节序的话我们只需要看第一个内存单元中(前八个bit)存的是二进制中的第一个低位还是第一个高位,这样我们就能够判断是大端还是小端
低位高位 -- 在二进制中一个字节八个bit视为一个位,我们规定二进制数从左往右位由高到低排列。
而内存单元的地址则是从左往右,地址不断增大(变高)
综上我们只需要访问对应存储空间的第一个内存单元(低地址)并判断它里面存的是低位的值还是高位的值就可以判断它是大端还是小端
那么如何只访问一个字节大小的内存单元呢? -- 这时我们就想到了字符指针 --- 字符指针的步长只有1个字节,当我们解引用一个字符指针的时候,只能访问和copy对应一个步长(char*一个步长的大小是1)的内存空间中的内容
所以我们只要将int*(其一个步长的大小是4)的指针强制类型转换为char*(一个步长的大小是1),此时再进行解引用操作的话,就只能访问和copy一步长的内存空间了
解引用操作:解引用操作令执行时会按照顺序实现以下几个目的:
1.创建一个新的变量,变量的类型与被解引用的指针指向的内容的类型一致
2.访问并将被解引用的指针指向的地址到该地址+1个步长地址之间的内存空间中存储的内容copy并存到新的指针变量开辟的空间中
Int* 一个步长大小是4 char*是一,double*是8.......依次类推
指针类型一个步长的大小与其指向的内容的类型开辟的空间大小一致
一个类型a存到另一个类型b中有两种情况,一种是a开辟的空间比b大,a的二进制存到b中会出现数据截断,否则出现未填满的情况。
如char i = 1 --- 1是一个整型,它的数据存储有32个Bit位4个字节,而存到char中,则只有1个bit位,则1在存到b中时会在发生数据截断后才能存进去 ---- 所谓数据丢失就是删掉多的二进制位--删除顺序是从高位到低位
无符号数和正数是不一样的,正数是有符号数,它的首位为符号位,值为0,而无符号数没有符号位,它的首位无论为1还是为0,都是正数
整型提升:
1.char,short类型在进行运算或者以整型的类型进行输出时,计算机都会对它们进行整型提升,然后再运算
2.有符号位的整型提升 ---- 从符号位开始往左补二进制数,二进制数的值与符号位相同,补到总二进制数为32位为止
3.无符号位 --- 从已有的最高位向左开始补0,补到32位为止
4.计算机存一个整型的时候存的是它的补码(正数:都一样,负数:算术转换),而当计算机要输出这个整型的时候会 1.补码转成原码 2.将原码转为对应的十进制数输出
%u是输入输出格式说明符,表示按unsigned int格式输入或输出数据,即用来声明我们要输出的数据是什么格式 ---- %u的作用就是让计算机认为我们的内存中放的是一个无符号整数,而%d则是让我们的计算机认为我们的内存中放的是一个有符号整数,其它的同理
对于二进制数而言,二进制数的首位就是符号位 --- 如32位的首位就是左起第一位
无符号数和有符号数最大的区别就是首位是不是符号位,对于正数和负数来说,其首位必须得是符号位,且分别只能为0和1,而无符号数的首位既可以是0也可以是1,无限制,且无论是0还是1它都是一个正数
上面这张图中显示的是存储在内存中的二进制数,其中最重要的一点就是 10000000 这个二进制数的符号位为1,是个负数,当我们想要输出这个负数的时候我们需要将其从补码变为原码再转换为二进制数输出,那么我们要将符号位后面的七位 - 1 ,但是问题是符号位后面是0000000,没有可以借,无法减1,搞不到反码,那怎么办呢?
为了解决这个问题,人们将这个特殊的有符号二进制数不进行原反补的变换,直接解析为 -128来解决问题
综上有cahr类型的数据存储上下限是从-128 到 127
有符号数的二进制盘
补码的出现让我们实现了用加法器代替减法器的母的 -->时间转盘
一个表达式计算完之后直接输出和一个表达式计算后之后还要再用其结果进行判断这两个操作的逻辑是完全不相同的。
一个表达式计算完之后直接输出 --- > 计算出的结果不用进行存储,因为我们要直接输出这个结果
一个表达式在计算完之后还要再用其结果进行判断/赋值等等,总之就是计算出的结果不是被直接输出而是被再次使用的话,那么这个计算的结果就会被存储到内存之中。
一个表达式的结果如果要被存储到内存之中,且这个表达式中的操作数类型不同的话,那么这个表达式中的操作数会先进行类型转换然后才进行计算得结果
若结果不需要进行存储的话,就不需要进行类型转换,直接进行计算
1. 补充一个类型转换,当无符号数和有符号数进行算术运算时,若结果要被存到内存中,则该表达式中需要进行类型转换再运算,且是有符号数转换为无符号数 ---- 即让首位失去符号位功能 ---- 首位数值不会改变,但首位会失去符号位功能。
当没有输入输出的时候,计算机会站在变量类型的角度来解析存储在对应内存中的数据,如站在a的角度来看,存到它开辟的内存空间中你的二进制数是无符号数
而在我们scanf和printf(即输入输出) ,计算机会站在我们的输入输出格式说明符的角度来解析我们存储在内存中的二进制数,如上面的printf中的%d和%u,%d时计算机就认为是内存中存的是有符号数
一定不要忘了这个有符号数圆盘,所有的有符号数都是以这个圆盘的形式来排列的!!
所有的100000000000....0对应的十进制都默认是最小的负数减1
终止符 \0 的ascii码值是 0
我们在编译器中创建一个整数的时候,这个整数都是以补码的形式存在的
无符号二进制数就是一条直线往后走,而有符号的数则是以圆盘的形式存在,且1000000....0被定义为最小的负数-1
所有的类型创建的空间有限,能够存储的数也有限,每一种类型都有自己能够存储的最大数和最小数
4.浮点型在内存中的存储解析
好问题,裂开了
1E10表示1.0乘以10的五次方,1E5表示1.0乘以10的五次方,其它同理
什么类型的指针解引用之后创建的就是对应类型的变量来存储指针指向的对应内存空间中的数据,哪怕内存空间中存的是一个描述整型的二进制码,只要我们用的是浮点型的变量来装它的话(无输入输出)计算机都会站在变量的角度来看待和解析这一串二进制码
关于前面的解引用的本质的修正
解引用的本质不是创建一个新的变量并copy被解引用的指针指向的内容,
解引用的本质是一种访问操作,它允许我们直接通过地址访问和修改被解引用的指针指向的内容
1.这里的有效数字指的是二进制的有效数字,而二进制数字是不可能大于2的,且一个数的左起第一个不为0的数字及其后才是有效数字,对于二进制数来说只有0和1,所以左起第一位一定是11.
2.十进制转换为科学计数法 --- 如1011 ---> 则它被转换为1.011*10^3(10的4次方),而将一个二进制数转换为科学计数法也是同样的道理,如二进制1011转换则是 1.011*2^3 --- 此时我们称这个1.011为有效数字,有效数字的第一位只能从非0数开始,所以对于二进制来说,有效数字的第一位一定是1,所以上面这个有效数字的范围只能是大于等于1小于2.
2^E 就是2的E次方
另外 :十进制的小数位和二进制的小数位之间的转换后面再讲,主要问题在于2的负一次方
如果有了上面这 S,M,E这三个量,我们就能够很好的描述浮点数了,也就是说计算机只要将s,m,e存储,他就能够将一个浮点数完整的储存好了
对于32位浮点类型,即float类型,在内存空间中的S,E,M的分布如图
对于64位浮点数,即double类型,其内存中的sem分布是这样的
反正都是1,不如直接省去,到时候要用的话再补上,既不用做特别区分,还能够提高精度,美哉
首先E是一个无符号整数,也就是说它一定是正数,也就是说我们不能存负数到E中去,但是!E又可能是负数,比如 0.5 变成二进制是0.1,转换成科学计数法就是1.0*2^-1,此时E就是负数,为了解决这个矛盾我们引入了中间数
规定float的中间数是127,double的中间数是1023 , 如果要存入一个转换好的E(无论正负)到内存中去,我们都需要先将原始的E加上中间数,然后再将得到的这个新的E转换为对应补码存到内存中
上面这个40 b0 00 00并不是内存空间的地址,而是我们存进去的二进制数据的16进制表示,在调试中地址是在这个16进制表示的左边的
为什么要有这个16进制表示呢?
方便人们查看内存空间中存的到底是个啥
对于M ,在内存中我们存的是小数点后对应的二进制值,如果这个二进制值没有把提供的内存区占满,那么我们就用0来把空的二进制位补上。
关于浮点数存储的情况分类,最重要的是两个特殊情况
第一个是正常的情况,正常情况下正常分析
1.先是s区,一个bit位,0为正,1为负
2.看E区即接着符号位区出现的指数区,在看指数区前我们要先将指数区-127(float单精度浮点型)/-1023(double,双精度浮点型),然后再将得到的二进制结果转为10进制的指数
3.是有效数字区,将有效数字转为10进制小数,然后再将其加一个1,得到最终结果
ps:如何将10进制小数转换为二进制小数:
十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又 得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,此时0或1为二进制的最后一位,若无限循环,则达到所要求的精度为止。
ps:如何将二进制小数转换为十进制小数:
方法如下:二进制的小数转换为十进制主要是乘以2的负次方,从小数点后开始,依次乘以2的负一次方,2的负二次方,2的负三次方等
第一位:对应位上的0/1成2的负一次方,后面依次对应位乘2的-2,-,3....次方,最后求和
(补充:二进制保留小数点后几位的时候的“四舍五入”是“0舍1入”,如0.10011,保留四位的话就是0.1010 --- 最后为1,所以1入,进1留0,得0.10100,然后,舍0,得0.1010)
两个特殊情况:
1.当E区全为0的时候,我们认为此时无限小(指无限趋于0),此时的E规定为1-127(float)/1-1023(double),且有效区M转换为小数之后不再加1,而是直接用小数作为有效位
2.当E区全为1,M区全为0的时候,我们认为此时为正负无穷大,至于是正还是负则取决于符号位。
输入输出格式控制符为%f时,能够打印的位数只有小数点后六位,如果想精确到 z 位的话,则要这么写说明符 --- % . zf
float 类型和 double类型一样用 %f , long double 用 %lf ---- %f 和 %lf 都只能精确到小数点后六位.
把一个非整数转换为浮点数存储的步骤是分三步分析,分被是整数部分和小数部分:
1.分别将整数部分和小数部分转化为对应的二进制位 ,即将十进制的非整数转换为二进制的非整数
2.将我们得到的这个二进制的非整数转换为二进制的科学计数法的形态,即将二进制的小数点向左移到左起第一个1为止,此时移了多少位,E没加127/1023就是多少。此时我们也到了有效位:即1加其小数位
3.得到了S,E,M之后,我们按照对应的处理方式将它们转化为正确的二进制形式存储
S(0/1)-- 第一位
指数位二进制+127/1023的二进制 -- 存到E区
有效区M :1不写 ,将小数点后的二进制位直接存进来(在一般情况下,转换为10进制时,1要补上,而在无穷小的时候1不要,注意无穷大的时候1是要的)
综合上面这些知识我们就能够解决一开始出现的那个问题了。