C语言-CH09-数据存储

C语言-CH09-数据存储

1、数据类型的介绍

内置类型:

type32bit64bit
char11
short11
int44
long48
long long88
float44
double88

类型的意义:
1、决定了开辟空间的大小。
2、决定了看待这个类型数据的内存空间的视角。
(1)在使用这些类型时会在内存开辟一个大小(大小即后面跟的字节),大小决定使用范围。
(2)类型决定了看待内存的视角。比如同样开辟了4个字节的内存。如果是整型创建的,则认为这4个字节是整型,如果是float创建的,则认为4个字节是float。

1.1类型的基本归类

1.1.1整型
char
	unsigned char
	signed char
int
	unsigned int 
	signed int
short
	unsigned short [int]
	signed short [int]
long
	unsigned long [int]
	signed long [int]
long long
	unsigned long long [int]
	signed long long [int]

(1)char本质上存储的是ASCII码,也是整数。
(2)我们写的int a 等价于 signed int a。而char a 等价于 signed char a还是unsigned char a取决于编译器
(3)生活中有些值是没有负数的,比如身高、体重、年龄:
这时候用int定义还是unsigned int定义呢?

什么是符号位:
符号位就是类型存在内存中的补码的第一个数字,所在的位叫
符号位。如int a = 1:
00000000000000000000000000000001
第一个零为符号位。

符号位 = 0:正数
符号位 = 1:负数

而unsigned int即符号位不再是符号位,跟其他的位是一个意思。
这个时候全是正数,没有负数如:
10000000000000000000000000000001
换算成十进制即:1*2^31+1*2^0 = 2^31+1

只要是有正有负的用int或者signed int,只有正数的用unsigned int。

1.1.2浮点簇
float
double

float的精度低、范围小,double的精度高,范围大。

1.1.3构造类型
数组类型
结构体struct
枚举enum
联合union
1.1.3.1数组类型

数组也是一种自定义类型或者构造类型。

定义的变量其类型
int arr1[10]int [10]
char arr2[20]char [20]
1.1.3.2结构体
1.1.3.3枚举
1.1.3.4联合
1.1.4指针类型
char* p
int* p
struct* p
void* p
...
1.1.5空类型
void

用在函数返回类型、函数参数类型、空指针

void test(void)
{
	printf("hehe\n");
}

int main()
{
	test(1);
	void* p;
	return 0;
}

1. 2、整型在内存中的存储

整型范围定义在头文件limits.h中
整数的二进制表示形式有三种:

原码
反码
补码

另外,正数和负数的形式有区别:

正数的原码补码反码相同。
负数有计算规则:
原码:直接写出二进制序列。
反码:符号位不变,其他的位置取反。
补码:反码+1。

可以自行编写代码,然后在VSCODE中查看内存情况,我们直接说结论:
整型在内存中存的是补码的二进制序列
why?

因为在计算机系统中,补码可以将符号位和数值域统一进行处理。
加法和减法也可以统一进行处理。(CPU中只有加法器)。此外
补码原码的相互转换,其运算过程也是相同的,不需要额外硬件电路

如:

int a = 1;
int b = -1;
a+b;

计算机如何计算1-1的呢?

1-1 = 1+(-1)
如果用原码:
00000000000000000000000000000001
10000000000000000000000000000001
算出来是:
10000000000000000000000000000010 = -2
算错了!
=========================================================
如果用补码就可以计算:
10000000000000000000000000000001转化为补码:
11111111111111111111111111111110
11111111111111111111111111111111补码
之后在相加:
 00000000000000000000000000000001+
 11111111111111111111111111111111=
100000000000000000000000000000000=-0=0
这样就可以计算了。


另外,为什么“补码原码的相互转换,其运算过程也是相同的,不需要额外硬件电路”。是因为我们通常
想到补码转化成源码是先-1,再取反,但其实同样的,补码取反再+1也能得到源码,就跟源码取反+1
得到补码是一样的。

1.3大小端的介绍

不管大端还是小端,只要先存的先取出就行了。

1.3.1大端字节序存储

把一个数的高位字节序内容存储到低地址,相反存储在高地址。

1.3.2小端字节序存储

把一个数的低位字节序内容存储到低地址,相反存储在高地址。

如图所示

在这里插入图片描述

我们注意到在VSCODE中采用的是小端存储模式。
另外浮点型等都是有大小端存储的。但是像char类型这种一个Byte的是没有顺序可谈的。

1.3.3【百度面试】设计小程序判断当前机器所在的字节序:

在这里插入图片描述
我们可以看到大端小端第一个字节是不同的。要想办法只取到第一个字节,我们需要利用到强制类型转换成char*类型的指针变量,再解引用就只有一个字节了。

#include<stdio.h>

void check_link(int a)
{
	if(*(char*)&a == 1)
	{
		printf("小端\n");
	}
	else
	{
			printf("大端\n");
	}
}

int main()
{
	int a = 1; 
	scanf("%d",&a);
	check_link(a);
	return 0;
}

2.1浮点型的存储

float
double
long double

浮点数的范围定义在float.h中

以整型存储数据,在取数据时应该以整数的形式。以浮点数的方式存储数据,在取数据时应该以浮点数的形式取数据。看下面一个例子:

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;

	printf("n的值为%d\n",n);
	printf("pFloat的值为%f\n",*pFloat);

	*pFloat = 9.0;
	printf("n的值为%d\n",n);
	printf("pFloat的值为%f\n",*pFloat);
	return 0;
}

输出结果为:

n的值为9
pFloat的值为0.000000
n的值为1091567616
pFloat的值为9.000000
2.1.1浮点数的存储规则

根据IEEE 754,任意一个二进制浮点数V可以表示为下面的几个形式:

V = (-1)^S * M * 2^E
(-1)^S表示符号位,当S = 0时,V是正数,S=1时,V是负数
M表示有效数字,大于1小于2.
2^E表示指数位

e.g.

V = 5.0f --------> (-1)^0 * 1.01 *2^2

V = 9.5f = 1001.1 = (-1)^0 * 1.0011 * 2^3

但是像float存储一些数值会存在精度差异,比如:

V = 9.6f = 1001.100...100101001010001...

无法确切知道V的精确二进制表达。不管是float还是double都是这样。无非是double比float位数更高更精确而已。

对于float,有32位,需要如此划分:
在这里插入图片描述
对于double,有64位,需要这样划分:
在这里插入图片描述
IEEE 754规定:

M

M>=1,M<2,且M一定是1.xxxxx的形式。所以通常可以把1舍去,只保留小数部分xxxxx,
舍去1之后,就可以保留24位有效数字。

E

E则比较复杂。E是一个unsigned int。8位取值为:0~255;11位取值为0~2047。由于科学计数法,E是可以出现负数的,所以,E要加上一个中间值之后存储。8位的E+127,11位的E+102B。

在这里插入图片描述

E从内存中取出分为三种情况:
E不全为0或E不全为1(这里的E指的是内存中存的E)
E全为0
E全为1

计算真实值方法:

不全为0或1全0全1
存储值减去中间值(127或1023)得到真的E,而M直接取出,再把之前删掉的那个1给补上,再根据公式V=(-1)^S * M * 2^E 计算VE的真实值=-126(或-1022),有效数字M不补上之前删除的那一个1,直接等于0.xxxxxx的形式.再根据公式计算此时有效数字M全为0,E等于128或1024,再根据公式计算

e.g.
第一种:
在这里插入图片描述
好了,此时我们再来看这个例子就可以解释了:

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;

	printf("n的值为%d\n",n);//1
	printf("pFloat的值为%f\n",*pFloat);//2

	*pFloat = 9.0;
	printf("n的值为%d\n",n);//3
	printf("pFloat的值为%f\n",*pFloat);//4
	return 0;
}

output:

n的值为9
pFloat的值为0.000000
n的值为1091567616
pFloat的值为9.000000

解释:

int n = 9;

表示9存整型,其二进制序列为:

00000000000000000000000000001001

打印1
没有什么好说的。
打印2

int n = 9;
	float* pFloat = (float*)&n;
	printf("pFloat的值为%f\n",*pFloat);//2

在*pFloat的视角下,这个序列是一个浮点数序列,长这样:

0 00000000 00000000000000000001001
S     E               M

E为全0,E = -126,M = 0.00000000000000000001001
则n = (-1)^0 * 0.00000000000000000001001 * 2^(-126)
由于只会打印六位小数,所以输出为0.000000
打印3

int n = 9;
*pFloat = 9.0;
	printf("n的值为%d\n",n);//3

虽然int n申请的四个字节的空间为整型空间,但是用*pFloat指代这个空间时,认为这个空间为浮点型空间,依然是4个字节,然后把浮点数9.0存储到这个空间。

9.0------>1001.0----->(-1)^0 * 1.001 * 2^3
S = 0
M = 1.001
E = 3
序列写成:
0 10000010 001000000000000000000000

而此时,存储按照浮点存储的,但是打印3是按照n来取的,n认为这4个字节是整型,所以认为这个新的序列是整型补码,这个整型补码转成10进制输出为:

1091567616

打印4

int n = 9;
	float* pFloat = (float*)&n;
	*pFloat = 9.0;
	printf("pFloat的值为%f\n",*pFloat);//4

打印4同打印3一个存储过程,只是取出不一样。
打印4取是按照*pFloat取,认为存储的四字节空间为浮点型,所以取出的是浮点型,而存储的也是浮点型9.0,所以输出为

9.0
  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值