数据的存储 - 大小端、浮点数的存储规则


一、数据的存储 – 数据类型

1、基本的内置类型:

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

C语言库函数是编译器的厂商提供的,如VS,gcc
C语言标准规定了一些库函数:函数名,参数类型,返回值类型,函数功能
size_t strlen(const char* str);


类型的意义:
  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
  2. 如何看待内存空间的视角。

2、类型的基本归类

整形家族:

char:
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

char的符号

char a; 有无符号 取决于编译器
short,int,long --都是有符号的,加上unsigned才是无符号

unsigned signed区别
//char -- 1byte-- 8bit
int main()
{
	unsigned char c1 = 255; //无符号 最高位是有效位 -1是全1
	printf("%d\n", c1); //255
	//11111111 - 内存中放二进制的补码

	char c2 = 255; //char --signed char 最高位是符号位
	//11111111
	//11111110
	//10000001
	printf("%d\n", c2); //-1

	return 0;
}

3、范围

与符号的char – -128~127
unsigned char – 0~255

浮点数家族:

float
double

4、构造类型(自定义类型)

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

数组类型
int main()
{
	int a = 10;
	int arr[10] = { 0 }; //arr-数组名
	//去了数组名就是类型 - int [10]

	printf("%d\n", sizeof(a)); //4
	printf("%d\n", sizeof(int)); //4
	
	printf("%d\n", sizeof(arr)); //40
	printf("%d\n", sizeof(int [10])); //40

	int arr2[5]; //数组类型:int [5]
	//数组元素个数不一样时,数组类型也会改变
	return 0;
}

5、指针类型

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

空类型:

void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。

void test(void) //函数调用不要传参
{
	printf("hehe\n");
}

int main()
{
	test();
	test(100);

	return 0;
}

二、整形在内存中的存储

原码、反码、补码

1、内存存放补码

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

原反补 转换 可以用符号位不变,其他位按位取反,+1相互转换


2、什么是大端小端

int a = 0x11223344;
以字节存放
44 33 22 11 小端字节序存储
低地址 ----高地址
11 22 33 44 大端字节序存储

如何存储

小端字节序存储
把一个数字的低位字节的内容,存放在内存的低地址处
把高位字节的内容存放在高地址处
大端字节序存储
把一个数字的低位字节的内容,存放在内存的高地址处
把高位字节的内容存放在低地址处

为什么有大端和小端

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一
个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具
体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字
节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22
为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小
端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小
端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。*/


三、练习


1、解引用

unsigned long pulArray[] = { 6,7,8,9,10 };
unsigned long* pulPtr;
pulPtr = pulArray;
*(pulPtr + 3) += 3;
printf("%d, %d\n", *pulPtr, *(pulPtr + 3));

把pulArray首元素的地址给pulPtr
pulPtr + 3解引用 指向第四个元素9 给9加三
6 12


2、结构体

struct S
{
	int a;
	int b;
};
int main()
{
	struct S a, * p = &a;
	a.a = 99;
	printf("%d\n", __________);
	return 0;
}

struct *p = & a;
a.a =99;
结构体类型变量需要访问其成员时,用.运算符,如果是指向结构体变量的指针访问时,
需要用->,或者先对指针解引用,取到指向的结构体类型的变量,再通过.访问,但是要注意优先级
*p.a 错误 因为.的优先级更高


3、字符串逆序

将一个字符串str的内容颠倒过来,并输出。str的长度不超过100个字符。
输入一个字符串,可以有空格

下标
#include <stdio.h>

void reverse(char* str)
{
	unsigned int len = strlen(str);
	int left = 0;
	int right = len - 1;

	while (left < right)
	{
		int tmp = str[left];
		str[left] = str[right];
		str[right] = tmp;
		left++;
		right--;
	}
}

int main()
{
	char arr[100] = { 0 };

	gets(arr);

	reverse(arr);

	printf("%s\n", arr);

	return 0;
}
指针
void reverse(char* str)
{
	unsigned int len = strlen(str);
	char* left = str;
	char* right = str + len - 1;

	while (left < right)
	{
		int tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}

int main()
{
	char arr[100] = { 0 };

	gets(arr);

	reverse(arr);

	printf("%s\n", arr);

	return 0;
}

4、打印菱形

int main()
{
	int line = 0;
	scanf("%d", &line); //7

	int i = 0;
	//上 - 7行
	for (i = 0; i < line; i++)
	{
		//打印一行
		//打印空格
		int j = 0;
		for (j = 0; j < line - 1 - i; j++)
		{
			printf(" ");
		}
		//打印*
		for (j = 0; j < 2 * i + 1; j++)
		{
			printf("*");
		}
		printf("\n");
	}
	//下 - 6行
	for (i = 0; i < line - 1; i++)
	{
		int j = 0;
		for (j = 0; j <= i; j++)
		{
			printf(" ");
		}
		//* 打印
		for (j = 0; j < (line - 1 - i) * 2 - 1; j++)
		{
			printf("*");
		}
		printf("\n");
	}

	return 0;
}

5、喝汽水问题

喝汽水,1瓶汽水1元,2个空瓶可以换一瓶汽水,给20元,可以喝多少汽水

int main()
{
	int money = 0;
	int total = 0;
	int empty = 0;
	scanf("%d", &money);
	//购买
	total += money;
	empty = money;

	//置换
	while (empty >= 2)
	{
		total += empty / 2;
		empty = empty / 2 +empty%2; //empty%2  换的时候剩下的一瓶
	}

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

	return 0;
}
int main()
{
	int money = 0;
	int total = 0;
	int empty = 0;
	scanf("%d", &money);

	if (money >= 1)
		total = 2 * money - 1; //买一瓶 19元换38空瓶
	else
		total = 0;

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

	return 0;
}

6、调整奇数偶数顺序

输入一个整数数组,实现一个函数,
来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,
所有偶数位于数组的后半部分。

//void move_Parity(int* arr, int sz)
//{
//	int* left = arr;
//	int* right = arr + sz - 1;
//
//	while (left < right)
//	{
//		while ((*left) % 2 != 0)
//		{
//			left++;
//		}
//		while ((*right) % 2 == 0)
//		{
//			right--;
//		}
//		if (left < right)
//		{
//			int tmp = *left;
//			*left = *right;
//			*right = tmp;
//		}
//	}
//}


void move_Parity(int* arr, int sz)
{
	int left = 0;
	int right = sz - 1;

	while (left < right)
	{
		//从前向后找到一个偶数
		while ((left<right) && arr[left] % 2 == 1)
		{
			left++;
		}
		//从后向前找到一个奇数
		while ((left < right) && arr[right] % 2 == 0)
		{
			right--;
		}
		//交换
		if (left < right)
		{
			int tmp = arr[left];
			arr[left] = arr[right];
			arr[right] = tmp;
		}
	}
}

void Print(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	//全是偶数 全是奇数
	//奇偶交替
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	move_Parity(arr, sz);

	Print(arr, sz);

	return 0;
}


四、写程序判断当前机器的字节序

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

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

	return 0;
}

用函数

int cheek_sys()
{
	int a = 1;
	char* p = (char*)&a;
	/*if (*p == 1)
		return 1;
	else
		return 0;*/
	//1
	return *p;
}

//2
int cheek_sys()
{
	int a = 1;
	return *(char*)&a;
}

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

五、练习:

1

int main()
{
	char a = -1;
	//10000000000000000000000000000001原
	//11111111111111111111111111111110反
	//11111111111111111111111111111111补
	//11111111 - a 只能存8个bit,截断
	//%d - 以有符号整数的形式打印
	//整型提升 补符号位
	//11111111111111111111111111111111 - 补
	//打印原码
	//11111111111111111111111111111110
	//10000000000000000000000000000001 -1

	signed char b = -1;
	//11111111 - b
	//与a一样 -1

	unsigned char c = -1;
	//11111111 - c
	//无符号整型提升高位补0
	//00000000000000000000000011111111
	//%d打印 正数原反补相同

	printf("a=%d,b=%d,c=%d", a, b, c);
	//-1 -1 255
	return 0;
}

2

int main()
{
	//char -128~127
	char a = -128;
	//10000000000000000000000010000000
	//11111111111111111111111101111111
	//11111111111111111111111110000000
	//10000000
	//11111111111111111111111110000000 整型提升
	//以无符号打印 补码即原码 4,294,967,168

	printf("%u\n", a);
	//%u - 打印无符号整型
	return 0;
}

3

int main()
{
	char a = 128;
	//00000000000000000000000010000000 正数
	//10000000 -a
	//11111111111111111111111110000000 整型提升
	//与-128存入内存的值一样 4294967168

	printf("%u\n", a);
	return 0;
}

4

int main()
{
	int i = -20;
	unsigned int j = 10;

	//10000000000000000000000000010100 -20
	//11111111111111111111111111101011
	//11111111111111111111111111101100  -20补码

	//00000000000000000000000000001010  10原反补
	
	//11111111111111111111111111110110 -20+10补
	//11111111111111111111111111110101
	//10000000000000000000000000001010 补
	//-10

	printf("%d\n", i + j);

	return 0;
}

5

#include <windows.h>

int main()
{
	unsigned int i;
	//无符号 所有二进制序列都是有效位 不会小于0 判断恒成立
	//9 8 7 6 5 4 3 2 1 0 -1
	//-1在内存中是全1
	//11111111111111111111111111111111 无符号 都是有效位
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
		Sleep(1000);
	}

	//死循环
	return 0;
}

6

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	//strlen找\0 \0de ASCII码值是0 就是找0
	//有符号char范围:-128~127  一个圈0 1 2。。。127 -128。。。-3 -2 -1
	
	//减 倒着转-1 -2...-128 127。。。5 4 3 2 1 0 -1 -2。。。
	//找0 一共128+127=255
	return 0;
}

//signed short
//-32768 ~ 32767
//-32768 ~ -1 0 ~ 32767

在这里插入图片描述


7

unsigned char i = 0;

int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	//unsigned char取值范围:0~255

	return 0;
}


六、浮点型在内存中的存储

float,double,long double
浮点型表示的范围在float.h中定义

INT_MAX
INT_MIN
整形定义在limits.h中
#include <limits.h>
INT_MAX;
右击转到定义

以下代码输出结果是什么?

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、规则

IEEE 754
(-1)^S *M *2^E
(-1)^S 符号位,当s=0时,正数;s=1时,负数
M 有效数字 大于等于1,小于2
2^E 指数位

例 十进制数5.5
101.1 2^2 2^0 2^-1
1.011*2^2
(-1)*0 *1.011 *2^2
S = 0
M = 1.011
E = 2

例:9.0
1001.0
1.001*2^3
(-1)^0 *1.001 *2^3
S = 0
M = 1.001
E = 3

32位的浮点数float:
最高一位是符号位s 下面8位是指数作为E 剩下23位是有效数字M
S(1bit) E(8bit) M(23bit)

64位浮点数double:
最高一位是符号位s 下面11位是指数作为E 剩下52位是有效数字M
S(1bit) E(11bit) M(52bit)


2、浮点数的存储

M的规定:

1<=M<2
在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。
以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

指数E:

把E当做无符号数
首先,E为一个无符号整数(unsigned int) 这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的
取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真
实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E
是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

int main()
{
	float f = 5.5f;
	//101.1
	//(-1)^0 *1.011 *2^2
	//S = 0
	//M = 1.011
	//E = 2      存储:+127=129   10000001
	//内存里:0 10000001 01100000000000000000000  M补23位
	//16进制:40 B0 00 00   小端:00 00 b0 40
	return 0;
}

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,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位
00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000

E全为0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为
0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

E全为1

E是128,表示±无穷大(正负取决于符号位s)

int main()
{
	int n = 9;
	//00000000000000000000000100000000

	float* pFloat = (float*)&n;

	printf("n的值为:%d\n", n); //9
	printf("*pFloat的值为:%f\n", *pFloat);
	//浮点数序列
	//0 00000000 00000000000000100000000
	//E为全0
	//E直接是1-127=-126
	//M= 0.00000000000000100000000
	//0.00000000000000100000000 *2^-126
	//%f只打印6位 0.000000

	*pFloat = 9.0;
	printf("num的值为:%d\n", n);
	//1001.0
	//(-1)^0 *1.001 *2^3
	//S=0
	//E=3     +127
	//M=1.001
	//0 10000010 00100000000000000000000
	//%d的打印 有符号 正数原反补相同
	//1,091,567,616

	printf("*PFloat的值为:%f\n", *pFloat);
	//9.000000

	return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

三春去后诸芳尽

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

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

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

打赏作者

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

抵扣说明:

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

余额充值