数据的存储(C语言)
- 创业
- 2025-08-20 19:45:02

数据类型:
要了解数据是如何存储的,我们就得先知道C语言中的数据类型
基本数据类型基本数据类型,也就是C语言内置类型:
char -> 字符型
short -> 短整型
int -> 整型
long -> 长整型
long long -> 更长的整型
float -> 单精度浮点型
double -> 双精度浮点型
数据类型的大小C语言中可用sizeof操作符来计算数据类型在内存中所占空间的大小,单位是字节
#include <stdio.h> 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 <stdio.h> 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 <stdio.h> 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; } 大小端字节序存储每个机器的存储模式不同,
二进制的补码在存储时 有的机器是从前往后存储,而有的机器是从后王前存储
由于存储顺序不同,就产生了大小端的概念
当前机器是从后往前存储的
大端存储:数据的低权值位保存在内存的高地址处,数据的高权值位保存在内存的低地址处
小端存储:数据的低权值位保存在内存的低地址处,数据的高权值位保存在内存的高地址处
(小端口诀:小小小(低权值位,低地址,小端))
例题:判断当前机器是大端字节序存储,还是小端字节序存储
#include <stdio.h> 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 <stdio.h> 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 <stdio.h> int main() { char a = -128; // 原码:10000000 // 反码:01111111 // 补码:10000000 printf("%u\n", a);//非常大的一个数字 //在取的时候是用无符号整数 //认为补码就是原码 // 10000000 00000000 00000000 00000000 return 0; } //第三题 #include <stdio.h> int main() { char a = 128; // 正数的原反补相同 // 补码: 10000000 printf("%u\n", a);//2^32 //在取得时候 进行整型提升 //10000000 00000000 00000000 00000000 return 0; } //第四题 #include <stdio.h> 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 <stdio.h> 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 <stdio.h> 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 <stdio.h> 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 <stdio.h> 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; }看完代码结果跟我们想的完全不一样,为什么不一样呢,是因为浮点数的存储,
以下让我们仔细了解一下浮点数到底是如何存储的
浮点数的存储规则根据国际标准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
2、 64位浮点数的存储
最高的1位是符号位S,接着的11位是指数E,剩下的52位是有效数字M
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 <stdio.h> 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语言)由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“数据的存储(C语言)”