吃鸡蛋要先打破较小的一端???

所有人都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端。可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。———《格列佛游记》

那今天要说的就是大端、还是小端,但不是剥鸡蛋,而是计算机内存的存储方式

  • 数据类型介绍
  • 整形在内存中的存储方式
  • 大小端介绍
  • 浮点型在内存中的存储方式

在之前已近学了许多的数据类型了,今天我们来深度剖析一下它们是怎么在内存中存储的

数据类型介绍

整型家族

1. char 字符数据类型 所在一个字节大小 ,分为signed char 和unsigned char
2. short 短整型 所占二个字节大小,分为signed short 和unsigned short
3. int 整型 所占四个字节大小,分为signed int 和unsigned int
4. long 长整型 所占四个字节大小,分为signed long 和unsigned long
5. long long 更长的整型 所占被个字节大小,分为signed long long 和unsigned long long

浮点数家族

1.float 单精度浮点数
2.double 双精度浮点数

结构类型

1.数组类型
2.结构体类型 struct
3.枚举类型 enum
4.联合类型 union

指针类型

1. char* pc
2. int* pi
3. float* pf
4.void* pv

空类型

1. void 表示空类型(无类型)
通常用于函数的返回类型、函数的参数和指针类型

原、反、补

整型由这么多那他们在内存中是如何存储的呢
在计算机中存储二进制的方法由三种:它们是原码反码补码

正数的原码、反码和补码相同
对于负数它的反码和补码是要计算的

  • 原码: 就是直接将数值转化成二进制进行了,对于signed来说最高位为符号位,而unsigned来说所有位都是数值位
  • 反码: 根据原码可以得到,符号位不变,其他位按位取反(0变1,1变0)
  • 补码: 根据反码可以得到,将反码加一

可以通过补码取反再加一得到原码原码到补码的过程相同,也不需要增加额外的电路

对于整数来说在计算机内存中存储的都是补码
那为什么是补码而不直接用原码呢
1、保证了0的唯一性,保证了数的表示的准确性。
2、让加减可以统一处理,优化了数的运算过程。
3、解决了自身逻辑意义的完整性
在CUP内只有加法器,这样加法和减法就可以统一处理了

大端小端问题

什么是大端?什么又是小端呢?
我们创建一个int 类型的变量来看看在内存中是怎样存储的

int a = 0X11223344;//创建一个int 类型的变量正好是四个字节

在这里插入图片描述
如图所示用的是高地址存储的是高位的数,而低地址存储的是低位这就是小端存储
在这里插入图片描述
那大端的就是高地址存储的是低位,而低地址存储的是低位
在这里插入图片描述
那不通过内存能不能判断编译器是大端存储还是小端存储呢
方法一样优化一下

int main()
{
	int a = 1;
	//1 在内存中的存储方式为补码,但是正数的原反补都是一样的
	//00000000000000000000000000000001
	//高位——————————低位
	//如果编译器是大端的话 低地址存储的是高位,如果用char*访问的话不会影响结果
	//相反如果编译器是小端的话低地址存储的是低位,如果用char*访问的话会影响结果的
	char* pa = (char*)&a;            //把原本是int*的强转为char*
	*pa = 0;                         //char*每次只访问一个字节
	//如果*pa=0 并没有改变a,就能说明编译器是
	if (a)
	{
		printf("大端");
	}
	else
	{
		printf("小端");
	}
	return 0;
}

优化一

int main()
{
	int a = 1;
	char* pa = (char*)&a;
	//判断pa里的值为多少,如果为一就说明小端
	//反之为大端
	if (*pa == 1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
}

优化二分装成函数

int Check_sys()
{
	int a = 1;
	char* pa = (char*)&a;
	if (*pa == 1)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{

	if (Check_sys() == 1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
}

优化三

int Check_sys()
{
	int a = 1;
	return *(char*)&a;
}
int main()
{

	if (Check_sys() == 1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
}

到这如果再优化的话交给你们吧
那我们来看看下面代码的运行结果吧

#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;
}

分析
首先创建了一个变量 a 类型是char,把 -1赋给了变量a
-1在内存中存的是补码
-1的原码:10000000 00000000 00000000 00000001
-1的反码:11111111 11111111 11111111 11111110
-1的补码:11111111 11111111 11111111 11111111
而char类型放不下这么多的字节,只能放下一个字节就是:11111111
接着又创建了变量 b类型是 signed char,把 -1赋给了变量b
和上面的一样在 b中存放的是: 11111111
最后创建了变量 c类型是unsigned char,把 -1 赋给变量c
也是存储的是11111111
下面打印

打印的是char类型的变量,却以%d打印的,%d 是十进制数的形式,打印有符号的整数
所以有整型提升,对于无符号数提升是:高位全补零,而有符号数提升是:正数高位补零,负数高位补一(也可以说有符号数提升提升的是符号位)

大多数编译器是把char 当作signed char 来看的 因为C语言没有规定,有些编译器并不是这样的
那先来看变量 a在内存中存储的是11111111类型是char是有符号位的,高位为 1所以提升之后是11111111 11111111 11111111 11111111而这个数是负数是补码,要转换为原码,取反加一,即可得到原码,反码为:10000000 00000000 00000000 00000000,反码为:10000000 00000000 00000000 00000001这个值就是 -1啦
再来看变量 b,和变量 a一样也是signed char 所以打印的时候也是 -1
变量 c就不一样啦,它是unsigned char 在内存中存储的是11111111它是无符号的提升的时候高位全补零为00000000 00000000 00000000 11111111这个值是正数,原反补相同,所以不用计算,这个二进制转成十进制是255
所以会打印a = -1,b = -1,c = 255,
在这里插入图片描述
下一道:

#include <stdio.h>
int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}

分析
首先创建了一个变量a类型是char,把-128赋值给变量a
-128的原码为:10000000 00000000 00000000 10000000
反码为:11111111 11111111 11111111 01111111
补码为:11111111 11111111 11111111 10000000
而char类型放不下这么多的字节,只能放下一个字节就是:10000000
下面打印

打印的是char类型的变量,却以%u打印的,%u是十进制数的形式,打印无符号的整数

a在内存中存储的是11111111,类型是char是有符号位的,高位为 1,也就是负数,所以提升是提升为11111111 11111111 11111111 10000000但%u打印的是无符号数,所以会把这个数转成十进制打印
在这里插入图片描述
下一道

#include <stdio.h>
int main()
{
	char a = 128;
	printf("%u\n", a);
	return 0;
}

分析
首先创建了一个变量a类型为char,把 128赋给了变量 a
正数的原反补相同为00000000 00000000 00000000 10000000
而char类型放不下这么多的字节,只能放下一个字节就是:10000000
到了这里就和上面一样了
最后打印结果一样
在这里插入图片描述
那除了这样的方法还有别的方法吗
我们知道一个char类型的变量大小为1个字节,也就是8个比特位
那如果再加上一个符号位呢,实际的数值位就只有7的比特位了
最大值为0111 1111十进制为127
最小为1000 0000十进制为-128
为什么最小不是1111 1111因为这是补码,前面我们得到结论补码为全1的时候,这个数值为为-1
根据它的取值范围,我们就可以画一个图叫轮回吧

在这里插入图片描述
如果像这一题一样把128赋给变量a,此时它是存不下的,通过这个图可以看出127下一个就是-128,所以和上一题的结果一样的,同样如果加上128+256的结果给a,那最后打印的结果是一样的
在这里插入图片描述
下一道

#include<stdio.h>
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}

分析
首先创建了一个变量 i类型为 int,把 -20赋给了变量 i
-20原码的为10000000 00000000 00000000 00010100
-20的反码为11111111 11111111 11111111 11101011
-20的补码为11111111 11111111 11111111 11101100
放在int4个字节正好放满
接下来创建了一个变量 j类型为unsigned int,把 10赋给了变量 j
10的原码为00000000 00000000 00000000 00001010
正数的原反补相同,不用计算
i+j
11111111 11111111 11111111 11101100
00000000 00000000 00000000 00001010+
——————————————————
11111111 11111111 11111111 11110110
得到的这个数是补码,要转换成原码
原码为:11111111 11111111 11111111 11110110
反码为:10000000 00000000 00000000 00001001
补码为:10000000 00000000 00000000 00001010
而这个数的十进制为-10
最后以%d的形式打印,十进制数形式,打印有符号的整数
结果就是-10啦
在这里插入图片描述
下一道

#include<stdio.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

既然放到了这里那肯定就不是循环10次就结束了
分析
首先创建了变量 i类型为unsigned int,并没有初始化
大家想象一下,刚刚的轮回是signed char的,那如果是unsigned char呢
在这里插入图片描述

注意 如果是到了256,那么实际上存到的值为0
如果到了-1,那么实际存的值为255

那我们把它想象成unsigned int 如果 i减到-1时,在内存总存的其实是unsigned int 的最大范围,也就是说这是个死循环,条件恒成立
在这里插入图片描述
下一道

#include<stdio.h>
#include<string.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

分析
我们看最后 strlen(a),a是个数组,类型为char,strlen统计字符串中’\0’有几个元素,而’\0’的ASCII码值为0,也就是说如果有元素为0,就会计算大小,for循环中对数组 a进行初始化
大家想象一下,数组中的元素是什么,第一个为 -1,从 -1开始到-128,再看那个轮回如果-1-128的值赋给下标为128的元素,那a[128]里的元素是什么呢?那就是 127,然后循环继续一直到 256的时候a[256]里的值就为0,接下来后面的赋什么值就不重要了,所以数组 a的元素为 -1到-128和127到0,后面的值和这个一样,但是 strlen函数统计到0就结束了,在0之前的元素个数为255
这是a[225]
在这里插入图片描述
在这里插入图片描述
最后一题

#include <stdio.h>
unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}

如果看到这里,再看这道题就会觉得很简单
没错就是死循环,在打印hello world
因为变量 i的类型为unsigned char 所以条件恒成立,当把256赋值给 i时,实际上 i得到的值是 0
在这里插入图片描述

浮点型在内存中的存储

浮点数家族
分为:floatdoublelong double

注意:整数的存储和浮点数的存储是不一样的

浮点数为什么叫浮点数呢?
顾名思义:浮点……浮动的点
比如1.234可以写成12.34*10-1也可以写成0.1234*101
先来看一题

#include<stdio.h>
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;
}

分析
首先创建了变量n类型为int ,把 9赋给了变量n,创建了一个指针变量 pFloat,指向的元素类型为 float,把 a的地址强制类型转换成 float赋给了指针变量 pFloat。
9的二进制位000000000 00000000 00000000 00001001 正数的原反补都一样
以 %d的形式打印 a,a的值为9,所以打印 9
接下来以 %f的形式打印 pFloat指向的元素
把 pFloa所指向的元素,通过float的视角赋值为 9.0
以 %d的形式打印 n
以 %f的形式打印pFLoat所指向的元素,上面把赋值为了 9.0,所以打印 9.000000
那中间打印上面呢?
在这里插入图片描述
*pFloat的值为什么是 0.000000呢
num的值为什么是 1091567616呢
其实整型存储的方式和浮点型存储的方式是不同的

根据国际标准 电气电子工程师学会(Instituteof Electrical and Electronics Engineers)754,任意一个二进制浮点数 V可以表示成下面的形式:

(-1)S * M * 2E
其中S的正负决定了整的数的正负
M表示有效数字,范围是 1<=M<2
2E表示指数位

举两个列子:
比如浮点数 9.5写成二进制为1001.1,那就可以写成1.0011 * 23
按照V的格式就可以写成 (-1)0 * 1.0011 * 23
S=0,M=1.0011,E=3
浮点数-5.0写成二进制为101.0,那就可以写成-1.01 * 22
按照V的格式就可以写成(-1)1 * 1.01 * 22
S=1,M=1.01,E=2
IEEE754规定:

最高位的一位为S(符号位)
接着的8位是E指数位
剩下的23 位为M(有效数字)

如图
在这里插入图片描述
对于64位的double的类型

最高位的一位仍位S(符号位)
接着的11位为E(指数位)
剩下的52位为M(有效数字)

iEEE754对有效数字M和指数E有一些特殊的规定
因为M的取值范围在是1<=M<2的,也就是说整数部分永远为 1,因此就将1舍去了,实际存的是小数部分。这样做可以多存储一个位。
就比如上面的1.001实际存储的是001,等到读取的时候,再把1加上去。

关于E的规定:
E是个unsigned int 类型的数,那么如果是8的字节的话E的取值范围就是0到255,11个字节的话就是0到2047,但是我们通过计算,得到的E有可能是负数,此时IEEE规定:存入内存是E要加一个中间值,用来调和,8位的中间值为127,11位的中间值为1023

那从内存中取出来分为三种情况
E的值有0有1的情况
取出时指数-127(或者1023),得到真实值,再将M的第一位加上1
比如9.0,写成V的形式为(-1)0 * 1.001 * 23
S=0,M=1.001,E=3
E的只要加上127,得到130,二进制位1000 0010
M把整数位的1舍去为001,后面补0
9.0再内存中实际为
0 10000010 00100000000000000000000

E为全0时
大家想象一下,如果E为全0,它的真实值为-127,是一个很小的数,那么IEEE754规定:此时E的真实值为1-127(或者1023),有效数字M也不需要再加1了,而是还原为0.xxxxxxx的小数,来表示正负无限接近与0的数

E为全1时
大家想想一下,如果为全1,那么E的值时很大的,它的真实值为 128(或者1024),此时就用来表示正负无穷大(小)的数

那再来看那道题

#include<stdio.h>

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;
}

9的二进制位00000000 00000000 00000000 00001001
第一个是以%d的形式打印的,%d十进制形式的形式,打印有符号的整数,所以打印的第一个就是 9
第二个是以%f的形式打印的,打印的是pFloat 这是一个指向浮点型的指针,在打印是它认为00000000 00000000 00000000 00001001是一个浮点数0 00000000 00000000000000000001001
前面说E位全0的时候这个数就是无限接近与0的数,而00000000000000000001001在再乘以2的-127的话将是一个非常非常小的数,所以打印0.000000
接着将9.0赋给了pFloat所指向的变量
9.0在内存中实际存储的是0 10000010 00100000000000000000000
第三个以&d的形式打印这个数 这个数也是比较大的
在这里插入图片描述
第四个以%f的形式打印
pFloat ,就是这个值0 10000010 00100000000000000000000,这个值就是9.0

到这里说完啦
那你是小端还是大端呢

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨曦的iPhone

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值