C语言笔记(数据的存储篇)

时间:2024-10-23 12:46:45

目录

1.数据类型的详细介绍

2.整型在内存中的存储:原码、反码、补码

3.大小端字节序介绍及判断

4.浮点型的内存中的存储解析


1.数据类型的详细介绍

下述是内置类型:

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

        在这里就不说不同数据类型在内存中所占数据类型大小了。注意的是C语言中的没有字符串的数据类型

数据类型的意义

1.使用数据类型开辟内存空间的大小;

2.如何看待内存空间的视角。

整形家族:

char
unsigned char     //字符在存储的时候存储的是ASCLL码值A,ASCLL是整数,
signed char       //所以在归类的时候,字符属于整型家族

short
unsigned short [int]
signed short [int]

int
unsigned int
signed int

long
unsigned long [int]
signed long [int]      //[]中的数据类型是可以省略的

        注意, char是signed char还是unsigned char 是取决于编译器的。常见的编译器上char的类型就是signed char。

        数据元素个数和数组的名字发生变化,数组的类型也会发生变化。

        浮点数家族:

float
double

        构造类型

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

        指针类型

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

        void  表示空类型以为就是没有什么类型,可以用于函数的返回类型、函数的参数、指针类型。

2.整型在内存中的存储:原码、反码、补码

        正整型的原反补码都是相同的,但负整型数据的反码要符号位不变其他位置取反,加上1就会变成补码,如下 : 补码也可以先取反再+1得到原码。负整型补码转变成原码有两种方式。

-1//32位机器
原码 10000000 00000000 00000000 00000001
反码 11111111 11111111 11111111 11111110
补码 11111111 11111111 11111111 11111111


补码转原码
//方式1
补码 11111111 11111111 11111111 11111111
反码 11111111 11111111 11111111 11111110 -1
原码 10000000 00000000 00000000 00000001 取反

//方式2
补码 11111111 11111111 11111111 11111111
中间 10000000 00000000 00000000 00000000 取反
原码 10000000 00000000 00000000 00000001 +1

        整形数据在内存中是以二进制补码的形式来存放的。本例子列举的是32位机器中的存储。

int a = 20;

// 20
// 00000000 00000000 00000000 00010100--原码
// 00000000 00000000 00000000 00010100--反码
// 00000000 00000000 00000000 00010100--补码
// 0x00 00 00 14   转换成16进制  在看看存储
 
int b= -10;
//  -10
// 10000000 00000000 00000000 00001010--原码
// 11111111 11111111 11111111 11110101--反码
// 11111111 11111111 11111111 11110110--补码
// 0xff ff ff f6  转换成16进制

int a = 20;在VS2017下的存储方式

int b = -10;在VS2017下的存储方式

例题,下属例题都是判断输出什么的

1.  

#include <stdio.h>
int main()
{
	char a = -1;
    //10000000 00000000 00000000 00000001 原码
    //11111111 11111111 11111111 11111110 反码
    //11111111 11111111 11111111 11111111 补码
    //11111111 截断,将一个整型数值赋予一个字符变量的时候要截断
    //11111111 11111111 11111111 11111111 补码
    // 因为要输出的是整型,应该整型提升,有符号位置应该按照符号位来提升 1 补1 0补0
    //10000000 00000000 00000000 00000000  取反
    //10000000 00000000 00000000 00000001  原码
	signed char b = -1;//和上面相同
    //10000001 
	unsigned char c = -1;
    //10000000 00000000 00000000 00000001 原码
    //11111111 11111111 11111111 11111110 反码
    //11111111 11111111 11111111 11111111 补码
    //11111111                            截断
    //00000000 00000000 00000000 11111111 //整型提升 无符号为整型提升 补0
    //存储方式是一样的,但输出的方式是不一样的 
	printf("a=%d,b=%d,c=%d", a, b, c);  // -1 ,-1,255
	return 0;
}

2.

#include <stdio.h>
int main()
{
	char a = -128;
//10000000 00000000 00000000 10000000  原码
//11111111 11111111 11111111 01111111  反码
//11111111 11111111 11111111 10000000  补码
//截取 10000000
//整型提升 看char是有符号的char 整型提升是看原来的数据类型来提升的
//11111111 11111111 11111111 10000000  //输出的是无符号数,可以直接输出,无符号数只有正的
	printf("%u\n", a);// 4294967168
	return 0; 
}

3.

int main()
{
	char a = 128;
	// 00000000 00000000 00000000 10000000
	// 10000000 截断
	// 11111111 11111111 11111111 10000000 整型提升
	printf("%u\n", a);
	return 0;
}

4.

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 11110110
	//10000000 00000000 00000000 00001001
	//10000000 00000000 00000000 00001010 //-10
	printf("%d\n", i + j);//-10 //输出的形式有符号数 相加之后本来是无符号数的
	return 0;
}

5.

#include <stdio.h>
#include <windows.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--) //会无限制的循环,i不可能是负数的,因此会一直大于0
	{
		printf("%u\n", i);
		Sleep(100);
	} 
	return 0;
} //可以执行一下 从零之后就是一个更大的数字了

6

#include <stdio.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i; 
	}
	//-1 -2 .......-128 127 126 3 2 1 0 128+127=255
	printf("%d", strlen(a)); //找到'\0' 其ASCLL码值为 0 找到0 
	return 0;
}

7.

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

循环限制条件是无符号数,是一定要注意,设置的限制条件一定要大于0。

3.大小端字节序介绍及判断

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

        为什么会存在大端存储和小端存储呢?

        其思想源于格列夫游记,两个国家由于鸡蛋应该是先从大头剥还是从小头处开始剥皮的问题开始了战争。   

        小端存储,举一个例子,下面是一个数

int a = 0x11223344;

     下面就是数据在VS2017的存储方式。

        数据的低位应该是这样理解的,123 3就是这个数据的低位。看数据的低位放在了低地址上,数据的高位放在了高地址上。

做个表格详细了解一下

地址 0x007EFBFC 0x007EFBFD 0x007EFBFE 0x007EFBFE
小端存储 44 33 22 11
大端存储 11 22 33 44
低地址 高地址

        我们再写一个小程序来判断一个编辑器是大端存储还是小端存储。

#include <stdio.h>
int main()
{
	int a = 1;//存储方式是 0x00 00 00 01  如果只取一个字节就比较好判断了
	char *pa = (char *)&a;//()括号中的强制类型转化
	if (*pa == 1)
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}
	return 0;
}

        还可以将这个逻辑封装到一个函数中,大家可以练习一下,在以后的时候中好的函数是经常调用的。

封装函数

void  check_byte()
{
	int a = 1;
	if ((char *)&a)
		printf("小端存储\n");
	else
		printf("大端存储\n");
}

4.浮点型的内存中的存储解析

        大家想一下 3.14159, 1E10.在内存中是如何存储的?当然和整型是不一样的。先看下面的例子。

int main()
{
int n = 9;
//00000000 00000000 00000000 00001001
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;
}

        以下是上述代码的值,可以往后看看浮点型是如何存储的就理解了,在文章的最后会有解析的。

       浮点数的存储规则

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

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

        举个例子就明白了,

将十进制的 5.0   1> 转换成二级制101.1,相当都1.01×2^2

按上述的格式就是 S=0,表示正数;M=1.01,E=2。如果是-5的话那就是S=-1了。

 再来一个 5.5= 101.1二进制  , 转换成科学计数法 1.011*2^2    (-1)^0*1.011*2^2

S=0, M=1.011, E=2。 0.5 大家思考一下  注意一下(浮点数中没有原反补的概念)

单精度浮点数存储模型(4byte)

双精度浮点型(8byte)

特殊规定

      1. M值的存储方式

        前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。 (此种方式可以提升精度)

        比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

        2. E的存储方式      

        E为一个无符号整数是无法判读E是正负的。如果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。

        指数E从内存中中取出还可以再分为三种情况

        1.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

        2.E全为0

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

        3.E全为1

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

实际的例子 

5.5

float f = 5.5f;
	// 转换成SME的形式 (-1)^0 * 1.011 * 2^2
	//S = 0;M = 1.011; E = 2; 2+127=129
	//0 10000001 01100000000000000000000
	//0100 0000 1011 0000 0000 0000 0000 0000
	// 4    0    b    0    0    0    0    0

4.小节开始的例题

#include <stdio.h>
int main()
{
	int n = 9;
	float *pFloat = (float *)&n;
	//00000000 00000000 00000000 00001001
	//0 00000000 0000000 00000000 00001001
	//(-1)^0 * 0.00000000000000000001001 * 2^-126 相当于0了
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);


	*pFloat = 9.0;
	//9.0的存储方式
	//1001二级制 1.001^3 3+127=130
	//0 1000 0010 00100000000000000000000
	//01000001000100000000000000000000 存储方式
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);//1,091,567,616
	return 0;
}

        由于存储方式的不同和访问方式的不同,会造成数据错误。

相关文章