关于c和c++的类型(1)
一、类型的含义
1) 什么是类型
一个计算机程序在运行时,其用到的任何变量(variable)、对象(object)、函数(function)等等(以下统称对象)都要占据一定字节数的内存。 那么,一个对象占据的字节数(简称对象的大小)由什么来决定呢? 答案是:它的类型(type)。
一个对象的类型决定了以下几件事:
- 对象的大小
- 对象的内存布局
- 对象可以参与的运算
2) 类型的分类
C/C++的类型一般分为两大类。
-
内建(built-in)类型 语言本身提供的类型,例如:
char
、int
、double
、bool
等等。 -
用户自定义类型 由内建类型、已有用户自定义类型复合而成的类型,例如:指针类型、数组类型、结构/联合/类类型、枚举类型等等。
除此之外,通常有如下说法:
- 内建类型、指针类型、枚举类型等合称为简单类型,其它的称为复合类型。
- 字符型、所有整型、浮点型、布尔型、枚举型合称为数值类型。
- 字符型、所有整型、布尔型合称为整数类型,因为它们的内存布局都采用整数的布局模式。
枚举类型的内存布局也与整数一样。但这并不能说明枚举类型和整型等价,尤其是在C++中。
二、对象(类型)的大小
1) 简单类型
简单类型对象的大小(字节数)根据语言的标准由编译器实现而定。常用简单类型的字节数如下表所示:
类型 | 32位 | 64位 |
---|---|---|
char | 1 | 1 |
int | 4 | 4 |
long | 4 | 8 |
T* | 4 | 8 |
实际上,我们编写的程序基本上没有特别精度要求。因此,可以不必去死记硬背类型的大小,只要大概了解类型的表示范围即可。例如:
- int:∓21亿,10个10进制数字
- long:32位系统中同int;64位系统中是19个10进制数字
- float:∓1038,6个10进制数字
- double:∓10308,15个10进制数字
一般地,表示范围大的类型称为长类型,反之则是短类型。
虽然float
类型的表示范围相对较大,但因其内存布局的原因,可能导致计算精度不能保证。例如:
#include <stdio.h>
int main() {
int si = 0;
long sl = 0;
float sf = 0;
double sd = 0;
for (int i = 0; i < 5794; ++i) {
si += i;
sl += i;
sf += i;
sd += i;
}
printf("si=%d\nsl=%ld\nsf=%f\nsd=%lf\n", si, sl, sf, sd);
return 0;
}
结果是:
si=16782321
sl=16782321
sf=16782320.000000
sd=16782321.000000
可以看到,累加到一个不大的数5794就使float
结果开始有精度损失了!
感谢李冠男先生,他提出的问题给了本程序灵感。
2) 复合类型
-
数组类型:基类型大小 x 所有维度长度的积
- 结构/类类型:≥ 所有成员的大小
因计算机的特性,成员之间可能被填充一些无用字节,所以最好用sizeof()运算符求大小。
- 联合类型:最大成员的大小
三、对象的内存布局
1) 简单类型
-
整数类型/指针类型
内存布局是平的,即除了符号位(如果有的话),其余的每一位都是数值的有机组成部分。
-
浮点类型
内存布局是结构化的,即内存是分段的,并有一些位有特殊含义。
请参阅IEEE浮点格式。这个格式能够解释前面float数据精度损失的原因。
2) 复合类型
-
结构/类类型
内存布局是结构化的,一般是按成员的声明顺序分配的。成员的内存布局由其(基)类型决定。
-
数组类型
连续存储。
四、对象参与的混合运算
从原则上来讲,如果有数值运算:
a @ b
那么对象a
和b
的类型应该是一样的,以保证结果类型的确定性。
如果不一样,例如a
是短类型,b
是长类型,那么编译器一般会采用类型提升方式,默认将短类型转换为长类型以保证计算的正确性。这种现象常被称为隐式类型转换。
一个特别的场合是复制。短类型向长类型复制可能面临精度损失的问题。例如:
int a = 1023456789;
float f = a; //可能有精度损失。一般会有编译警告。
除了赋值,形参实参结合也可能是复制。
指针的运算比较特殊,这里不再赘述。请参阅文章”指针和数组“。
复合类型(尤其是C++的类类型)的运算比较复杂,这里就不展开讨论了。