C目录
c语言中数据的类型及其基本归类
c语言中内置数据类型以及该类型的每一个数据所占内存主要有以下几种:
· char 占一个字节
· short 占两个字节
· int 占四个字节
· long 占四个字节
· long long 占八个字节
· float 占四个字节
· double 占八个字节
此外,还有其他的数据类型,比如结构体类型,指针类型,空类型,这里不过多赘述。
可以把数据类型归类为以下几种:
1.整形:
char
signed char
unsigned char
short
signed short
unsigned short
int
signed int
unsigne int
long
signed long
unsigned long
2.浮点型
float
double
3.构造类型
数组
结构体 struct
枚举 enum
联合 union
4.指针类型
5.空类型
整型在内存中的存储
在了解整形在内存中的存储之前,要先知道原码,反码,补码的概念,即整形数值以二进制形式存储,对于整形数据来说,都是以补码的形式存放在内存中。
对于原码、反码、补码而言,都有“有符号”和“无符号”两种。
“有符号”是指二进制数据从左往右第一位,如果是“0”,那么这个二进制数据表示正数,如果是“1”,那么这个二进制数据表示负数。
“无符号”是指没有正负之分,全部是正数,这样可以存范围更大的数值。(比如有符号位int类型,可以存 -2^31——2^31-1 范围内,而无符号int类型可以存0——2^32)
原码:数值翻译成二进制后得到的就是原码。
反码:原码的符号位不变,其他位取反。
补码:反码加1得到补码。
(正数的原码、补码、反码相同)
比如下面:
然后在VS2019里面设计一个int类型变量,初始化值位-10,找到对应的内存存储,发现如下:
其顺序和补码按高位字节到低位字节不同,所以这里又要引入大小端的概念。
大小端介绍
大端存储模式:指数值的低位字节存储在内存的高地址中,高位字节存储在内存的低地址中。
小端存储模式:指数值的低位字节存储在内存的低地址中,高位字节存储在内存的高地址中。
低位字节和高位字节:
比如int类型-10的补码:11111111111111111111111111110110
在四个字节中存储,分为 11111111 11111111 11111111 11110110
四个比特位一组,十六进制,所以是 ff ff ff f6 ,这样子左边是高位字节,右边是低位字节。
内存的高地址和低地址:
如上图,在内存图右边的0x007DE841到0x007DE845就是从低地址到高地址。
char类型在内存中的存储
存储方式
定义 char a=-10;由于char类型只有一个字节,所以只有八个比特位的位置存放。
它在内存中存储是:11110110
得到的方式如下:
-10的原码:100000000000000000000000000001010
-10的反码:1111111111111111111111111111111110101
-10的补码:1111111111111111111111111111111110110
int类型的-10的补码取后八位得到的结果就是char类型的存储结果
整形提升
使用char类型的数据的时候,要把它存储的八个二进制位整形提升,有符号位的char类型,从左往右第一位是符号位,符号位后面补齐的数字和符号位相同;无符号位的,前面全部补0。
比如char(默认signed)类型的1110110 第一个“1”就是符号位,于是1后面补24个“1”,得到最后的结果是1111111111111111111111111111111110110,
而如果是unsigned char类型存储的1110110,那么无符号位,则最后结果是:
0000000000000000000000001110110
转化成原码有两种方式,一个是先减“1”,再除符号位以外按位取反;另一个是先除符号位外按位取反,再加上“1”。
此外,由于char类型只有一个字节,即八个比特位,所以有符号位只能存-128——127的数据,超过则无法存储,如下图。
顺时针是+1操作,当01111111(十进制127)+1后,到了10000000,由于是补码的方式存下来,
10000000整形提升后变换得到的原码是1000000000000000000010000000,所以是-128,然后不断+1到了11111111,实际上是-1,这个时候+1就到了0。所以char类型存储是这样循环的,可以以下面一个例子看出:
#include<stdio.h>
int main()
{
char a[1000] = {0};
int i=0;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
运行结果是255,而不是1000,是因为它a[i]是这样子的:
-1,-2,-3,-4,-5,……,-127,-128,127,126,……,3,2,1,0,-1,-2………………
对于strlen,读到“\0”或者ASCll码是0的时候就会停下来,所以从-1读到0就会停下。而这里面有-1到-128是128个,127到1是127个,所以127+128=255,结果是255。
浮点型在内存中的存储
存储方式
根据国际标准 IEEE (电气和电子工程协会) ,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s 表示符号位,当 s=0 , V 为正数;当 s=1 , V 为负数。
M 表示有效数字,大于等于 1 ,小于 2 。
2^E 表示指数位
对于32位(float类型单精度浮点数)的浮点数,存储模型是,符号位S占一个比特位,指数位E占八个比特位,有效数字M占23个比特位。而对于64位(double类型双精度浮点数)的浮点数,存储模型是,S占1个比特位,E占11个比特位,M占52个比特位。下图是32位浮点数存储模型图:
定义 float a=10.5;换算成二进制是1010.1
这个数是正数,所以S=0,由于二进制是1010.1,所以M是1.0101,指数位应该是3。(指数位可以类比十进制10^E,因为这里是二进制,所以是2^E,效果都是小数位移动。)
所以写成 (-1)^0*1.0101*2^3
但是写进内存是01000001001010000000000000000000
分区来看,0 10000010 01010000000000000000000
但是发现指数位E和有效数字M和我们想的不一样,这是因为IEEE对其有特殊规定。
E和M的特殊规定
E的特殊规定:
首先,E是一个无符号整数,所以八位的E取值范围位0~255;11位的E取值范围位0~2047。但是指数E是可以取负的,所以对于八位的E,初始的E要加上中间数127,得到的才是存进内存的E的数值;对于11位的E,这个中间数是1023。
所以上面的真实E是3+127=130.
比如0.5,换算成二进制是0.1,但是规定M要是1<=M<2,所以0.1=1*2^-1(二进制),这个时候E就是-1,存进内存的E就是-1+127=126.
M的特殊规定:
由于M是一个1~2之间的数,所以M总是1.xxxxxxx的形式,这个时候小数点左边的1是恒定的,所以可以把它省略,在取出这个浮点数的时候,再把这个“1”加上去就可以了,这样就可以空出一个比特位来存有效数字。
然后,E从内存中取出还分为三种情况:
E不全为0或者全为1:
这个时候,把E减去127得到后的E,然后M左边加上1,M原本的是小数点后面的内容。
比如0.5,二进制存储0 01111110 00000000000000000000000
取出来就是S=0,E=126-127=-1,M=1.00000000000000000000000,得到的结果是
(-1)^0*1*2^(-1),是0.1,换算成十进制是0.5。
E全为0:
此时,E存储结果全是0,只有原本值是-127,才会得到E=-127+127=0,所以结果是
1.xxxxx /(2^127),再化一下就是0.1xxxxx/(2^126),是一个非常非常小的数字,就是0.
E全为1:
这时,如果有效数字全为0,那么表示+-无穷大。