当前位置:网站首页>程序猿入门攻略(十二)——数据的存储
程序猿入门攻略(十二)——数据的存储
2022-07-02 18:21:00 【Yuan_o_】
1、数据类型介绍
C语言中基本的内置类型:
char //字符数据类型 所占字节大小–>1
short //短整型 所占字节大小–>2
int //整形 所占字节大小–>4
long //长整型 所占字节大小–>4(32位)/8(64位)
long long //更长的整形(C99) 所占字节大小–>8
float //单精度浮点数 所占字节大小–>4
double //双精度浮点数 所占字节大小–>8
类型的意义:
- 使用这个类型开辟内存空间的大小(大小决定使用范围)。
- 如何看待内存空间的视角。
1.1 类型的基本分类
整形家族:
char(字符的本质是ASCII码值,是整型)
unsigned char
signed charshort
unsigned short [int]
signed short [int]int
unsigned int
signed intlong
unsigned long [int]
signed long [int]long long
unsigned long long [int]
signed long long [int]
tip:其中char到底是signed char还是unsigned char标准是未定义的,取决于编译器的实现。
浮点数家族:
float
double
构造类型:(自定义类型 --> 我们可以自己创建出新的类型)
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
tip:数组类型:
指针类型:
int *pi;
char *pc;
float *pf;
void *pv;
空类型:
void 表示空类型(无类型)。
通常应用于函数的返回类型、函数的参数、指针类型。
//第一个void 表示函数不会返回任何值
//第二个void 表示函数不需要传任何参数
void test(void)
{
}
int main()
{
test(1);
return 0;
}
2、整形在内存中的存储
一个变量的创建是要在内存中开辟空间的。
空间的大小是根据不同的类型而决定的。
2.1 原码、反码、补码
计算机中的整数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位
和数值位
两部分,符号位都是用0表示“正”,用“1”表示“负”,而数值位负整数的三种表示方法各不相同。
原码
直接将二进制按照正负数的形式翻译成二进制就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1就得到补码。
正数的原、反、补码都相同。
对于整形来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值一律用补码来表示和存储。
原因在于,使用补码,可以将符号位和数值位统一处理。
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。(tip:补码取反+1可以得到原码)
ep:
#include <stdio.h>
int main()
{
int a = 20;
//20
//原码:00000000 00000000 00000000 00010100
//16进制:0x 00 00 00 14
//反码:00000000 00000000 00000000 00010100
//补码:00000000 00000000 00000000 00010100
int b = -10;
//-10
//原码:10000000 00000000 00000000 00001010
//16进制:0x 80 00 00 0a
//反码:11111111 11111111 11111111 11110101
//16进制:0x ff ff ff f5
//补码:11111111 11111111 11111111 11110110
//16进制:0x ff ff ff f6
return 0;
}
上例说明整型数据存放在内存中的是补码。
2.2 大小端介绍
什么是大小端:
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;(大端字节序存储)
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。(小端字节序存储)
为什么会有大小端:
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节排序的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个16 bit
的short
型x
,在内存中的地址为0x0010
,x
的值为0x1122
,那么0x11
为高高字节,0x22
为低字节。
对于大端模式,就将0x11
放在低地址中,即0x0010
中,0x22
放在高地址中,即0x0011
中。小端模式刚好相反。
我们常用的x86
结构是小端模式,而KEIL C51
则为大端模式。很多ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
百度2015年系统工程师笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)
//方法一
#include <stdio.h>
int main()
{
int a = 1;
if (*(char*)&a == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
//方法二
#include <stdio.h>
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
ep:
指针类型存在的意义:当我们想要访问一个字节时,我们就把指针类型转化为char*
,当我们想要访问两个字节时,转化为short*
,当我们访问四个字节并且是浮点数类型时,转化为float*
。
2.3 练习
1、
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d\n", a, b, c);
return 0;
}
上面的代码输出什么?
解析:首先我们来了解内存中signed char
和unsigned char
的存储范围,有符号数signed char在内存中占一字节,8个bit位,其中第一位为符号位,0表示正数,1表示负数,如下图所示,signed char的取值范围为-128~127;无符号数unsigned char没有符号位,如下图,他的取值范围为0~255。
然后是代码在内存中具体的转换过程:(此过程中需要注意将整型数据存放到char中时会发生截断,而以整型方式打印char a时会发生整型提升)
打印结果:
tip:
当整型提升有符号数时,前面补符号位。
当整型提升无符号数时,前面补0。
2、
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
printf("%d\n", a);
return 0;
}
打印结果:
3、
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
printf("%d\n", a);
return 0;
}
与第二题同理:打印无符号数整型提升有符号数时,提升了符号位。
打印结果:
4、
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
//按照补码的形式进行运算,最后格式化成为有符号整数
5、
#include <stdio.h>
#include <windows.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
Sleep(1000);//为观察方便,休眠1000毫秒
}
return 0;
}
输出结果:
因为定义i
为无符号数,所以当i=0
,再减一时,默认没有符号位,成为一个很大的数,造成死循环。
6、
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
a[1000]
是char类型,取值范围:-128~127strlen
是求字符串的长度,关注的是字符串中’\0
’(ASCII码值为0)之前出现了多少字符。
当程序如上图所示循环-1,-2...-128,127,126,125...3,2,1,0
碰到0就停止了。
此时a[1000]的长度为128+127=255
7、
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
因为unsigned char i
的取值范围为0~255
所以i <= 255
恒成立,程序死循环
8、
//错误示例:
#include <stdio.h>
#include <string.h>
int main()
{
if (strlen("abc") - strlen("abcdef") >= 0)
printf(">\n");
else
printf("<\n");
return 0;
}
//修改一
#include <stdio.h>
#include <string.h>
int main()
{
if (strlen("abc") > strlen("abcdef"))
printf(">\n");
else
printf("<\n");
return 0;
}
//修改二
#include <stdio.h>
#include <string.h>
int main()
{
if ((int)strlen("abc") - (int)strlen("abcdef") >= 0)
printf(">\n");
else
printf("<\n");
return 0;
}
因为strlen
的返回值是size_t
类型,而size_t
就是unsigned int
类型,所以程序运行结果一直大于(无符号数-无符号数=无符号数)
3、浮点数在内存中的存储
常见的浮点数:
3.14159
1E10(科学计数法表示)
浮点数家族包括:float、double、long double
类型
浮点数表示的范围:float.h定义(整型:limits.h定义)
3.1 一个例子
浮点数存储的例子:
#include <stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%d\n", *pFloat);
*pFloat = 9.0;
printf("n的值为:%d\n", n);
printf("pFloat的值为:%f\n", *pFloat);
return 0;
}
输出结果:
由此可见,在内存中,整型和浮点型的存储方式是不一样的。
3.2 浮点数存储规则
num和*pFloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差这么大?
要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
根据国际标准IEEE(电气和电子工程协会)745,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S * M * 2^E(-1的S次方乘M乘2的E次方)
- (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
- M表示有效数字,大于等于1,小于2。
- 2^E表示指数位。
ep:
由此可知,浮点数在内存中无法精确保存。
ep2:
十进制的-5.0,写成二进制是-101.0
,相当于-1.01*2^2
。那么,S=1,M=1.01,E=2。
IEEE 754规定:
对于32为的浮点数,最高的1位是符号位S,接着的8位数是指数E,剩下的23位有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 745对有效数字M和指数E,还有一些特别规定。
前面说过,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂。
首先,E是一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0255;如果E为11位,它的取值范围为02047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 745规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个数是127;对于11位的E,这个中间数是1023.比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
然后,指数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 0000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
补充:
解释一开始的题目:
为什么0x00000009
还原成浮点数,就成了0.000000
?
首先,将0x00000009
拆分,得到第一位符号位S=0,后面8位的指数E=00000000
,最后23位的有效数字M=00000000 00000000 00001001
9 -> 00000000 00000000 00000000 00001001
由于指数E全为0,所以符合E从内存中取出的第二种情况,因此,浮点数V就写成:
V=(-1)^0 * 0.000000000000000000001001 * 2(-126)=1.001*2(-146)
显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000
再看例题第二部分:
首先,浮点数9.0等于二进制的1001.0,即1.001 *2^3
9.0 -> 1001.0 -> (-1)^0 * 1.001 * 2^3 -> S=0,M=1.001,E=3+127 = 130
那么,第一位的符号位S=0,有效数字M等于001后面加20个0,凑满23位,指数E4等于3+127=130,即10000010
所以,写成二进制形式,应该是S+E+M,即
0 10000010 0010000 00000000 00000000
这个32位的二进制数,还原成十进制,正是1091567616
边栏推荐
- "Patient's family, please come here" reading notes
- Emmet基础语法
- Obligatoire pour les débutants, cliquez sur deux boutons pour passer à un contenu différent
- Windows2008R2 安装 PHP7.4.30 必须 LocalSystem 启动应用程序池 不然500错误 FastCGI 进程意外退出
- PHP-Parser羽毛球预约小程序开发require线上系统
- codeforces每日5题(均1700)-第四天
- MySQL
- 数据降维——因子分析
- Quanzhi A33 uses mainline u-boot
- According to the atlas of data security products and services issued by the China Academy of information technology, meichuang technology has achieved full coverage of four major sectors
猜你喜欢
IEDA refactor的用法
性能测试如何创造业务价值
Markdown基础语法
机器学习笔记 - 时间序列预测研究:法国香槟的月销量
Imitation Jingdong magnifying glass effect (pink teacher version)
教程篇(5.0) 10. 故障排除 * FortiEDR * Fortinet 網絡安全專家 NSE 5
yolov3 训练自己的数据集之生成train.txt
Use cheat engine to modify money, life and stars in Kingdom rush
新手必看,点击两个按钮切换至不同的内容
Markdown basic grammar
随机推荐
xml开发方式下AutowiredAnnotationBeanPostProcessor的注册时机
MySQL advanced (Advanced) SQL statement
Memory management of C
论文导读 | 关于将预训练语言模型作为知识库的分析与批评
Qpropertyanimation use and toast case list in QT
【ERP软件】ERP体系二次开发有哪些危险?
Date tool class (updated from time to time)
In pytorch function__ call__ And forward functions
Web2.0 giants have deployed VC, and tiger Dao VC may become a shortcut to Web3
[100 cases of JVM tuning practice] 02 - five cases of virtual machine stack and local method stack tuning
A4988驱动步进电机「建议收藏」
golang:[]byte转string
论文导读 | 机器学习在数据库基数估计中的应用
Gamefi chain game system development (NFT chain game development function) NFT chain game system development (gamefi chain game development source code)
Machine learning notes - time series prediction research: monthly sales of French champagne
机器学习笔记 - 时间序列预测研究:法国香槟的月销量
NPOI导出Excel2007
Chic Lang: completely solve the problem of markdown pictures - no need to upload pictures - no need to network - there is no lack of pictures forwarded to others
C file input operation
高级性能测试系列《24. 通过jdbc执行sql脚本》