C语言程序设计 第 10 讲 数据的存储

数据类型介绍

C语言基本内置类型

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

类型的意义:

  1. 类型决定了开辟内存空间的大小
  2. 决定了使用范围
// 1. 整形
char //字符类型底层存储的是字符的ASCII码值,所以归为整形
	unsigned char		//无符号
	signed char			//有符号
short
	unsigned short
	signed short
int
	unsigned int
	signed int
long
	unsigned long
	signed long
// 2. 浮点型
float
double

以上仅仅介绍了C语言当中的一些内置类型,C语言中还有许多派生类型,由用户自己定义,如数组类型,结构体类型,指针类型等。

数据的存储

整数

数据在内存中以二进制的形式存储,对于整数来说,整数的二进制有三种表示形式:原码、反码、补码
对于正整数,原码反码补码相同,负整数的三者需要进行换算
换算过程:

  1. 原码:负整数的原码最高位为符号位,1为负,0为正
  2. 反码:在源码的基础上,符号位不变,其余按位取反
  3. 补码:反码 +1 得到补码
//example
int a = -10;
// 原码:100000000000000000000000000001010
// 反码:111111111111111111111111111110101
// 补码:111111111111111111111111111110110

整数在内存中存储的是其补码
使用补码可以将符号位与数值域统一处理;且CPU中只有加法器,使用补码时加减法可以统一处理,运算过程相同,节省硬件电路。
在这里插入图片描述

大端小端

  • 大端存储:指数据的低位在内存的高地址中。数据的高位在内存的低地址中
  • 小端存储:指数据的低位在内存的低地址中。数据的高位在内存的高地址中
    在计算机存储中,以字节为单位进行存储,一个地址对应一个字节,一个字节是8bit,C语言中的数据类型存储大于1个字节时,存在字节分配问题,就出现了大端存储和小端存储。
    常用的X86结构为小端模式,KEIL C51为大端模式,很多ARM和DSP都是小端模式
// 一道笔试题
// 设计一个程序判断当前机器的字节序
// 程序 1
int check_sys()
{
	int i = 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;
}
// 练习1
int main()
{
	char a = 1;
	signed char b = -1;
	unsigned char c = -1;
	printf("a = %d, b = %d, c = %d", a, b, c);
}
// outcome:a = -1, b = -1, c = 255
// -1 的存储
// 原码:100000000000000000000000000000001
// 反码:111111111111111111111111111111110
// 补码:111111111111111111111111111111111
// a 的类型是 char,存入 11111111,最高位理解为符号位
// b 的类型是 signed char,存入 11111111,最高位理解为符号位
// c 的类型是 unsigned char,存入 11111111,最高位理解为有效数字
// 打印整数 %d 会发生整型提升
// a 补完是 111111111111111111111111111111111(补码)
// b 补完是 111111111111111111111111111111111(补码)
// c 补完是 000000000000000000000000011111111
// 练习2
int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}
// outcome:4294967168
// -128 的存储
// 原码:100000000000000000000000010000000
// 反码:111111111111111111111111101111111
// 补码:111111111111111111111111110000000
// a 的类型是char,存入10000000,最高位理解为符号位
// 无符号整形 %u 打印,发生整型提升
// a 补完整是 111111111111111111111111110000000,最高位理解为有效数字
// 练习3
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}
// outcome:-10
// i 的存储
// 原码:100000000000000000000000000010100
// 反码:111111111111111111111111111101011
// 补码:111111111111111111111111111101100
// j 的存储
// 原/反/补码:000000000000000000000000000001010
// 加法运算按照补码进行
// 111111111111111111111111111101100	+
// 000000000000000000000000000001010	=	
// 111111111111111111111111111110110(补码)-->
// 111111111111111111111111111110101(反码)-->
// 100000000000000000000000000001010(原码)--> -10
// 练习4
int main()
{
	unsigned int i;
	for (i = 9;i > 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}
// 死循环
// 练习5
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}
// outcome:255
// a = -1,-2,-3,...,-128,127,126,...,0,-1,-2,...
// strlen(a) 求 a 字符串长度,基本原则是遍历寻找 \0 作为字符串结束标志, \0 的ASCII码为 0

浮点数的存储规则

任意一个浮点数V可以表示成以下形式
(-1)^S*M*2^E

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

//example
-5.5(十进制) = -101.1(二进制) = -1.011 x 2^2
S = 1, M = 1.011, E = 2
  • 单精度浮点数(32位)
    在这里插入图片描述

  • 双精度浮点数(64位)
    在这里插入图片描述
    IEEE 745 对于 M 和 E 存储的规定:

  • 对于 M 存储
    由于 1 ≤ M ≤ 2,M = 1.xxxxxxxxx,默认 M 的第一个数字都是 1,故只存储小数点后面的数字,在读取得时候再加上 1,这样做可以保存小数点后 23 位有效数字。

  • 对于 E 的存储

  1. E 是一个无符号整数,如果 E 为 8 位,其取值范围为 0 ~ 255,若 E 为 11 位,其取值范围为 0 ~ 2047。
  2. 但实际上 E 的取值可以为负数。所以规定存入的 E 必须加上一个中间值。对于 8 位的 E,需要加上127;对于 11 位的 E,需要加上 1023。
  3. 取出 E 时分为 3 种情况
    1)E不全为1或不全为0
    E 的计算值减去加上的中间值(127 或 1023),得到其真实值,同时 M的有效数字前加上第一位 1。
    2)E全为0
    此时 E 等于 1-127 或 1-1023即为真实值,此时 M 不再补上第一位 1,直接还原为0.xxxxxxxxxxxx,这样可以表示±0.xxxxxxx,以及接近于0的很小的数字
    3)E全为1
    此时若 M 全为 0,则表示±无穷大
int main()
{
	int n = 9;

	float* pFloat = (float*)&n;
	printf("n = %d\n", n);
	// outcome:n = 9
	// 存入内存的四个字节(16进制表示):09 00 00 00
	printf("*pFloat = %f\n", *pFloat);
	// outcome:*pFloat = 0.000000
	// 9 -> 0000 0000 0000 0000 0000 0000 0000 1001
	// 以浮点数的角度读取
	// E 全为 0,表示一个很小的数字
	// V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)

	*pFloat = 9.0;
	// 以浮点数的形式存入9.0
	// 9.0 -> 1001.0 ->(-1)^0x1.001x2^3 -> s=0, M=1.001, E=3+127=130
	// 存入内存 0 10000010 001 0000 0000 0000 0000 0000
	printf("n = %d\n", n);
	// outcome:n = 1091567616
	// 以整数的角度都内存,为1091567616
	printf("*pFloat = %f\n", *pFloat);
	// outcome:*pFloat = 9.000000
	return 0;
}