【C进阶】 one -> 数据的存储(一起来修炼内功)

本文详细介绍了计算机中的数据类型,包括整形、浮点型、构造类型和指针类型,着重讲解了整型在内存中的存储机制,如原码、反码、补码以及大小端的概念。此外,还探讨了浮点数的存储规则,遵循IEEE 754标准,并通过实例展示了数据类型的转换和内存存储差异。

※※※大家好!我是同学〖森〗,一名计算机爱好者,今天让我们进入学习模式。若有错误,请多多指教。

👍 点赞  收藏 📝留言 都是我创作的最大的动力!


目录

1、数据类型的介绍

1.1 类型的基本归类:

整形家族:

浮点型家族:

构造类型: (自定义类型)

指针类型: 

空类型: 

2、 整形在内存中的存储

2.1 原码、反码、补码 

2.2 大小端介绍  

 什么大端小端:

为什么有大小端呢? 

 练习

3、浮点型在内存中的存储       ★★★ 

3.1 浮点数存储规则  


1、数据类型的介绍

 类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
2. 如何看待内存空间的视角。

1.1 类型的基本归类:

整形家族:

char
unsigned char
signed char
char  虽然是字符类型,但是字符类型存储的时候,存储的字符的ASCII码值,ASCII值是整数
char c1;    //char 到底是有符号的?还是无符号的呢?

signed char c2;    //有符号的

unsigned char c3;  //无符号的
short
unsigned short [int]
signed short [int]
short s1;        //有符号的

signed short s2; //有符号的

unsigned short s3; //无符号的

//short 等价于 signed short。 int可省略

既然这样,可能就有小伙伴会问啦!什么是有符号的,无符号的呢?

在我们生活中会有一些数字,如温度:10, -10。

有正负的数据可以存放在有符号的变量中。

也有一些数字,如年龄,只有正数。

只有正数的数据可以存放在无符号的变量中。

那又会有小伙伴问:你这也没讲什么是有符号,无符号呀! 

例如:我们都知道short的大小为:short -  2byte -  16bit

所有的存储数据如下表所示:

如果是有符号的数据,最高位为符号位

最高位是0,表示正数。

最高位是1,表示负数。

如果是无符号的数据,最高位也是数据位。

int
unsigned int
signed int
int n1;         //有符号的

signed int n2;  //有符号的

unsigned int n3;//无符号的

//int 等价于 signed int。
long
unsigned long [int]
signed long [int]
long也同int 和short 即long 等价于 unsigned long [int], int可省略。

 那么 char 到底是有符号的?还是无符号的呢?

 不确定,c语言标准并未明确说明。取决于编译器,大部分的编译器下,char等价于signed char


浮点型家族:

float                //单精度浮点型
double            //双精度浮点型
long double 

构造类型: (自定义类型)

> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union

在这里可能有同学就有疑问了数组怎么就算自定义类型呢?

例如:int arr[10] ;         //arr的类型是: int  [10]

我们可以改变[10]为[5]

int arr2[5];          //int  [5]

也可以改变int为char

char arr3[10];        //char [10] 

所以数组只要它的类型发生变化如arr3;或者大小发生变化如arr2;或者全都发生变化都会变成不同的类型;即arr, arr1与arr2各不相同。


指针类型: 

int * pi;
char * pc;
float*  pf;
void*  pv;
指针变量是用来存储地址的!

空类型: 

void

 void 表示空类型(无类型)

通常应用于函数的返回类型、函数的参数、指针类型。

2、 整形在内存中的存储

2.1 原码、反码、补码 

计算机中的整数有三种表示方法,即 原码、反码和补码。
三种表示方法均有 符号位 数值位 两部分,符号位都是用 0 表示 ,用 1 表示 ,而数值位用1和0的二进制来表大小。

负 整数的三种表示方法:

原码: 直接将数值按照正负数的形式翻译成二进制。

反码:将原码的符号位不变,其他位依次按位取反就可以得到了

补码:反码+1就得到补码。

正整数的原码、反码、补码相同 

例如:

int a = 10;    //正整数的原、反、补一样。

//原码:00000000 00000000 00000000 00001010

//反码:00000000 00000000 00000000 00001010

//补码:00000000 00000000 00000000 00001010


int b = -10;    //负整数

//原码: 10000000 00000000 00000000 00001010

//反码: 11111111 11111111 11111111 11110101

//补码: 11111111 11111111 11111111 11110110

负整数由补码变为原码 

方法一:补码减一,再符号位不变,其他位按位取反。

方法二:补码符号位不变,其他位按位取反,再加一。

有人可能会有疑问了。同一个补码的通过不同的方式得到的原码会不同吗?总不能有两个原码吧!

究竟结果如何,让我们走着瞧。

int b = -10;

//补码: 11111111 11111111 11111111 11110110

//方法一:

//补码减一:11111111 11111111 11111111 11110101

//按位取反:10000000 00000000 00000000 00001010

//方法二:

//补码取反:10000000 00000000 00000000 00001001

//再加一:10000000 00000000 00000000 00001010


//没错,方法一和方法二的结果相同,都可转化为十进制的10

既然有三种形式,那么计算中存储的是那种类型呢?

计算机内存储的是数据的补码。

那么为什么数据在计算机中存的是补码呢?

例如我们求1-1;

	int i = 1 - 1;
	//因为CPU中只有加法器,
	//所以1-1会被转换成1 + (-1)
	// 若计算机里储存的是原码;
	// 1的原码: 00000000 00000000 00000000 00000001
	//-1的原码: 10000000 00000000 00000000 00000001
	//则i的原码:10000000 00000000 00000000 00000010
	//1-1变成了-2;
	//若是反码
	//	 1的补码:00000000 00000000 00000000 00000001
	//	-1的补码:11111111 11111111 11111111 11111111
	//	相加:  1 00000000 00000000 00000000 00000000
	//	而整型只能保存32bit,所以1丢失,
	//	就变成:  00000000 00000000 00000000 00000000
	//	i = 0;			
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统
一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

  扩展一点:char的取值范围多少,unsigned char呢?

char占1byte = 8bit

char
//		二级制		十进制
//原码  0000 0000		0
//		0000 0001		1
//		0000 0002		2

//		……			……

//		0111 1111		127
//		1000 0000	直接解析为  -128 
//		1000 0001	1111 1111	-127

//		……

//		1111 1110	1000 0010	-2		
//		1111 1111	1000 0001	-1
//					原码

char的范围为-128到127,unsigned char的范围为0到255 

 例题:

猜猜打印结果是什么

#include <stdio.h>

int main()
{
	unsigned int ch = -10;
	//-10
	//10000000000000000000000000001010
	//11111111111111111111111111110101
	//11111111111111111111111111110110
	//
	printf("%u\n", ch);
	printf("%d\n", ch);
	return 0;
}

%u是打印无符号数,意思是你要我打印的一定是无符号数,不是无符号数,我也认为是无符号数 

//%d 是打印有符号数,意思是你要我打印的一定是有符号数,不是有符号的数,我也认为是有符号数

输出结果 

没错这时候你可能又有疑惑了,4294967286是什么鬼,随机数吗?

让我们用计算器算一下-10的补码。 

没错和我们打印的一样。正如你想。它就是-10的补码。

2.2 大小端介绍  

 什么大端小端:

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位 , ,保存在内存的高地址中。

例如 int a = 0x11223344;

为什么有大小端呢? 

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器)。

另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式就将 0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

 例题:

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。

 答案:

#include<stdio.h>

//int check_sys()
//{
//	int a = 1;
//	char* p = (char*)&a;
//	return *p;
//}

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

int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}


	return 0;
}

由图可知可用char类型 的p指针来存int的地址,若 *p为1就是小端。若*p为0就是大端

 练习

输出打印结果

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

运行结果:

分析: 

-1是整数,占32bit。

-1 原码:10000000 00000000 00000000 00000001

-1反码: 11111111 11111111 11111111 11111110

-1补码: 11111111 11111111 11111111 11111111

因为char占8bit,故只能存 11111111

而打印a,b时,%d是整型,需要整型提升。 而a为有符号的。符号位为1,提升时补1.

整型提升为:11111111 11111111 11111111 11111111        即-1

而打印c时,%d是整型,需要整型提升。c为无符号的,为正数,提升时直接补0

整型提升为:00000000 00000000 00000000 11111111

因为符号位为0,表示正数。原反补相同。为255

3、浮点型在内存中的存储       ★★★ 

浮点数的两种形式:3.1415,2E10

浮点型的种类:float, double, long double

让我们来看一个代码:

大家可以猜猜结果是多少?

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

答案是否和你想的一样呢?

从上结果可以看出,整型与浮点型的存储形式是不同的,没办法强制转换。 

3.1 浮点数存储规则  

根据国际标准 IEEE (电气和电子工程协会) 754 ,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。 2^E表示指数位。

现在可能有同学比较疑惑了,这是什么? 

让我们先来看道例题 

5.5用上述形式表示。

十进制:5.5

转化成二进制:101.101,对吗?

 小数点后面的1代表2^-1,第三个1代表2^-3.

101.101转化为十进制为2^2+1=5,  2^-1+2^-3=0.625,相加为 5.625。

而5.5的二进制形式应改为101.1。

因为是正数,所以S = 0,101.1变成1.011*2^2,(可以类比10进制的科学计数法)。

其中1.011是M,第二个2是E

综上:5.5 =(-1)* 0 * 1.011 * 2^2.

 十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1M=1.01E=2

5.0 = (-1) * 1 * 1.01 * 2^2。

IEEE 754 规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。


对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。 

IEEE 754 对有效数字 M 和指数 E ,还有一些特别规定。
前面说过, 1≤M<2 ,也就是说, M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。
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
比如, 2^10 E 10 ,所以保存成 32 位浮点数时,必须保存成 10+127=137 ,即
10001001

而计算机只存储S、E、 M的值。

讲了那麽多,为防止讲错,误人子弟。让我们在编译器上跑一边。 

#include<stdio.h>
int main()
{
	float a = 5.5;
	//101.1
	//(-1) * 0 * 1.011 * 2 ^ 2;
	//s = 0;	1bit
	//E = 2 + 127 = 129;	8bit 加中间数再转换至二进制
	//E:1000 0001
	//M:1.011	32bit		小数点后面的部分,不够不够补0
	//01100000000000000000000
	//0 10000001 01100000000000000000000
	//    0100 0000 1011 0000 0000 0000 0000 0000    
	// 0x	4	0	  b		0	0	0	 0	  0    转化为16进制。
	// 因为VS处理器是小端,
	// 存储为:00 00 b0 40
	return 0;
}

 由上图所示,我们的推断是正确的。


指数E从内存中取出还可以再分成三种情况:
E 不全为 0 或不全为 1
这时,浮点数就采用下面的规则表示, 即指数E的计算值减去127(或1023),得到真实值,再将
有效数字M前加上第一位的1. 。
比如:
0.5 1/2 )的二进制形式为 0.1 ,由于规定正数部分必须为 1 ,即将小数点右移 1 位,则为
1.0*2^(-1) ,其阶码为 -1+127=126 ,表示为
01111110 ,而尾数 1.0 去掉整数部分为 0 ,补齐 0 23 00000000000000000000000 ,则其二进
制表示形式为 :
0 01111110 00000000000000000000000
E 全为 0
这时, 浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字 M 不再加上第一位的 1 ,而是 还原为0.xxxxxx的小数 。这样做是为了表示 ±0 ,以及接近于 0 的很小的数字。
E 全为 1
这时,如果有效数字 M 全为 0 ,表示 ± 无穷大(正负取决于符号位 s );

这时再让我们再回头看刚开始的题 

 

 n的反码为:00000000000000000000000000001001

所以        printf("n的值为:%d\n", n);        //n的值为:9

把整形强制转换成单精度浮点型。

*pFloat         0 00000000 00000000000000000001001

因为被转换成浮点数,所以 以浮点数的提取方式来提取。

E = -126, M = 0.00000000000000000001001, S = 0

即:((-1)^0 *  0.00000000000000000001001 * 2^(-126)

我们知道%f默认存储小数点后面6位小数。光一个M就四舍五入为0了,别提再乘上一个

2^(-126)

printf("*pFloat的值为:%f\n", *pFloat);        //*pFloat的值为:0.000000

*pFloat = 9.0;

存储使用浮点数的形式进行储存。

    9.0
    1001.0
    1.001 * 2^3
    (-1)^0 * 1.001 * 2^3
    S = 0
    M = 1.001
    E = 3   +127
    0 10000010 00100000000000000000000

整型打印的话,计算机把它认为称补码。

而0为符号位是正数。

正数,原、反、补相同

而上列数转化成十进制

printf("num的值为:%d\n", n);        //num的值为:1,091,567,616

而浮点型再提取处理也必然是9.000000.

printf("*pFloat的值为:%f\n", *pFloat);        //*pFloat的值为:9.000000
 

推测结果: 

n的值为:9

*pFloat的值为:0.000000

num的值为:1,091,567,616

*pFloat的值为:9.000000

结果相同, 推测正确。

浮点型在内存中的存储 你会了吗?

若有疑问。或觉得小主有需要改善的地方。欢迎大家在文章末留言。


 朝暮与年岁并往,愿与你一同行至天光。

评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

同学〖森〗

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

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

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

打赏作者

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

抵扣说明:

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

余额充值