文章目录
一、数据类型
1.1 数据类型基本介绍
C语言中数据类型主要包括内置类型
和自定义类型
(构造类型)
内置类型:C语言本身就有的类型
自定义类型:自己可以创造或改变这个类型
大致分类如下:
2.2 数据类型的意义
1)数据类型决定了开辟内存空间
的大小
使用int类型,就开辟4个字节,使用char类型,就开辟1个字节
例如:int – 4Byte , double – 8Byte
2)类型不同导致存储方式
不同
例如:整型数据以补码方式存储,而浮点型数据的存储是按照IEEE754标准,存储了S、M、E相关的值
二、整数在内存中的存储
2.1 有符号与无符号
整数分为有符号整数和无符号整数,要弄清楚有符号与无符号,首先要了解符号位这个概念
符号位:二进制的最高位(最左边一位)是符号位
例如:
有符号数:用signed修饰,符号位为0表示正数,符号位为1表示负数
例如:
无符号数:用unsigned修饰,没有符号位的概念,只能表示0和正整数
注意:
1)只有整型和字符型才能被signed,unsigned修饰
2)整型默认是有符号的
3)char 是有符号还是无符号,取决于编译器,大部分是有符号的
打印有符号的整数:%d
打印无符号的整数:%u
signed认为最高位是符号位
unsigned认为最高位不是符号位
但都向内存申请相同大小的空间,类型只是用来创建变量
2.2 原码、反码、补码
当我们把一个数转换成2进制表示的时候,整数的二进制就有三种表示形式:
原码、反码、补码
无符号数:原码、反码、补码相同
有符号数:
(1)正数:原码、反码、补码相同
(2)负数:
原码 = 数值按正负数的形式直接翻译成二进制形式
反码 = 原码符号位不变,其它位按位取反
补码 = 反码+1
补码转原码:取反+1
例如:
int a = 10;
//00000000 00000000 00000000 00001010 - 原码
//00000000 00000000 00000000 00001010 - 反码
//00000000 00000000 00000000 00001010 - 补码
int b = -10;
//10000000 00000000 00000000 00001010 - 原码
//11111111 11111111 11111111 11110101 - 反码
//11111111 11111111 11111111 11110110 - 补码
注意:
1)整数在内存中
存储
的是二进制序列的补码
2)在计算机运算的时候,都是以补码的方式来运算的
3)看运算结果,要看原码
使用补码的好处:
1.统一了正数和负数
2.加法和减法可以统一处理(CPU只有加法计算器)
例如 1减1的问题可以转换成1加上负1,即1 - 1 --> 1 + (-1)
三、大小端字节序和字节序的判断
3.1 字节序存储基本介绍
在计算机中,内存被分为一个个字节存储在内存中,对于超过一个字节的数据(比如 short-2 , int-4),
就需要考虑它在内存中存放顺序的问题。
字节序存储的两种常见方式为大端字节序存储和小端字节序存储
3.2 大小端字节序存储存储
在了解大小端字节序前,我们需要先了解高位、低位、高地址和低地址的概念
对数据:左边为高位,右边为低位;
对内存空间:左边为低地址,右边为高地址
例如:
-
大端字节序存储
将一个数据的低位字节序内容放到高地址,高位字节序内容放到低地址处 -
小端字节序存储
将一个数据的低位字节序内容放到内存的低地址处,高位字节序内容放到内存的高地址处
例如:
- 如何判断当前机器是大端还是小端?
x86结构一般小端模式,KEILC51则为大端模式。很多的ARM,DSP都是小端模式,有些ARM处理器还可以由硬件来选择是大端模式还是小端模式
- 不管是大端存储还是小端存储,最终都要将数据还原回来(怎么存的,怎么还原)
练习:写一个函数,判断当前机器的字节序
思路:
(1)定义一个变量a,值为1
1对应的二进制序列为 0x00 00 00 01
如果是大端存储那么内存中是00 00 00 01,如果是小端存储那么内存中是01 00 00 00
(2)拿到第一个字节
如果值是1那么就是小端存储;如果值是0,那么就是大端存储
具体步骤:拿到a的地址(int * 类型),将a强制类型转换成 char * ,然后再解引用
int check_sys()
{
int a = 1;
return *(char*)&a;//返回1是小端,返回0是大端
}
int main()
{
if (check_sys())//int*
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
四、浮点数在内存中的存储
4.1 浮点数基本介绍
浮点数的表示
(1) 一般写法,比如:3.1415926
(2)科学计数法,比如:1E10 表示1.0乘以10的10次方
浮点型包含:flaot,double,long double
-
编译器默认一个小数是double类型,要想表示float类型,需要在数字后面加上 f 或F
-
默认打印小数点后六位
-
浮点数在内存不能保证精确保存
如果要比较两浮点数是否相等
1.确定可接受的误差范围
2.两者作差,取绝对值
例如:比较浮点数 f 和3.45是否相等就可以让 f 和 3.45 作差,如果它们之间的差值小于自己可接受的范围
(比如 0.00000001),那么就认为这两个浮点数相等
if(fabs(f-3.45)<0.00000001)//fabs求浮点数的绝对值,abs是求整数的绝对值
{
//相等
}else
{
//不相等
}
4.2 浮点数在内存中的存储
基本介绍:
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式
V = (-1)^ S * M * 2 ^ E
- (-1) ^ S表示符号位,当S=0,V为正数;当S=1,V为负数
- M表示有效数字,M是大于等于1,小于2的
- 2^E表示指数位
例如:
10进制:5.5
对应的2进制:101.1
因为M要满足大于1小于2,所以化为 1.011* 2^2
5.5 = (-1)^0 * 1.011 * 2^2
S = 0,表示正数
M = 1.011,满足大于等于1小于2
E = 2
小数点前的权重:2^0 2^1 2^2 ……
小数点后的权重:2^(-1) 2^(-2) 2^(-3)……
存储规则
- 浮点数的存储,存储的就是S,M,E 相关的值
- 单精度的浮点数(float-32比特位):1bit存S,8bit存E,23bit存M
- 双精度的浮点数(double -64比特位):1个bit存S,11个bit存E,52个bit存M
具体存储的过程
对于M:
M可以写成1.xxxxx的形式,xxxxx表示小数部分,由于计算机内部保存M时,默认第一位总是1,
所以IEEE 745规定计算机在存储的时候只存小数
,输出的时候在前面补上1,这样可以节省1位有效数字。
比如32位的浮点数,留给M只有23位,如果将第一位舍去,那么就可以多保存一位小数,相当于保存了24位有效数字
对于E:
E是一个无符号整数,当E有8位时,取值范围为0 ~ 255;当E有11位时,取值范围为0 ~ 2047 ,但E有可能为负数
E为负数的情况:
10进制:0.5
对应的2进制:0.1 --> 1.0 * 2^(-1)
所以IEEE 745规定,存入E要加上一个中间值
,如果E有8位,就加上127,如果E有11位,就加上1023,
拿出来时再减去中间值
浮点数取的过程
一般情况(E不为全0或全1)
对于E:E减去中间值
对于M:在小数点前面补上1
例如:
0.5的二进制为0.1 ,因为M的取值需要满足大于1小于2,所以将0.1转换为1.0 * 2 ^ (-1) ,再让 E+中间值
即(-1)+ 127 = 126,表示二进制的 0111 1110,再将M = 1.0 小数点前的1去掉,用0补齐23为
得到二进制表示形式:
特殊情况
(1)E为全0
E为全0时,规定E的真实值为1-127或1-1023,此时M不再加上小数点前的1,而是还原为0.xxxx的小数
可以这么理解:
如果E有8位,那么E为全0也就是E的真实值 + 127后结果为0
的情况,那么E的真实值就是-127
±1.xxxx * 2 ^ (-127) 2 ^ (-127) 表示2分之127次方,所以 ±1.xxxx * 2 ^ (-127)表示的就是
正负无穷接近0
的数字
(2)E为全1
二进制的11111111表示255,如果E有8位,那么E为全1也就是E+中间值=255
,如果中间值是-127,那么E=128
浮点数表示为 ±1.xxxx * 2 ^ (128)
2 ^ (128) 表示2的128次方,所以 ±1.xxxx * 2 ^ (128)表示的就是正负无穷大
的数字
练习1:
float f = 5.5f;
分析:
5.5 = 1.011 * 2 ^2
S = 0 ,正数
M = 1.011E = 2,存的是 E+127 = 129 ,129对应的二进制序列为1000 0001
得到二进制表示形式 0 1000 0001 01100000000000000000000
让四个二进制位为1组 0100 0000 1011 0000 0000 0000 0000 0000
转换为16进制 0x40 b0 00 00
练习2:
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
分析:
(1)
9对应的二进制序列为 00000000 00000000 00000000 00001001
将n的地址强制类型转换成float*并赋给pFloat
以整数方式存储,以整数方式(%d) 打印,n的值为9
*pFloat 认为内存中存的是浮点数,所以 以浮点数的形式取出一个值,然后使用%f 打印
E为全0,表示的是正负无穷接近0的数字,所以打印结果为0.000000
(2)
pFloat = 9.0
9.0是浮点数,要转换为S,E,M的相关值才能存进去
9.0 = 1.001 * 2 ^ 3
9.0表示成 (-1)^0 * 1.001 * 2^3
S=0
M=1.001,存001,同时要补0凑够23位
E=3,存到内存要加127,得130,对应二进制:10000010
存到内存中的二进制序列为: 0 10000010 00100000000000000000000
以整数形式打印(%d),认为存的是补码,因为第一位为0,是正数,原、反、补码相同
n的值为1091567616
以浮点数方式存储,以浮点数方式打印,*pFloat的值为9.000000
运行结果: