6.数据的储存

1. 数据类型介绍

char        //字符数据类型         char 占1byte
short       //短整型             	short 占 2byte
int         //整形                int 占 4byte      
long        //长整型               在32位机器 占 4byte 在64位机器占 8byte         
long long   //长长的整形            long long 占 8byte             
float       //单精度浮点数           float 占 4byte               
double      //双精度浮点数           double 占 8byte               

类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
  2. 如何看待内存空间的视角。(即 int a 存放的就是整型,float a 存放的就是浮点型)

1.1 类型的基本归类

整形家族:

char                	// 字符的本质是ASCII码值,所以划分到整型家族
 	unsigned char
 	signed char
short
 	unsigned short [int]
 	signed short [int]
int
	unsigned int
 	signed int
long
 	unsigned long [int]
 	signed long [int]
long long
 	unsigned long long [int]
 	signed long long [int]
  • short, int, long, long long 都默认为 signed.
  • char 到底是 signed char 还是 unsigned char 标准是未定义的,取决于编译器的实现

什么是signed,unsigned

  1. 生活中有些数据是没有负数的,例如:身高,体重,长度,年龄 (只可以表示正数,即使用 无符号的)
  2. 有些数据有正数和负数,例如:温度 (有正数和负数,即使用 有符号的)

例如:将 int a = 10 放到内存中

image-20220726155921122

而对于无符号数,最高位不是符号位,并且是有数值意义的

image-20220726160352542

浮点数家族:

image-20220726160528855

构造类型:

> 数组类型     例如: int arr[2] 的类型就是 int [2];   int arr[8] 的类型就是 int [8]
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union

指针类型:

int *pi;
char *pc;
float* pf;
void* pv;

空类型:

void 表示空类型(无类型)

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

2. 整形在内存中的存储

2.1 原码、反码、补码

数值有不同表示形式(例如:十进制的21),可以表示为如下
2进制          0001 0101
8进制          025
10进制         21
16进制         0x15


整数的2进制表示也有三种表示形式:
    三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”
1. 正的整数,原码、反码、补码相同
2. 负的整数,原码、反码、补码是需要计算的
原码:直接通过正负的形式写出的二进制序列就是原码
反码:原码的符号位不变,其他位按位取反得到的就是反码
补码:反码+1就是补码
整数内存中存放是补码的二进制序列

image-20220913102034524

image-20220913102010274

  • 内存中是用二进制储存的,只是在电脑上查看时,以16进制显示

  • 我们可以看到对于a和b分别存储的是补码。但是我们发现顺序有点不对劲

    这是又为什么?

对于整形来说:数据存放内存中其实存放的是补码。

为什么呢?
1.在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
2.同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

例如:1-1该怎么计算呢

我们可以理解为1+(-1), 相加它们的补码(符号位和数值域统一处理),得到的补码的和再转化为原码就是1-1的值。

image-20220913102100505

2.2 大小端介绍

什么大端小端:

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

例如:下图为小端存储image-20220913102134505

  • 只有两个字节以上才可以排序(是以字节为单位的);所以char类型,没有大小端的排序

百度2015年系统工程师笔试题:

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

image-20220913102420451

image-20220913102231492

由上图分析得如下代码:

//代码1
#include <stdio.h>
int check_sys()
{
   int i = 1;
   // 返回底地址的第一个字节的数据,如果为1,则是小端,如果不为1,则是大端
   return (*(char *)&i);
}

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


//代码2
int check_sys()
{
   // 联合体
   union
   {
      int i;
      char c;
   }un;
   
   un.i = 1;
   return un.c; 
}

2.3 练习

做练习之前,先了解下图所示的内容

image-20220726182738471

  • 纠正:-126,原码为 1111 1110;反码是 1000 0001; 补码是 1000 0010; -1同理

image-20220726182803550

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; 
}
// 打印结果为:a=-1,b=-1,c=255
-1的原码为 10000000 00000000 00000000 00000001
    反码为 11111111 11111111 11111111 11111110
    补码为 11111111 11111111 11111111 11111111
因此char类型只有8bit,所以截断后为 11111111
    (补码)整型提升后为 11111111 11111111 11111111 11111111
     反码              11111111 11111111 11111111 11111110
     原码              10000000 00000000 00000000 00000001   所以a的打印结果为-1
  • 分析如下

image-20220726184317022

下面程序输出什么?
2.
#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);   // %u:打印无符号整数
    printf("%d\n",a);
    return 0;
}
  • 分析如下

image-20220726191902201

3.
#include <stdio.h>
int main()
{
    char a = 128;           //signed char 的取值范围是-128~127
    printf("%u\n",a);   	//打印的值为 4294967168
    printf("%d\n",a);   	//打印的值为 -128
    return 0; 
}

// 128转化为2进制 1000 0000 将其存储到a里面(此时最高位是符号位,代表的是-128)
// 整型提升 11111111 11111111 11111111 1000 0000 补码
// 如果按照无符号打印,那么整型提升后,正数补码、反码、原码相同,转化为10进制就是4,294,967,168
// 如果按照有符号打印,那么整型提升后,最高位是符号位

补码为:11111111 11111111 11111111 1000 0000
反码为:11111111 11111111 11111111 0111 1111
原码为:10000000 00000000 00000000 1000 0000 这也就是-128
4.
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);  
	return 0;
}

分析 
-20的原码:10000000000000000000000000010100
-20的反码:11111111111111111111111111101011
-20的补码:11111111111111111111111111101100   
    
10的补码: 00000000000000000000000000001010    
   
20的补码:11111111111111111111111111101100
10的补码:00000000000000000000000000001010
补码(-20+10)的补码:11111111111111111111111111110110-20+10)的反码:  10000000000000000000000000001001-20+10)的原码:  10000000000000000000000000001010    最终结果为-10
	
5.
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}

	return 0;
}
  • 分析如下:(为了便于观察,我加入了sleep函数)

image-20220726194749257

  • 为什么会发生上图所示的情况呢

    当我们打印完 9~1,当i被初始化为-1时

image-20220726195350539

6.
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));//打印结果为255
	//strlen 是求字符串的长度,关注的是字符串中'\0'(数字0)之前出现多少字符

	return 0;
}

注:0, 字符0(即'0'), \0之间的关系

  • 在字符串中‘\0’代表空字符,‘\0’ASCII值为数字0. 所以可认为‘\0’ == 0.
  • 注意只有数字0才会被当做空字符’\0’哦,字符0(即’0’)还是不会被当做空字符的。
  • 比如char *str = “abc0”,char str1[] = “abc0”;这里字符串"abc0"中的0会被当做字符0(即’0’),strlen()不终止;
  • 再比如char str2[] = {‘a’, ‘b’, ‘c’, ‘0’},这个字符数组str2就相当于字符串str,strlen()函数求长度都是4;
  • 再比如char str3[] = {‘a’, ‘b’, ‘c’, ‘\0’},char str4[] = {‘a’, ‘b’, ‘c’, 0};这俩是求长度是一样的,原因就是上面的’\0’ 和 数字0均认为是空字符;

image-20220726203024197

7.
#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}
  • 当我们运行起来时,会发现是一个死循环,这是为什么呢?
  • 因为 unsigned char 的取值范围是0~255,所以 i <= 255 条件恒成立 ,所以会是死循环打印。
8.
int main()
{
	// strlen的返回值是一个无符号的整型,因此3-6=-3
    // -3的原码:10000000 00000000 00000000 00000011
    // -3的反码:11111111 11111111 11111111 11111100
    // -3的补码:11111111 11111111 11111111 11111101 也就是(按照无符号数进行打印)4,294,967,293
	if (strlen("abc") - strlen("abcdef")>0)
		printf(">\n");
	else
		printf("<\n");

	return 0;
}

运行结果如下:

image-20220726210104839

分析如下:

image-20220726204510842

因为strlen的返回类型为无符号整型,因此

image-20220726210308758

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

常见的浮点数:3.14159

浮点数家族包括: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;
}

运行之后的打印结果如下:

image-20220727225211672

打印的结果为什么是这些值呢?

3.2 浮点数存储规则

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:

  • (-1)^S * M * 2^E

  • (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。

  • M表示有效数字,大于等于1,小于2。

  • 2^E表示指数位。

举例来说明:

image-20220727225631940

image-20220727225834520

有特殊的情况,浮点数无法精确保存

image-20220727230034344

image-20220727230135262

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

image-20220727230218206

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位,它的取值范围为0255;如果E为11位,它的取值范围为02047。

  • 但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

3.3 浮点数拿出规则

然后,指数E从内存中取出还可以再分成三种情况:

E不全为0或不全为1

  • 这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。

比如:

  • 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),存放到内存的数值为-1+127=126(即【-1(真实值)+127(中间值),表示为01111110,M =1.0 去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:

    0  01111110  00000000000000000000000
    0为最高位s,是符号位(-1)^0
    01111110 是指数为126126-127=-1
    00000000000000000000000 表示M,加上正数部分的1,则为1.0
        // (-1)^0 * 1.0 * 2^(-1) = 0.5
    // 综上,表示的是0.5(对应10进制)
    

E全为0

  • 这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,

  • 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

E全为1

  • 这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

解释前面的题目:

7=126(即【-1(真实值)+127(中间值),表示为01111110,M =1.0 去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:

0  01111110  00000000000000000000000
0为最高位s,是符号位(-1)^0
01111110 是指数为126126-127=-1
00000000000000000000000 表示M,加上正数部分的1,则为1.0
    // (-1)^0 * 1.0 * 2^(-1) = 0.5
// 综上,表示的是0.5(对应10进制)

E全为0

  • 这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,

  • 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

E全为1

  • 这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

解释前面的题目:

image-20220727234114188

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值