
在写操作符的时候这部分就写过一些: 常用操作符,操作符相关笔试题(谷歌)及算法的优化
整数的二进制表示方法有3中,即原码、反码和补码
有符号的整数,三种表示方法均有符号位和数值位两部分,符号位都是用0表示”正“,用1表示”负“,最高位的一位是被当成符号位,剩余的都是数值位。
补充:无符号数无符号位
正整数的原、反、补码都相同。 负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。 补码:反码+1就是补码。

对于整型来说:数据存放内存中起时存放的是二进制的补码。 在计算机系统中,数值(整数)一律用补码来表示和存储。 原因在于,使用补码,可以将符号位和数值域统一处理(统一参与运算)。 同时,加法和减法也可以统一处理(CPU只有加法器,减法是转换为加法计算)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

可以看到a这个数字是按照字节为单位,倒着存储的(有些平台是正着存的),这是为什么呢?
其实超过一个字节的数据在内存中存储的时候,就有存储顺序(以字节为单位的顺序)的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储,这两个名词又是什么意思呢?
比如说现在有一个变量b中存着这样一串数据
int b = 0x11223344;这串数据按原样在内存中正着存放就是大端字节序存储,倒着存放就是小端字节序存储。

红色方块代表一个内存块

像这样的一百二十三,数学里叫百位叫高位,叫个位叫低位,对应到内存里来。
大端(存储)模式:
小端(存储)模式

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8个bit位,但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(这个要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。
比如说我们所用的intel的芯片就是x86架构,x86架构是小端模式,而KEIL C51(51单片机)则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端还是小端模式
存一个整型拿一个整型,一个整型4个字节,不关心大小端也无所谓,存4个字节拿其中某个字节的时候,就与顺序有关,则需要判断是大端还是小端。
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)- 百度笔试题
写法1:

写法2

优化



为什么会出现这样的打印结果呢? 在看到这道题,首先想到的是char和signed char是一样的,其次想到的是signed char和unsigned char数据的存储范围
signed char 的取值范围是 -128-127 unsigned char 的取值范围 0-255
那么为什么他们的取值范围是这样的呢?如下图

这里10000000取反+1已经超出8bit位的存储范围,所以该二进制序列会直接解析为-128。
而这样的二进制序列不断+1之后也能够形成一个循环

unsigned char没有符号位,原符号位也是数值位

相应的short型数据也可以推导出来这里【1】是符号位,【15】是数值位

再回来看刚刚的代码
#include <stdio.h>
int main()
{
char a = -1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//11111111 - a 截断高位
//整型提升
//11111111 11111111 11111111 11111111 - 补码
//10000000 00000000 00000000 00000000 - 反码
//10000000 00000000 00000000 00000001 - 原码
signed char b = -1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//11111111 -b 截断高位
//整型提升
//同a的效果
unsigned char c = -1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//11111111 - c
//整型提升 - 整数的原反补码都相同
//00000000 00000000 00000000 11111111 - 补码
//00000000 00000000 00000000 11111111 - 反码
//00000000 00000000 00000000 11111111 - 原码
printf("a = %d, b = %d, c = %d", a, b, c);//-1 -1 255
//%d - 是以十进制的形式打印有符号的整数(认为我们要打印的数据在内存中是以有符号数的补码进行存储的)
//
return 0;
}因为%d要打印有符号的整数,但a是char a,所以要整型提升 初识结构体,整型提升及操作符的属性
c按道理来讲它的存储范围是0-255,不能存-1,但是应要存的话就要按照编译器的思路来解读,能存多少存多少。
#include<stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}这串代码的打印结果又是什么呢? 这串代码与上面代码的区别就是用了%u来打印 还是用刚刚的方法进行分析
#include<stdio.h>
int main()
{
char a = -128;//
//10000000 00000000 00000000 10000000 - 原码
//11111111 11111111 11111111 01111111 - 反码
//11111111 11111111 11111111 10000000 - 补码
//10000000 - a
// 整型提升
//11111111 11111111 11111111 10000000 - 补码
//11111111 11111111 11111111 10000000 - 反码
//11111111 11111111 11111111 10000000 - 原码
printf("%u\n", a);//4,294,967,168
//
//%u 是以十进制的形式打印无符号的整数 - 内存中存储的是无符号数的补码
//
return 0;
}因为%u 是以十进制的形式打印无符号的整数 - 内存中存储的是无符号数的补码 所以这里认为整型提升后的补码是无符号数的补码,即原反补都一样
补充一下,整型提升是根据变量的数据类型来提升的,这里的a是有符号的char类型,高位1就是符号位,所以整型提升前面补1,与%u,%d无关

结果就是这样一个超级大的值
再照猫画虎
#include <stdio.h>
int main()
{
char a = 128;//-128~127
//00000000 00000000 00000000 10000000 - 原码 - 反码 - 补码
//10000000 - a
//11111111 11111111 11111111 10000000 - 补码 - 反码 - 原码
//
printf("%u\n", a);//4,294,967,168
return 0;
}所以这里打印出来的值和上式一样

从数学角度来看,数组中存放的应该是这些数据:-1 -2 -3 -4 -5 … -1000,而char的取值范围是-128-127,放-128-127是没有问题的,但是其他数的解读方法就不同了。
这里就和之前的循环图有关了

就是把刚刚的顺时针循环反过来,逆时针循环,数组里放-1 -2 -3 -4 -5…-128之后,再-1就是127了,-128-1在数学中的结果是-129,-129放不下呀,这里就放的是其补码的低8位了,也就是


而127的补码的低8位也是如此 就这样循环往复凑够1000个元素

而strlen求字符串长度,其实是统计\0(0)之前字符的个数,0之前有(-1到-128)+(127到1)个字符,也就是255

可以看到这里死循环了。 变量i被定义为unsigned char类型,其取值范围是 0 到 255。 循环条件是i <= 255。由于i是无符号字符型,当它达到 255 打印一次hello world后再加 1,会发生溢出,重新回到 0,即上面的循环图 每次i从 255 溢出到 0 时,i <= 255这个条件始终为真,使得循环永远不会终止。

由于i的数据类型,这里的判断条件永远为真。
再看此代码,依旧是死循环,从数学角度来讲,i等于0输出结束之后,0-1=-1,但-1被当成无符号整数来看待存到i中去就是一个非常大的正数了。
这个应该是整数存储最难的一题了,是把指针和数据的存储放在一起了
#include <stdio.h>
//X86环境 小端字节序
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x, %x", ptr1[-1], *ptr2);//?
//%x 是以16进制的形式打印数据
return 0;
}先看这一行代码:
int* ptr1 = (int*)(&a + 1);这里取出数组a的地址,a的类型为数组指针int( * )[4],所以需要强制类型转换再赋值给(int * )类型的指针变量 ptr1[-1]向前指向一个整型,然后从该位置向后督导一个整型内容

4用%4打印时0x4,0x不会打印出来,所以打印结果直接为4
再看这一行代码
int* ptr2 = (int*)((int)a + 1);a为数组首元素地址,强制类型转换为整数,这时就是整数+1了

假设a=500,a+1=501,这两个数如果当作地址存在时,它们中间只差一个字节,也就是501从整型再强制类型转换为整型指针,该地址与a的起始地址只差一个字节。为什么呢?
假设a的地址为0x002ff40,其转换为整数(十进制)是1244992,该整数+1为1244993,该整数再写为16进制表地址,就是0x0012ff41。
打印的时候需要解引用,因为是整型指针,所以要访问4个字节,该4个字节以小端的形式放进内存,拿出来就是0x 02 00 00 00,实际上0x0是不会打印出来的。
常见的浮点数:3.14159、1E10(科学计数法,1.0*10的10次方),浮点数包括:float、double、long double类型。 浮点数表示的范围:float.h中定义

如图就有double类型数据表示的范围,默认是正数的表示范围,前面加负号就是负数的表示范围了。

按照整数的打印思路,所打印的数据应该如注释所写,但结果只有两个数据对上了。
所以可以得出结论:浮点数的存储方式和整数的存储方式不一样。
上面的代码中,n和* pFloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?
接下来说一下浮点数在计算机内部的表示方法。 根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:

举例来说: 十进制的5.0,写成二进制是101.0,相当于1.01 * 22
按照上面V的格式:

为什么是22可以参考下面十进制数的科学计数法写法:

小数点后二进制的含义分别是2-1,2-2,…

所以9.5按照V的写法就是下图

还有一种情况就是5.2转为二进制的过程中

0.2小数点后写很多位都凑不齐,永远差一点点,这种情况一个浮点数是无法明确转换为一个二进制序列的
前面提取出了S,M,E IEEE 754规定: 对于32位的浮点数(float),最高位1位存储符号位S,接着8位存储指数E,剩下23位存储有效数字M


前面写过,1<=M<2,就是说M必然是1.xxxxxxx的形式,其中xxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字(精度提高一位)。
指数E的存储就有些复杂了。 首先要知道,E是一个无符号整数(unsigned int) 这就意味着,如果E为8位,它的取值范围为0-255;如果E为11位,它的取值范围是0-2047。但是,科学计数法中的E是可以出现负数的:

所以IEEE 754规定了,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023,加上中间数再存入内存中去,因为浮点数的表示有其最大值和最小值,仅管E可以为负,但不会大于中间数。这里加一个中间值把数据放入内存,拿出来的时候再减去中间值即可。

再比如: 210的E是10,所以保存为32位浮点数时,必须保存为10+127=137,即10001001。
代码展示

虽然这样的浮点数存储方式已经设计的很精巧了,但是有些浮点数有可能无法精确保存,比如1.2,这是存在误差的,误差原因前面写过:

指数E从内存中取出来还可以再分为三种情况 E不全为0或不全为1(常规情况) 这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
E全为0 这是一个非常极端的情况,E=指数加127(或1023),此时E为0,说明原指数就是-127或-1023,此时这个数字就非常小了。 这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxx的小数。这样做是为了表示正负无穷接近于0的数字。

E全为1 E全为1,说明E+127=255,真实的E为128,这时就是1 * xxxxx * 2128非常大的数字了。 这时,如果有效数字M全为0,表示正负无穷大(正负取决于符号位s)
这三种情况的图如下,一般都讨论规范化区间

再回来看刚刚的题:

以上就是数据在内存中存储的全部内容了,说实话我个人觉得这里愈发变得更像数学了,要不说蓝桥杯也叫数学杯呢,喜欢的兄弟们不要忘记一键三连支持一下~