数据的存储(C语言)

数据类型:

要了解数据是如何存储的,我们就得先知道C语言中的数据类型

基本数据类型

基本数据类型,也就是C语言内置类型:

char                 -> 字符型

short                -> 短整型

int                     -> 整型

long                  -> 长整型

long long          -> 更长的整型

float                   -> 单精度浮点型

double               -> 双精度浮点型

数据类型的大小

C语言中可用sizeof操作符来计算数据类型在内存中所占空间的大小,单位是字节

#include 
int main()
{
	printf("%zd\n", sizeof(char)); //char大小
	printf("%zd\n", sizeof(short));//short的大小
	printf("%zd\n", sizeof(int));//int的大小
	printf("%zd\n", sizeof(long));//long的大小
	printf("%zd\n", sizeof(long long));//long long 的大小
	printf("%zd\n", sizeof(float));//float的大小
	printf("%zd\n", sizeof(double));//double的大小

	return 0;
}

数据类型的意义

1、类型决定了要开辟空间的大小

2、类型的大小决定了空间的使用范围

3、类型决定了如何看待内存空间的视角

类型的基本归类

整型家族:

char    

        unsignde char  

        signed char

short

        unsigned short

        signed short

int 

        unsigned int

        signed int

long

        unsigned long

        signed long

long long

        unsigned long long

        signed long long

注:char类型也是整型家族的一员

       char类型在内存中存储的时候,存的是其对应的ASICC码值

        unsigned 表示 无符号数

        signed 表示 有符号数

浮点数家族:

float   单精度浮点型

double  双精度浮点型

构造类型:

构造类型也叫自定义类型

是我们自己所创建的类型

数组类型

结构体类型 struct

联合类型  union
枚举类型  enum

指针类型:

指针类型分类:

整型指针:int*

字符指针:char*

浮点型指针:float*

空类型指针:void*

........

注:空类型的指针可以接收任意类型的指针数据

空类型

void 空类型(无类型)

用途:

        函数的返回类型

        函数的参数

        指针类型

        ......

整型在内存中的存储

整型家族在内存中是以二进制的补码进行存储

那么要了解它的存储,

我们首先要了解清楚,原码、反码、补码的概念

原码、反码、补码

为什么存的是补码?

因为使用补码,可以将符号位和数值域统一处理

同时,加法和减法也可以统一处理(CPU只有加法器)

此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

原码 转 补码

原码 —> 反码 —> 补码 (原码转补码)

原码:将整数直接转化成二进制,这个二进制数就是它的原码

反码:将原码的符号位不变,其它位按位取反得到反码

补码:给反码 +1 得到补码

注:转化二进制数的最高位是符号位,其中 0 表示正数,1 表示负数

eg:

#include 

int main()
{
	 int a = 10;

	//定义一个整型变量,在内存中开辟4字节的空间
	//变量名为a,这个空间里面存储10
	//那么数字10是如何存储的呢?
	// 整型家族在存储的时候存的是二进制的补码

	// 先将数字化成二进制 得到原码
	// 00000000 00000000 00000000 00001010   --》原码

	// 再将原码按符号位不变,其它位按位取反得到反码(最高位是符号位,0表示正数,1表示负数)
	// 01111111 11111111 11111111 11110101   --》反码

	//再让反码+1得到补码
	// 01111111 11111111 11111111 11110110   --》补码

	 //而在内存中存的就是二进制的补码

    printf("%d\n",a);

	return 0;
}

补码转原码

方法1: 倒着推回去

补码  —> 反码 —> 原码

先让补码 -1 得到反码,

再让反码符号位不变其它位按位取反得到原码

方法2: 重新按照原码转补码的步骤走一遍

先让补码符号位不变其它位按位取反 得到一个二进制序列

再让这个二进制序列 +1 得到原码

eg:

#include 

int main()
{
	int a = 10;
	// 定义一个整型变量a

	// a的原码:00000000 00000000 00000000 00001010
	// a的反码:01111111 11111111 11111111 11110101
	// a的补码:01111111 11111111 11111111 11110110

	printf("%d\n", a);
	//打印a
	
	//因为在内存中存的是补码,而我们取出来的时候是要用原码,
	//所以 要将补码 转回 成原码
	
	// 方法1:倒着推回去
	// a的补码:01111111 11111111 11111111 11110110
	// 让补码 -1 得到反码
	// a的反码:01111111 11111111 11111111 11110101
	// 让反码符号位不变,其它位按位取反得到原码
	// a的原码:00000000 00000000 00000000 00001010
	// 最后将原码以 %d(十进制数)的形式打印出来


	//方法2:重新按照原码转补码走一遍
	//   a的补码:01111111 11111111 11111111 11110110
	// 让补码符号位不变,其它位按位取反 得到一个二进制序列
	//二进制序列:00000000 00000000 00000000 00001001
	//再让这个二进制序列 +1 得到原码
	//   a的原码:00000000 00000000 00000000 00001010

	return 0;
}

大小端字节序存储

每个机器的存储模式不同,

二进制的补码在存储时 有的机器是从前往后存储,而有的机器是从后王前存储

由于存储顺序不同,就产生了大小端的概念

当前机器是从后往前存储的

数据的存储(C语言)_第1张图片

大端存储:数据的低权值位保存在内存的高地址处,数据的高权值位保存在内存的低地址处

小端存储:数据的低权值位保存在内存的低地址处,数据的高权值位保存在内存的高地址处

(小端口诀:小小小(低权值位,低地址,小端))

例题:判断当前机器是大端字节序存储,还是小端字节序存储

#include 
int main()
{
	int a = 1;
	//原码:00000000 00000000 00000000 00000001
	//反码:01111111 11111111 11111111 11111110
	//补码:01111111 11111111 11111111 11111111

	//显示的时候是用十六进制显示
	//原码:00000000 00000000 00000000 00000001
//  十六进制显示:00 00 00 01

	// 小端存储:10 00 00 00
	// 大端存储:00 00 00 01

	//将a的第一个字节的地址给b
	// *b取到第一个字节的内容,若为1 是小端,若为0 是大端
	char* b = &a;
	if (*b == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

例题:

//第一题
#include 
int main()
{
	char a = -1;
	//原码:10000001
	//反码:11111110
	//补码:11111111
	signed char b = -1;
//有符号的char取出来的时候还是将补码转为原码
// 取出来的原码:10000001
	unsigned char c = -1;
//无符号char 取出来的时候,认为补码就是原码
//取出来的原码:11111111
	printf("a=%d  b=%d  c=%d\n", a, b, c);// -1  -1  255
	return 0;
}


//第二题
#include 
int main()
{
	char a = -128;
// 原码:10000000
// 反码:01111111
// 补码:10000000
	printf("%u\n", a);//非常大的一个数字
//在取的时候是用无符号整数
//认为补码就是原码
// 10000000   00000000 00000000 00000000 
	return 0;
}


//第三题
#include 
int main()
{
	char a = 128;
	// 正数的原反补相同
	// 补码: 10000000
	printf("%u\n", a);//2^32
	//在取得时候 进行整型提升
	//10000000 00000000 00000000 00000000
	return 0;
}


//第四题
#include 
int main()
{
	int i = -20;
	//原码:10000000 00000000 00000000 00010100
	//反码:11111111 11111111 11111111 11101011
	//补码:11111111 11111111 11111111 11101100
	unsigned int j = 10;
	//原反补相同
	//补码:00000000 00000000 00000000 00001010
	//补码:11111111 11111111 11111111 11101100
	//      11111111 11111111 11111111 11110110

	//让他们的补码相加,在取的时候是%d 有符号的,所以结果是-10

	//      10000000 00000000 00000000 00001001
	//      10000000 00000000 00000000 00001010   -10
	printf("%d\n", i + j);
	return 0;
}


//第五题
#include 
int main()
{
	unsigned int i = 0;
	for (i = 9; i >= 0; i--)
	{
		printf("%u ", i);
	}
	// 9 8 7 6 5 4 3 2 1 0 
	//当i--变成-1的时候-1的二进制序列为:10000001 
	
	//但因为i是无符号整数,所以在取的时候,会将符号位当做是数位计算进去
	//在打印的时候会发生整型提升,所以会发生死循环!
	// 10000001 0000000 00000000 00000000

	return 0;
}


//第六题
#include 
int main()
{
	char a[1000];
	int i = 0;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	// -1   ..... -127  128 ...... 0
	// 127+128=255
	//strlen函数遇到’'\0'才停止 而‘\0'的ASICC值是 0 所以遇到0就结束
	printf("%d\n", strlen(a));

	return 0;
}


//第七题
#include 
unsigned char i = 0;
int main()
{
	// 00000000
	// 00000001
	// .......
	// 11111111
    // ......
    // 10000000
	//死循环
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}

浮点型在内存中存储

  浮点家族:float、double、long double 类型

点数存储的一个例子:

#include 

int main()
{
	int n = 9;
	float* p = (float*)&n;
	
	printf("%d\n", n);//9
	printf("%f\n", *p);//0.000000

	*p = 9.0;

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

	return 0;
}

数据的存储(C语言)_第2张图片

看完代码结果跟我们想的完全不一样,为什么不一样呢,是因为浮点数的存储,

以下让我们仔细了解一下浮点数到底是如何存储的

浮点数的存储规则

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

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

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

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

2^E表示指数位。

举个例子:

浮点数的 5.0,转成二进制:101.0,相当于:1.01*2^2

按照IEEE规定则可以看成:(-1)^0  *  1.01  * 2^2

按照公式:S=0,M=1.01,E=2

存储时如何放入内存的呢?

IEEE 754 规定:

1、32位的浮点数的存储:

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

数据的存储(C语言)_第3张图片

2、 64位浮点数的存储

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

数据的存储(C语言)_第4张图片

 

IEEE 754,对有效数字M,和指数E 有些特别的规定:

1、对于有效数字M:

因为 1≤M<2  所以M总是 1.xxxx...  的数,

因此1 可以被舍弃,我们在存储的时候只存储M的xxxx...部分,

等到读取的时候在xxx...前面把1加上去

好处:舍弃1这样可以存储24位有效数字

2、对于指数E:

首先E为一个无符号整数:

因为E是科学技术法的指数,所以会出现负数的情况,但是E是一个无符号整数,

所以IEEE 754 规定:E在存入内存时,其真实值必须加上一个中间数

对于8位的E 中间数是 127,对于11位的E 中间数是 1023

eg: 2^10  中 指数E为10,将它保存成32位浮点数,E=10+127=137

      存入内存中:10001001

然而指数E从内存中取出又分为三种情况:

取E的三种情况:

1、E不全为0或不全为1:

        此时取的时候 将E减去中间值(127或1023)得到真实值,

        再将有效数字M前面加上第一位的1

        eg:浮点数0.5 (1/2) 二进制为:0.1 

        转化为:(-1)*0 * 1.0 * 2^-1

         在存储时 给8位E加上中间数127,舍弃M第一位的1,不够位的补0

        S=0,E=-1+127=126,M=1.0

        则存储起来的二进制为:0 01111110 00000000000000000000000

2、E为全0:

        此时取的时候,指数E=1-127或者E=1-1023,即为真实值

        有效数字M不再加上第一位的1,而是还原为 0.xxxx... 的小数

3、E为全1:

        如果有效数字M全为0 ,表示无穷大 (±无穷大)(正负号取决于S)

注:由于浮点数跟整数的存取方式不同,用不同的方式取的时候结果也大不相同

此时我们再回过头来解释刚刚的例题

#include 

int main()
{
	//由于浮点数跟整数的存取方式不同,用不同的方式取的时候结果也大不相同

	int n = 9;//n是整数,存在原反补问题
	//原码:00000000 00000000 00000000 00001001
	//正数的原码、反码、补码相同
	//补码:00000000 00000000 00000000 00001001
	// 内存中存的就是整数的补码

	float* p = (float*)&n;
	//将整型的内存空间强制转化成浮点型

	printf("n = %d\n", n);//9
	// 用%d读取的时候,打印的是有符号十进制整数
	// 由于n是正数,原码、反码、补码都相同
	// 所以用%d打印 结果就是9

	printf("%*p = %f\n", *p);//0.000000
	//用%f读取的时候,打印的是浮点数,
	// 而这片空间里面存储的是:00000000 00000000 00000000 00001001 
	// 用浮点数的存储规则取出来
	// 0 00000000 00000000000000000001001
	// S    E         M
	// 在取的时候,S为0表示正数,E全为0的情况 有效数字M不加第一位的1
	//而在打印的时候%f只能精确到有效数字后六位
	// 所以结果为:0.000000

	*p = 9.0;
	//在p指针指向的空间n里面放入浮点数字9.0
	//9.0的二进制数:1001.0   --  1.001*2^3
	//由浮点数的存储规则可知:
	//(-1)^0 * 1.001 * 2^3
	//S=0,E=3,M=1.001
	//在存储的时候让E加上中间值,让M舍弃第一位的1
	//S=0,E=3+127=130,M=001
	//存入到内存的二进制数,不够位数的补0
	//0 10000010 00100000000000000000000

	printf("n = %d\n", n);//1091567616
	//用%d读取的时候是以整数的形式读取内存中存入的二进制数
	//0 10000010 00100000000000000000000
	//将内存中的二进制数读取为整数结果是:1091567616
	
	printf("n = %f\n", *p);//9.000000
	/*用%f读取的时候读取的是浮点数
	而内存中本身就存入的是浮点数
	打印的结果就是它本身:9.000000*/

	return 0;
}

你可能感兴趣的:(c语言,开发语言)