目录
一、数据类型的基本归类
1.1 整型
char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long
signed long [int]
字符在内存中存储的是字符的ASCII码值,ASCII码值是整型,所以字符类型归类到了整型。
char 是否是有符号的 signed char?
C语言标准并没有规定,取决于编译器,在绝大部分编译器中 char == signed char。
1.2 浮点数
float
double
1.3 构造类型
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
1.4 指针类型
int * pi;
char * pc;
float * pf;
void * pv;
1.5 空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型
void test(void)
test函数没有任何参数,表示test函数不会返回任何值
二、整型在内存中的储存
当我们写下int a = 10;这行代码时,我们知道,会在栈区开辟一块4个字节空间,并把10储存进去。但是,数据到底是以什么形式怎样去储存呢?
计算机能够处理的是二进制数据;
整型和浮点型数据在内存中也都是以二进制的形式进行存储的。
2.1 反码、原码、补码
整数由3种二进制表示方法:原码、反码、补码。三种方法均有符号位和数值位两部分。
符号位用“0”表示“正”,正的整数,原码、反码、补码相同。
用“1”表示“负”,负的整数,原码、反码、补码要进行计算。
负整数:
原码:直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码:原码符号位不变,其他位依次按位取反,得到反码。
补码:反码+1,得到补码。
整数在内存中储存的是补码的二进制序列。例如:
#include<stdio.h>
int main()
{
int a = -10; //4个字节,32个bit位
//1000 0000 0000 0000 0000 0000 0000 1010 原码
//1111 1111 1111 1111 1111 1111 1111 0101 反码
//1111 1111 1111 1111 1111 1111 1111 0110 补码,也是内存中储存的形式
//按16进制换算 - 每4个二进制位可以换算1个十六进制位
//例如:1111 - f(15)
// f f f f f f f 6
//0x ff ff ff f6
unsigned int b = -10;
//1111 1111 1111 1111 1111 1111 1111 0110 内存中储存的
//站在无符号整型的角度,所有位都是数值位
return 0;
}
在计算机中,整数数值一律用补码来表示和储存。
优势在于:CPU只有加法器,采用补码储存可以将符号位和数值域统一计算,加法和减法可以统一处理。例如:我们需要做减法:1-1,代码如下:
1-1
1+(-1)
如果用原码计算,是错误的:
0000 0000 0000 0000 0000 0000 0000 0001 - 1
1000 0000 0000 0000 0000 0000 0000 0001 - -1
相加后得到:
1000 0000 0000 0000 0000 0000 0000 0010 - -2//err
采用补码计算:
0000 0000 0000 0000 0000 0000 0000 0001
1111 1111 1111 1111 1111 1111 1111 1111
相加后得到:
1 0000 0000 0000 0000 0000 0000 0000 0000
最高位1丢掉后:
0000 0000 0000 0000 0000 0000 0000 0000 - 0//ok
2.2 大小端介绍
大端字节存储:把一个数据的低位字节处的数据存放在高地址处,高位字节内容放在低地址处。
小端字节存储:把一个数据的地位字节处的数据存放在低地址处,高位字节内容放在高地址处。
我们以 int a = 0x11223344;为例:
那么,我们可以写一个代码,判断当前计算机的字节序。
int a = 1;
小端存储:
低 -------> 高
01 00 00 00
大端存储:
低 -------> 高
00 00 00 01
a占4个字节,怎么才能拿出4个字节中的1呢?
char* 解引用就可以访问1个字节
代码如下:
#include<stdio.h>
int main()
{
int a = 1;
char *p = (char*)&a;
if(*p == 1)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
我们在上述代码中,将a的地址强制类型转化为字符型指针,使其每次访问1个字节。那么,整型先储存为字符型,再转化为整型,涉及到整型提升,会得到什么样的结果呢?
char a = -1;
1111 1111 1111 1111 1111 1111 1111 1111
char类型只保留8个bit位,所以a中取后8位1111 1111,高位是符号位,后7位是数值位
signed char b = -1;
1111 1111 - b
unsigned char c = -1;
1111 1111 - c
printf("a = %d, b = %d, c = %d", a, b, c); //%d是十进制的形式打印有符号的整数(整型)
a是char类型,要发生整型提升。有符号:整型提升是按照数据类型的符号位来提升的。补的全是1:
//1111 1111 1111 1111 1111 1111 1111 1111
同理,b也一样
c是unsigned char
无符号:整型提升,高位补0
//0000 0000 0000 0000 0000 0000 1111 1111 - 补码,原码与补码相同
此外,值得注意的是,当整型被强制类型转化为字符型后再做运算时,被强制类型转化的数据会先整型提升再运算。
三、浮点型在内存中的存储
3.1 常见的浮点数
3.14159
1E10 - 1.0*10^10
1.23 - 12.3*10^-1
整型的存储形式和浮点数不同,代码证明如下:
#include<stdio.h>
int main()
{
int n = 9;
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;
}
3.2 浮点数存储规则
任何一个二进制浮点数V都可以表示成下面的形式:
V = (-1) ^ S * M * 2 ^ E
其中:
(-1) ^ S:表示符号位,取0或-1;
M:表示有效数字,大于等于1,小于2;
2 ^ E:表示指数位,类似于10 ^ (-1)。
例如:
十进制的:5.5,转化为二进制为:101.1(1*2^2+0+1*2^0+2^(-1))。转化为上述形式可表示为:(-1)^0*1.011*2^2。
十进制的:9.0,转化为二进制位:1001.0。转化为上述形式可表示为:(-1)^0*1.001*2^3。
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位S,接下来的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接下来的11位是指数E,剩下的52位为有效数字M。
对于有效数字M和指数E,还有一些特别的规定:
前面说过,1<=M<2,也就是说,M可以写成1.xxxxxxx的形式,其中xxxxxx表示小数部分。
IEEE 754规定,再计算机内部保存M时,默认这个数第1位总是1,因此可以被舍去,只保存后面xxxxxxx部分,比如保存1.01时,只保存01,等读取的时候,再把第一位的1加上去,以此节省1位有效数字。
至于指数E,情况有些复杂:
首先,规定,E为一个无符号整数(unsigned int)。
这意味着,如果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,即1000 1001。
float f = 5.5;
储存形式为:(-1)^0*1.011*2^2
S = 0
M = 1.011
E = 2
0 10000001 01100000000000000000000
0100 0000 1011 0000 0000 0000 0000 0000
// 40 b0 00 00
3.2 指数E从内存中取出的三种情况
1. E不全为0或不全为1
浮点数表示:
指数E的计算值减去127(或1023),得到真实值;再将有效数字M前加上第1位的1。
比如:
0.5的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为:0111 1110,
而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000。
2. E全为0
浮点数的指数E等于1-127(或者1-1023)即为真实值。
有效数字M不再加上第一位的1,而是还原为0.xxxxx的小数,这样做是为了表示正负0,以及接近0的很小的数字。
3. E全为1
如果有效数字M全为0,表示正负无穷大,正负取决于符号位。
所以,我们可以理解刚才的例子:
#include<stdio.h>
int main()
{
int n = 9;
//0000 0000 0000 0000 0000 0000 0000 1001
float* pFloat = (float*)&n;//因为是int*,所以不能直接赋给pFloat类型,需要强制转化,所以现在pFloat指向了n的4个字节
printf("n的值为:%d\n", n);//9
printf("*pFloat的值为:%f\n", *pFloat);//0.000000
//访问浮点型并拿出来,因此内存理解为
//0 00000000 00000000000000000001001
//S E M
//E全0
//E=-126
//M=0.00000000000000000001001
//S=0
//(-1)^0*0.00000000000000000001001*2^(-126)
//float只能打印小数点后6位
*pFloat = 9.0;
//9.0
//1001.0
//(-1)^0*1.001*2^3
//往内存中存放
//0 10000010 00100000000000000000000
//拿出来
printf("num的值为:%d\n", n);
//0100 0001 0001 0000 0000 0000 0000 0000// -> 1091567616
printf("*pFloat的值为:%f\n", *pFloat);//9.000000
return 0;
}