文章目录
数据类型
所谓数据类型 实际上就是对内存中的数据(01串)规定了长度和解读规则
分类
C语言中的数据类型大致可以分类如下
类型分类 | 具体 |
---|---|
基本类型 | 数值型、浮点型 |
构造类型 | 数组,结构体,共用体,枚举类型 |
指针类型 | 指针 |
布尔型 | bool |
空类型 | void |
而其中数值型又可以分为
类型分类 | 具体 |
---|---|
整型 | 短整型(short), 整型(int), 长整型(long long) |
浮点型 | 单精度型(float),双精度型(double),长双精度实型(long double) |
存储方式/表示形式 &数据范围
众所周知, 8 b i t = 1 B y t e 8bit = 1Byte 8bit=1Byte,而C语言中,几乎所有数据类型都是以Byte为单位进行存储的,从存储方式即可知道基本数据类型的数据范围。
整型
整型,即整数类型。在计算机中用一串01串来进行表示,每一个bit都可以放一个0或者一个1。
同时,整型在计算机中按照补码形式进行表示,即最高位当作符号位并且有(不看符号位):
正
数
:
补
码
=
原
码
正数: 补码=原码
正数:补码=原码
负
数
:
原
码
=
补
码
取
反
+
1
负数: 原码=补码取反+1
负数:原码=补码取反+1
那么只要知道字节数,就可以知道其存储范围了,最大的情况自然是除掉符号位,其余全填1;而最小则是除了符号位全为0。这样保证取反之后可以最大。
为了知道整型的取值范围首先还需要了解一下移位操作。
移位
移位即把内存中存储的数据整体左移或者右移,例如:
int a = 1
此时在a占用的这块内存中存储的内容是00000000 00000000 00000000 00000001
使用variable << num语句可以将变量所在内存中所有数据整体左移,超过存储范围则舍去,不足则补0a = a << 2
此时a占用的这块内存中存储的内容是00000000 00000000 00000000 00000100
知道了这这些就可以探究各个整型数据类型的数据范围了
#include <iostream>
using namespace std;
int main()
{
int num;
int size_of_num = sizeof(num);
cout << "size of int is: " << size_of_num << endl;
cout << "min value of int is: " << ( num = (1 << size_of_num * 8 - 1) ) << endl;
//除了最高位为1,其余全是0
cout << "max value of int is: " << ~num << endl;
//除了最高位为0,其余全是1
}
其中~为取反操作。
同理可以得到:
数据类型 | 定义标识符 | 占字节数 | 数值范围 |
---|---|---|---|
短整型 | short | 2 | -215~215-1 (32767) |
整型 | int | 4 | -231~231-1(约2×109) |
超长整型 | long long | 8 | -263~263-1(约9×1018) |
无符号整型 | unsigned int | 4 | 0~232-1(约4×109) |
浮点型
浮点型的存储有些复杂,C语言中的浮点数表示基于IEEE浮点表示法。对于浮点数,可以根据指数的情况分为三种类型
- 指数全为1
- 指数全为0
- 指数既不全为1又不全为0
float
指数既不全为1又不全为0
- float占用4字节
- float在内存中的存储方式:SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
- S为符号位
- E为指数部分,规定指数减去127才是真正的指数,即此部分实际表示 2 D e c i m a l ( E E E E E E E E ) − 127 2^{Decimal(EEEEEEEE)-127} 2Decimal(EEEEEEEE)−127
- M为系数,默认最高为小数点后的部分,即真实值为1.MMMMMMMMMMMMMMMMMMMMMMMM,这里为二进制表示,M为0或1
e.g.
01000001 11000000 00000000 00000000
分析:
- 符号位为0,正数
- 指数部分为10000011,减去127为4,表示 2 4 2^4 24
- 系数为1.1,即十进制数1.5
- 具体表示的数为 1.5 × 2 4 = 24 1.5\times2^4=24 1.5×24=24
知道了这些则可以探究其数据范围了。根据规则除了符号位全填1为最大;包括符号位全填1为最小。但是,C语言中为了处理溢出的情况,当指数部分全部为1时,浮点数的值将变为nan,故最大为指数除最后一位全为1,系数全为1;最小同理。
#include <iostream>
using namespace std;
int main()
{
unsigned int num = 0b011111111011111111111111111111111;
float f = *((float *) &num);
cout << "min value of float is: " << f << endl;
num = num ^ ( 1 << 31 );
f = *((float *) &num);
cout << "max value of float is: " << f << endl;
}
指数全为1
如果此时小数部分全为0,那么会这个值表示的是无穷。如果符号位为0那么就是正无穷,否则,则是负无穷。
而如果不全为0,则是nan。
指数全为0
这时候小数部分的值不再是1.MMMMMMMMMMMMMMMMMMMMMMMM,而是0.MMMMMMMMMMMMMMMMMMMMMMMM。
指数部分则是
2
−
2
k
−
1
2 - 2^{k-1}
2−2k−1,其中k是指数部分的位长。
这样做的目的有两个:
- 如果都是1.MMM,这种形式的话,我们没有办法表示0.
- 采用这种表示后会根据符号位有+0.0和-0.0的区别
- 为了让数足够小的时候,能够让这些数字均匀的接近0。
others
double等和float的表示方式类似,唯一不同是存储的位数不同,具体见下表
数据类型 | 符号位/阶码 | 指数长度/伪码 | 系数长度 |
---|---|---|---|
float | 1 | 8 | 23 |
double | 1 | 11 | 52 |
临时数 | 1 | 15 | 64 |
综上,可以得到:
数据类型 | 定义标识符 | 数值范围 | 占字节数 | 有效位数 |
---|---|---|---|---|
单精度实型 | float | -3.4×1038~3.4×1038 | 4 | 6~7位 |
双精度实型 | double | -1.7×10308~1.7×10308 | 8 | 15~16位 |
长双精度实型 | long double | -3.4×104932~1.1×104932 | 16 | 18~19位 |
结构体的存储
结构体存储所占空间,理想情况应该是所有成员所占空间之和,但是事实上,会采用内存对齐的方式进行处理。即按照定义顺序进行处理,按照当前所占空间最大的数据进行对齐。
所谓对齐就是假设前边的数据都按照当前所占空间最大的数据进行分配空间,如果前边有数据自己所占空间没占满,则可以把可以放的下的数据补上。
e.g.
struct Data
{
long long c;
short a;
short d;
int b;
long long f;
};
long long为8,short为2,自动补齐为8,下一个short为2,可以和上一个short合在一起,int为4,可以和上两个short合在一起,总共占空间为8,不会再分配内存,故此时结构体所占空间为16。
struct Data
{
long long c;
short a;
short d;
int b;
short e;
};
前边几个类似,最后多一个short,不能再补在前边,故会单独分配一个大小为8的空间。
struct Data
{
short a;
int b;
short c;
long long d;
};
这里a为2,int为4,打包成一起;short自动补齐为4自己打包成一起;
以此类推。
类型转换
数据之间可以数据类型转换。不同数据类型之间规则不尽相同。
整数类型之间的转换
低位数向高位数
直接即可转换
int main()
{
long long a;
int b = INT32_MAX;
a = (int)b;
cout << a << endl;
}
结果为2147483647
高位数向低位数
按照内存,超出的高位直接舍弃
int main()
{
long long a = 1LL << 62;
int b = (int)a;
cout << b << endl;
}
按照内存关系即可推知为0
浮点和整数之间的转换
- 整数和浮点之间如果在数据范围内可直接互换
- 浮点转换为整数时,小数部分直接舍弃
- 超出数据范围时,直接内存覆盖
#include <iostream>
using namespace std;
int main()
{
unsigned int num = 0b011111111011111111111111111111111;
cout << num << " ";
float f = *((float *) &num);
cout << f << " " << ( num =(int) f ) << endl;
}
会发现前后num数值没有改变。
隐式类型转换
按照运算顺序,自动由低类型(数据范围小的类型)转换成高类型(数据范围大的类型),大致过程如下
s
h
o
r
t
,
c
h
a
r
→
i
n
t
→
u
n
s
i
g
n
e
d
i
n
t
→
l
o
n
g
l
o
n
g
−
>
d
o
u
b
l
e
←
f
l
o
a
t
short,char\rightarrow int\rightarrow unsigned int\rightarrow long long -> double \leftarrow float
short,char→int→unsignedint→longlong−>double←float
double a = 2.0 * ( 1 / 2);
double b = 2.0 * 1 / 2;
结果a=0,b=1;
对于a,先算1 / 2为整型运算,向下取整,为0之后转换成浮点和2.0相乘,结果为0
对于b,先算2.0 * 1为浮点,此后所有运算均为浮点运算,故结果为1
有关结构体的类型转换
结构体之间的强制类型转换
直接内存覆盖,按照目标结构体的定义对数据进行解释。
#include <iostream>
using namespace std;
struct A
{
int a = 1;
int b = 2;
};
struct B
{
int a = 3;
};
int main()
{
A a; B b;
a = (A&) b;
cout << a.a << endl;
}
结果为3.
结构体与整数之间的隐式类型转换
#include <iostream>
using namespace std;
struct A
{
int a;
A( int a ): a(a) {}
};
int main()
{
A a = 5;
a = 3;
cout << a.a << endl;
}
即先将3利用单变量构造函数转换成A;对第二条语句来说,a=3实际上相当于:
A tmp = 3;
a = tmp;
这样如果有多个成员的话,可能会造成不必要的影响。
溢出
类型转换造成的溢出
参见类型转换部分
运算过程造成的溢出
#include <iostream>
using namespace std;
int main()
{
int a = 1 << 30;
int b = 1 << 30 | 1;
int c = a * b;
cout << c << endl;
}
结果为1073741824,可见是将计算结果的低32位直接赋值给c。
直接使用临时数造成的溢出
临时数都有个范围,如果在代码中直接出现整数,则默认是int型,而出现浮点数同前文所述。
e.g.
#include <iostream>
using namespace std;
int main()
{
long long a = 1 << 33;
cout << a << endl;
}
按照常理应该为
2
33
2^{33}
233,但实际上结果却是0,应为立即数是int型,只有32位,左移33位超出了范围,按照规则会被舍弃。
如果想使结果真正为
2
33
2^{33}
233,则只需要使用:
#include <iostream>
using namespace std;
int main()
{
long long a = 1LL << 33;
cout << a << endl;
}
1LL即long long型的1,这时候自然不会超出数据范围了。