本期介绍🍖
主要介绍:类型的基本分类,整型在内存中的存储模式,浮点数在内存中的存储模式,为什么会提出原码、反码、补码的概念?什么是大小端存储顺序。👀
文章目录
一、数据类型介绍🍖
1.1 基本内置类型:🍖
类型名 | 介绍 | 存储大小(字节) |
---|---|---|
char | 字符型 | 1 |
short | 短整型 | 2 |
int | 整型 | 4 |
long | 长整型 | 4/8 |
float | 单精度浮点型 | 4 |
double | 双精度浮点型 | 8 |
注意:
1. 类型的意义:类型不仅决定了开辟空间时的大小,还决定了看待内存空间时的视角。
2. long类型在规定大小的时候,只规定:sizeof(long) >= sizeof(int),并没有明确规定到底是4还是8个字节。正真决定其大小的是编译器的平台:在32位平台上long类型大小为4个字节,而在64位平台上long类型大小为8个字节。
1.2 类型的基本分类:🍖
注意:
1. C标准规定:short、int、long默认都是有符号的,但char到底是有符号还是无符号的标准未定义,完全取决于编译器的实现(VS编译器上char是有符号的)。
2. 构造类型(自定义类型):就是一种可以自己创建或者定义某种新类型的类型。就譬如数组,它的类型会随着数组元素的类型和数组元素的个数的变化而变化着,而元素的类型和个数是由我们所决定的。
3. float类型的精度要比double类型的精度要低(注意这里的精度指的是存储值与真实值的接近程度)。浮点型往往不能够准确的存放数据,而是以无限接近的方式来存放的。
4. void类型又称:空类型 / 无类型,通常应用于数组的返回类型、数组参数的类型、指针的类型。
二、整型在内存中的存储模式🍖
2.1 原码、反码、补码🍖
我们知道数值的表示形式有:二进制、八进制、十进制、十六进制,而在计算机中二进制又存在三种表示形式:原码、反码、补码。三种表示形式均有符号位和数值位两部分,二进制序列中的最高位就是符号位(若为0则表示正,若为1则表示负)。
注意:正数的原码、反码、补码相同。负数的原、反、补并不相同,需要通过一系列计算才能得到。计算规则为:
原码: 将数值直接转换成二进制形式就行。
反码: 原码的符号位不变其他位按位取反就可以得到反码。
补码: 反码+1就可以得到补码。
那整型到底是以什么样的模式存放的呢?通过调试中的内存我们就可以得知,如下图所示:
由此可以得出一个结论:整数在内存中是以补码的形式进行存放的。(备注:该结论非常重要)
2.2 为什么要以补码的形式进行存放?🍖
在计算机系统中整数一律用补码来表示和存储,原因在于:
- 使用补码,可以在运算的时候将符号位和数值域统一处理,同时加减法也可以统一处理。
举个例子:如果不是以补码的形式存放,而是以原码的形式进行存放。那在计算(1)+(-1)的时候我们就无法得我们所认为的结果。原码是无法进行负数的运算的,但补码却可以。如下图所示:
补码的提出顺带着解决了另一个问题:CPU只有加法器。那CPU在读取完数据后如何进行减法操作呢?只需要将减法运算转换成加负数的运算不就行了。
- 补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
什么意思呢?举个例子:如下图所示
我们知道原码通过先按位取反再+1操作后可以得到补码,按道理补码只有反过来先-1再按位取反操作后得到的结果才会与原码相同。可事实上,补码完全可以使用原码转换成补码的方法来求原码(即:补码先按位取反后再+1),求得的结果完全与原码相同。
注意:这是补码的一种特性,是设计之初就存在的,没有必要深究为什么会出现这种情况。
- 如果内存是以补码的形式存放,可以保证数据在整型提升前与提升后表达的值不变。
之前《隐式类型转换》一章中详细介绍了整型提升,这是链接:隐式类型转换
看到这不禁感叹,在计算机设计之初到底是如何想到补码的,太牛逼了,向大神致敬!!!
2.3 有符号数和无符号数🍖
有符号数二进制序列的最高位是符号位。而无符号数的最高位不是符号位,而是有效的数值,因为无符号数是不可能出现负的情况,所以没有必要浪费一个bit位的精度。故有符号数和无符号数的取值范围如下图所示:
类型 | 存储大小(bit位) | 取值范围 |
---|---|---|
signed char | 8 | (-27)~(27-1) |
unsigned char | 8 | 0 ~(28-1) |
signed short | 16 | (-215)~(215-1) |
unsigned short | 16 | 0 ~(216-1) |
signed int | 32 | (-231)~(231-1) |
unsigned int | 32 | 0 ~(232-1) |
signed long | 64 | (-263)~(263-1) |
unsigned long | 64 | 0 ~(264-1) |
注意:当存放的整型数值超过类型的存储范围时,就会导致数据的丢失。而真正存放进去的数值,只会是一个在该类型存储范围内的数。举个例子:
如若有一个char类型的变量,初值为0。使得其每次循环只自增1,循环1000次。问该变量会最后的结果?
#include<stdio.h>
#include<Windows.h>
int main()
{
char n = 0;
int i = 0;
for (i = 1; i <= 1000; i++)
{
printf("%d ", n);
n++;
Sleep(100);
}
return 0;
}
结果是什么并不重要,重要的是知道了有符号整型存在着这样一种规律,即随着变量不断的自增,它的值只会在其类型的存储范围内无限的循环下去,并不会超出该范围。 同理对于无符号整数来说,其值只会在0~255这个范围内循环下去(转折点为:255自增1变为0)。
2.4 练习🍖
- 例一:求下面代码输出结果会是什么?
#include<stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("%d %d %d\n", a, b, c);
return 0;
}
为什么会输出最后输出的-1 -1 255,而不是我们所认为的 -1 -1 -1 呢?
- 例二:计算下面代码输出的结果
#include<stdio.h>
int main()
{
char a = -128;
char b = 128;
printf("%u\n", a);
printf("%u\n", b);
return 0;
}
- 例三:计算下面输出的结果
#include<stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
- 例四:执行下面代码会发生什么?
#include<stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
执行该代码会陷入死循环。因为当 i=0 时,再次自减1得到的 i 并非负数,而是正的4294967295。所以会从4294967295往下减至0以此无限的循环下去。
- 例5:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[1000];
int i;
for (i = 0; i < 1000; i++)
{
arr[i] = -1 - i;
}
printf("%d\n", strlen(arr));
return 0;
}
三、浮点数在内存中的存储模式🍖
3.1 举个例子🍖
在讲述浮点数的存储模式前,先来看一个例子:
可以看出,整型和浮点型在对同一块空间进行读取操作时得到的结果是不同的。可为什么会不同呢?根本原因是由于整型和浮点型向内存中存储时的方式有所不同,向外取时的方法自然也是天差地别的。所以同一块内存空间,不同的读取方法,结果自然大相径庭。
3.2 浮点数存储规则🍖
根据国际标准IEEE(电气电子工程师学会)754规定,任意一个二进制浮点数V都可以用科学计数法的方式来表示,如:V = (-1)S × M × 2E。
- (-1)S表示符号位,S为0表示正数,S为1表示负数。
- M表示有效数字位(1 <= M < 2)。
- 2E表示指数位。
举个例子:十进制5.5,写成二进制是101.1,科学计数法是1.011 × 22,按照上面的格式可以得知S=0,M=1.011,E=2。由于每一个二进制浮点数都能以上面的格式来表示,故内存中只需要保存这三个符号位就相当于存放了一个浮点数。
3.2.1 IEEE754规定🍖
对于float类型,最高位的1位是符号位S,接下来的8位是指数位E,剩余23位是有效数字M。
对于double类型,最高位的1位是符号位S,接下来的11位是指数位E,剩余52位是有效数字M。
3.2.2有效数字M、指数E的特别规定🍖
我们知道二进制浮点数的科学计数法中有效数字M总是 1.xxxxxxx 的形式,那么在存储M时1可以被舍去(因为默认第一位总是1),只需存放后面的小数位。至于读取的时候再加上这个1不就行了,这样做的目的是可以节省出一位有效数字。所以IEEE754规定,在计算机内保存M时,只需要存放小数位。读取时,再加上第一位的1。
指数E是一个无符号的整数(unsigned int)。对于float类型来说,其指数位E占8个bit位,存储范围为0~255。可是要知道指数E是存在负数的可能的,就譬如:二进制的0.00101,它的E = -3。所以IEEE754规定,存入内存中的真实值必须再加上一个中间数。对于8位的E,中间数为127;对于11位的E,中间数为1023。比如,210的E=10,需要加上中间数127,所以存入值为137,即:10001001。
指数E从内存中取出时还需要分为3种情况:
1. E不全为0或不全为1时
取出内存中存放的指数E,然后减去中间数(127 / 1023)得到真实值,再将有效数字M前加上第一位的1。
2. E全为0时
这时浮点数指数E的真实值为 1-127 或 1-1023 。有效数字M不再加上第一位的1,而是而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0很小的数字。
3. E全为1时
如果这时有效数字M全为0,则表示±无穷大。
3.3 回头再看该例子🍖
四、大小端字节序🍖
我们发现之前整型或浮点型在内从中存放时都是倒着存放的,如图所示:
这就要说到大端存储顺序和小端存储顺序了。
4.1 什么是大小端🍖
大端存储和小端存储描述的是:一个数据在内从中到底是以什么样的顺序存放到。
- 大端存储顺序:
把一个数据的高位字节序内容存放到低地址处,低位字节序内容存放到高地址处。- 小端存储顺序
把一个数据的高位字节序内容存放到高地址处,地位字节序内容存放到低地址处。
所以我们发现在VS编译器上是以小端存储顺序来存放的,也就是倒序存放。如下图所示:
注意:大小端是按字节为单位的顺序来排放的,并不是以bit位为单位来排放的。
4.2 为什么存在大小端🍖
为什么会有大小端之分呢?这是因为在计算机系统中是以字节为单位来存放的,所以对于那些所需空间大于1个字节的变量来说,如:short、int、float、double等等。就会面临一个问题:如何将多字节安排。因此导致了大端存储模式和小端存储模式的出现。
注意:数据的类型仅仅只决定了数据在内存中的存储模式,并没有决定存储时的顺序,而决定到底是大端存储还是小端存储的是硬件。
4.3 如何判断当前计算机是大端还是小端🍖
请设计一个小程序来判断当前机器的字节序。
方法一:
#include<stdio.h>
int check_sys()
{
int n = 1;
return *(char*)&n;
}
int main()
{
int flag = check_sys();
if (flag == 1)
printf("小端字节序\n");
else
printf("大端字节序\n");
}
方法二:
int check_sys()
{
union
{
int n;
char c;
}un;
un.n = 1;
return un.c;
}
int main()
{
int flag = check_sys();
if (flag == 1)
printf("小端字节序\n");
else
printf("大端字节序\n");
}
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。