c语言进阶-第1节-数据的存储

目录

1. 数据类型介绍

1.1.类型的基本归类

2.整形在内存中的存储

2.1.原码、反码、补码

2.1.1.原码、反码、补码介绍

2.1.2.内存中为什么存的是补码

2.2.大小端介绍

2.2.1.大端和小端

2.2.2.为什么有大端和小端

3.3.3.设计一个小程序来判断当前机器的字节序

2.3.练习(※)

3. 浮点型在内存中的存储 

 3.1.一个例子(※)

3.2.浮点数存储规则


1. 数据类型介绍

char              // 字符数据类型
short            // 短整型
int                // 整形
long            // 长整型
long long   // 更长的整形
float           // 单精度浮点数
double       // 双精度浮点数

c99中引入了布尔类型: _Bool,其作用是存储真假值,如下图所示(图中true就是1,false就是0)(其实基本上用不到布尔类型,因为int类型完全能够解决,并且布尔类型本质上就是对int类型进行了重命名)

需要引用stdbool.h头文件

1.1.类型的基本归类

1.整形家族:

char
        unsigned char     char到底是signed char还是unsigned char是取决于编译器实现的
        signed char        vs编译器中char与signed char等价
short
        unsigned short [int]    无符号短整型
        signed short [int]        有符号短整型   short与signed short等价
int
        unsigned int    无符号整型
        signed int        有符号整型   int与signed int等价
long
        unsigned long [int]    无符号长整型
        signed long [int]        有符号长整型   long与signed long等价
longlong
注:
1.上面[ ]里的内容可以省略
2.%d打印有符号整型,%u打印无符号整型
2.浮点数家族
float
double

3.构造类型 /自定义类型

> 数组类型 数组名去掉,其余的就是数组的类型( int a[10]类型是int [10] )
> 结构体类型 struct
> 枚举类型     enum
> 联合类型     union

4.指针类型

int*  pi ;
char*  pc ;
float* pf ;
void* pv ;
5.空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型

2.整形在内存中的存储

2.1.原码、反码、补码

2.1.1.原码、反码、补码介绍

整数的二进制表示有三种表示形式:原码、反码、补码
内存中存储的是二进制的补码
整数分为正整数和负整数
正整数:原码、反码、补码相同
15的原码: 00000000 00000000 00000000 00001111
15的反码: 00000000 00000000 00000000 00001111
15的补码: 00000000 00000000 00000000 00001111
负整数:原码、反码、补码需要计算,如下
原码:按照一个数的正负直接写出来的二进制就是原码
-15的原码:10000000 00000000 00000000 00001111
反码:符号位不变,其他位按位取反就是反码
-15的反码:11111111 11111111 11111111 11110000
补码:反码的二进制序列加一就是补码
-15的补码:11111111 11111111 11111111 11110001
注:
1.32位二进制数中,第一位是符号位,符号位是0表示整数,符号位是1,表示负数
2.反码不是对原码按位取反,因为按位取反是连符号位也取反了

2.1.2.内存中为什么存的是补码

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
计算机计算1-1,因为计算机中只有加法器,所以是1+(-1),下面我们尝试用原码和补码计算
1.使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理
原码计算:
1的原码:00000000 00000000 00000000 00000001
-1的原码:10000000 00000000 00000000 00000001
1+(-1)的原码:10000000 00000000 00000000 00000010   数值为-2(结果错误)
补码计算:
1的补码:00000000 00000000 00000000 00000001
-1的原码:10000000 00000000 00000000 00000001
-1的反码:11111111 11111111 11111111 11111110
-1的补码:11111111 11111111 11111111 11111111
1+(-1)的补码:1 00000000 00000000 00000000 00000000  最前面进上去的1在实际中就丢了因此应该为00000000 00000000 00000000 00000000 ,而该值为0(结果正确)
2.补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
-1的原码:10000000 00000000 00000000 00000001
-1的反码:11111111 11111111 11111111 11111110
-1的补码:11111111 11111111 11111111 11111111
此时想将-1的补码转换成-1的原码有两种方法:1.逆着计算,将补码-1变成反码,在对反码符号位不变其他位取反。2.继续对补码求补码,即对补码符号位不变其他位取反,然后所得值+1,如下:
-1的补码:11111111 11111111 11111111 11111111
-1的补码的反码:10000000 00000000 00000000 00000000
-1的补码的补码:10000000 00000000 00000000 00000001    可以看出此值就是-1的原码

2.2.大小端介绍

10的补码:00000000 00000000 00000000 00001010
10的补码转换为16进制:0x 00 00 00 0a
-10的原码:10000000 00000000 00000000 00001010
-10的反码:11111111 11111111 11111111 11110101
-10的补码:11111111 11111111 11111111 11110110
-10的补码转换为16进制:0x ff ff ff f6

总结:我们发现内存中的数字总是倒着存的

2.2.1.大端和小端

int a = 0x11223344;
大端字节序存储:当一个数据的低字节数据存放在高地址处,高字节序的内容放在了低地址处,这种存储方式就是大端字节序存储    内存: 11 22 33 44
小端字节序存储:当一个数据的低字节数据存放在低地址处,高字节序的内容放在了高地址处,这种存储方式就是小端字节序存储    内存:44 33 22 11

2.2.2.为什么有大端和小端

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在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处理器还可以由硬件来选择是大端模式还是小端模式。

2.2.3.设计一个小程序来判断当前机器的字节序

代码1:

代码2:

代码3:

2.3.练习

1.练习一
计算下面代码输出的结果
#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.  char a=-1 和  signed char b=-1 和 unsigned char c=-1

-1的原码:10000000000000000000000000000001

-1的反码:11111111111111111111111111111110
-1的补码:11111111111111111111111111111111
a的补码(char类型):11111111
b的补码( signed char类型):11111111
c的补码(  unsigned char类型):11111111
2.%d打印(打印的时候认为补码是有符号数),发生整型提升(vs环境中char和signed char相同)
a是char类型,是有符号的,提升后补码为11111111111111111111111111111111
a以%d打印,补码求原码:10000000000000000000000000000001   数值为-1
b是 signed char类型,是有符号的,提升后补码为11111111111111111111111111111111
b以%d打印,补码求原码:10000000000000000000000000000001    数值为-1
c是  unsigned char类型,是无符号的,提升后补码为00000000000000000000000011111111
b以%d打印,补码求原码:00000000000000000000000011111111     数值为255
2.练习二
计算下面代码输出的结果
#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0; 
}

答案

注:

1.char a = -128

-128的原码:10000000000000000000000010000000

-128的反码:111111111111111111111111101111111
-128的补码:111111111111111111111111110000000
a的补码(char类型):10000000
2.%u打印(打印的时候认为补码是无符号数),发生整型提升。
a是char类型,是有符号的,提升后补码为11111111111111111111111110000000
a以%u打印,补码与原码相同:11111111111111111111111110000000   数值为4294967168
3.练习三
计算下面代码输出的结果
#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0; 
}

答案:

注:

1.char a = 128

128的原码:00000000000000000000000010000000

128的补码:00000000000000000000000010000000

a的补码(char类型):10000000(与练习二相同)
2.%u打印(打印的时候认为补码是无符号数),发生整型提升(与练习二相同)
a是char类型,是有符号的,提升后补码为11111111111111111111111110000000
a以%u打印,补码与原码相同:11111111111111111111111110000000   数值为4294967168
4.练习四
计算下面代码输出的结果
#include <stdio.h>
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}

答案:

注:

1.int i = -20

-20的原码:10000000000000000000000000010100

-20的反码:11111111111111111111111111101011
-20的补码:11111111111111111111111111101100
2.    unsigned int j = 10
10的补码:00000000000000000000000000001010
3. i + j(补码相加),再以%d打印,因为j是unsigned int类型,计算时,将a的int类型也转换成了unsigned int类型进行计算(其实计算机在两个数值相加(相减等)的时候是不看类型直接相加)
i + j的补码:11111111111111111111111111110110
 
i + j的反码:11111111111111111111111111110101
i + j的原码:10000000000000000000000000001010    数值为-10
5.练习五
计算下面代码输出的结果
#include <stdio.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--) {
		printf("%u\n", i);
	}
	return 0;
}

答案:死循环

注:

1.当i为0的时候,i--为-1

-1的补码:11111111111111111111111111111111

因为i是unsigned int类型
所以-1的补码被认为是一个32二进制位全一的正数,该数值为4294967295
然后每次再减一进行计算,形成死循环
6.练习六
计算下面代码输出的结果
#include <stdio.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

答案:

 注:

1.一个char类型的变量中到底能放什么数值

char类型是一个字节八个比特位,放的数据(都是补码)有下面的可能

对于无符号char类型来说,补码就是原码,数据范围如下所示

对于有符号char类型来说,补码转换为原码,数据范围如下所示(规定其中10000000系统默认解析为-128)

 总结:

(1)上面的数值是循环的,也就是最后一个数加一变为第一个数,如下图所示

(2)与char同理,推演出其他类型数值范围

对于有符号char,其取值范围为 -128~127

对于无符号char,其取值范围为 0~255

对于有符号short,其取值范围为 -32768~32767 (-32768为1000000000000000直接解析得到)

对于无符号short,其取值范围为 0~65535
整型家族各类型表示的范围:limits.h中定义,如下图所示(选中头文件转到文档)

2.因此char a[1000]里面存的数据应该是

-1-2-3-4-5...-128127126...3210-1-2...
01234...127128129...252253254255256257...

3.求字符串的时候到'\0'截至,统计'\0'之前数值的个数,其实'\0'的ASCII码值为0,所以到0截止,由上表知0的前面有255个数。

7.练习七

计算下面代码输出的结果

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

答案:死循环

注:无符号char,其取值范围为 0~255,到255时下一个值为0,永远无法达到循环截止条件,因此死循环


3. 浮点型在内存中的存储 

常见的浮点数:
3.14159
1E10(意思是1.0\times10^{10}
浮点数家族包括: float、double、long double 类型
浮点数表示的范围:float.h中定义,如下图所示

 3.1.一个例子(※)

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

运行结果:

注:

1.%f和%lf都会默认小数点后有6位

2.我们发现用浮点型指针进行存储n的地址,解引用出来的值不是9或9.0,我们可以推断出浮点型和整型在内存中的存储方式是有区别的。

3.上面代码中*pFloat是站在浮点数的视角进行解引用的

当n=9时

n的原码:00000000000000000000000000001001

n的补码:00000000000000000000000000001001

00000000000000000000000000001001
SEM

E为全零,取出来的值是一个非常接近0的数字,因此打印出来是0.000000

当n=9.0时

十进制9.0转换为二进制为1001.0转换为科学计数为:-1^{0}*1.001*2^{3},S=1,M=1.001,E=3

03+127=130的二进制:10000010001的二进制:00100000000000000000000
SEM

 因此此时的n为:0100 0001  0001 0000  0000 0000  0000 0000

n的十六进制为       4        1        1       0        0      0       0        0    即0x41100000

 由上图知,浮点数存储也符合小段存储

将内存中n的值01000001000100000000000000000000用%d取出来,此时原反补相同,取出来的值为1091567616

3.2.浮点数存储规则

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

例如:十进制的5.5转换成二进制为101.1,101.1可以写成-1^{0}*1.011*2^{2}(S=0,M=1.011,E=2)

1.对于float(32 位的浮点数)

 最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M

2.对于double(64位的浮点数)

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

一、存储方式:
M的存储方式:
前面说过, 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 是可以出现负数的
比如:
十进制0.5转换成二进制为0.1,0.1转换成科学计数为: -1^{0}* 2^{-1}

所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023

比如:
十进制0.5的E的真实值为-1,而存入E中时要存入的是真实值+中间数(float:-1+127=126 double:-1+1023=1022)
二、取出方式( 从内存中取出可以分成三种情况)
1.E不全为0或不全为1
浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1
2.E全为0(E的真实值为-127 / -1023为一个非常小的数字,十分接近于0)
浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
3.E全为1 E的真实值为128 / 1024为一个非常大的数字
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

随风张幔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值