当前位置: C语言 -- 专题 -- 类型

类型

对象值的含义以及函数返回值的含义是由访问该值的表达式的类型(type)决定的。C语言中类型可分为对象类型(object types)和函数类型(function types)。对象类型是描述对象的类型;函数类型是描述函数的类型。在编译单元的不同位置,对象类型可能是不完整类型(incomplete type),也可能是完整类型(complete type)。不完整类型缺少足够的信息确定该类型对象的大小;完整类型有足够的信息确定该类型对象的大小。一个类型可以在整个编译单元中都是完整的或者不完整的;也可以在编译单元的不同位置改变状态。

/*这里struct s类型是不完整类型。*/
struct s;
...
/*这里struct s类型是完整类型。*/
struct s{
        int x;
        int y;
    };

根据ISO/IEC 9899:2018标准第6.2.5 Types节,C语言中类型及相互关系归纳总结如下图所示:

类型    { 基本类型    { 整数类型    { char类型   标准整数类型    { 标准有符号整数类型    { signed char类型   short int类型   int类型   long int类型   long long int类型   标准无符号整数类型    { _Bool类型   unsigned char类型   unsigned short int类型   unsigned int类型   unsigned long int类型   unsigned long long int类型   扩展整数类型   枚举类型   浮点类型    { 实数浮点类型    { float类型   double类型   long double类型   复数浮点类型    { float _Complex类型   double _Complex类型   long double _Complex类型   void类型   派生类型    { 聚合类型    { 数组类型   结构类型   联合类型   函数类型   指针类型   原子类型

:枚举类型虽属于整数类型,但ISO/IEC 9899:2018标准并未将其列入基本类型。


布尔类型(_Bool)对象能够存储的值为0或者1

char类型对象能够存储基本执行字符集中的任何字符。如果基本执行字符集中的字符存入char类型对象,其值应为非负值;其它字符存入char类型对象,其值由实现定义,但应在char类型所能表示的范围内。


C语言中存在5种标准有符号整数类型(standard signed integer types),即signed charshort intintlong intlong long int,实现还可能定义扩展有符号整数类型(extended signed integer types)。标准有符号整数类型和扩展有符号整数类型统称为有符号整数类型。signed char类型对象占有的存储空间同char类型对象相同。int类型对象应具有执行环境构架(the architecture of the execution environment)所建议的自然大小(natural size),能够表示INT_MININT_MAX范围内的任何值。

对于每个有符号整数类型都有一个对应的无符号整数类型,两者具有相同的存储空间和对齐要求(alignment requirements)。布尔类型(_Bool)和与标准有符号整数类型对应的无符号整数类型(unsigned charunsigned short intunsigned intunsigned long intunsigned long long int)统称为标准无符号整数类型。与扩展有符号整数类型对应的无符号整数类型称为扩展无符号整数类型。标准无符号整数类型和扩展无符号整数类型统称为无符号整数类型。

标准有符号整数类型和标准无符号整数类型统称为标准整数类型(standard integer types);扩展有符号整数类型和扩展无符号整数类型统称为扩展整数类型(extended integer types)。


对于任意两个具有相同符号和不同整数转换等级(integer conversion rank)的整数类型,具有较小整数转换等级的类型的值范围是另一个类型的值范围的子集。

有符号整数类型的非负值范围是对应无符号整数类型值范围的子集,相同值在两个类型中的表示是相同的。涉及无符号整数的计算永远不会溢出(overflow),如果计算结果不能使用结果无符号整数类型表示,计算结果将会减去模(模(modulo)是比结果类型可表示的最大值大1的数。)。


实数浮点类型有3种,即floatdoublelong doublefloat类型的值集是double类型值集的子集;double类型的值集是long double类型值集的子集。复数浮点类型也有3种,即float _Complexdouble _Complexlong double _Complex。实数浮点类型和复数浮点类型统称为浮点类型(floating types)。

每个浮点类型都存在一个对应的实数类型,它总是一个实数浮点类型。对于实数浮点类型,它是相同的类型。对于复数类型,它是从类型名中删除关键词_Complex得到的类型。

每个复数类型的表示和对齐要求与包含两个元素的相应实数类型的数组类型相同;第一个元素等于复数的实部部分(real part),第二个元素等于复数的虚部部分(imaginary part)。

double complex x = 1.0 + 2.0*I;
double arr[2];

memcpy(arr, &x, sizeof(double complex));
printf("%f\n", arr[0]);     //输出1.000000。
printf("%f\n", arr[1]);     //输出2.000000。

char类型、有符号整数类型、无符号整数类型、浮点类型统称为基本类型(basic types)。基本类型都是完整对象类型(complete object types)。即使实现将两个或者多个基本类型定义成相同的表示形式,它们仍然是不同的类型。实现可以定义新的关键词,为指定基本类型或者其他类型提供方法;这并不违反所有基本类型都应不同的要求。

char类型、signed char类型、unsigned char类型统称为字符类型(character types)。实现中char类型与signed char类型(或者unsigned char类型)应具有相同的表示范围、表示形式和行为。如果宏CHAR_MIN的值为0char类型与unsigned char类型具有相同的表示范围、表示形式和行为;如果宏CHAR_MIN的值与宏SCHAR_MIN相同,char类型与signed char类型具有相同的表示范围、表示形式和行为。无论选择哪种类型,char类型都是一种独立于signed char类型和unsigned char类型的类型,与signed char类型和unsigned char类型都不兼容。从程序可移植性的角度考虑,应显式地使用signed char类型或者unsigned char类型,尽量避免使用char类型。


枚举(enumeration)由一组命名的整数常量值组成;每个不同的枚举构成一个不同的枚举类型(enumerated type)。

char类型、有符号整数类型、无符号整数类型、枚举类型统称为整数类型(integer types)。整数类型和实数浮点类型统称为实数类型(real types)。整数类型和浮点类型统称为算术类型(arithmetic types)。每个算术类型属于一个类型域(type domain),实数类型域包括实数类型,复数类型域包括复数类型。

void类型包含一组空值,该类型是一个不完整对象类型(incomplete object type)。


对象类型和函数类型可以构造任意数量的派生类型(derived types),如下所示:

1、 数组类型(array type)描述了一组具有特定成员对象类型(称为元素类型)的连续分配的非空对象。当数组类型指定时,元素类型应是完整类型。数组类型的特征在于它的元素类型和数组中的元素数量。数组类型认为是从其元素类型派生而来;如果元素类型为T,该数组类型有时称为T类型数组。从元素类型构造数组类型称为数组类型派生(array type derivation)。

2、 结构类型(structure type)描述了一组按顺序分配的非空成员对象(某些情况下,结构成员是不完整的数组。),每个结构成员具有可选的名称和可能不同的类型。

3、 联合类型(union type)描述了一组重叠的非空成员对象,每个联合成员具有可选的名称和可能不同的类型。

4、 函数类型(function type)描述了具有指定返回类型的函数。函数类型的特征在于其返回类型及其形式参数的数量和类型。函数类型认为是从其返回类型派生而来;如果返回类型为T,该函数类型有时称为返回T类型函数。从返回类型构造函数类型称为函数类型派生(function type derivation)。

5、 指针类型(pointer type)可以从函数类型或者对象类型派生,称为引用类型(referenced type)。指针类型描述了一个对象,其值提供对引用类型的实体引用。从引用类型T派生的指针类型有时称为指向T的指针。从引用类型构造指针类型称为指针类型派生(pointer type derivation)。指针类型是完整的对象类型。

6、 原子类型(atomic type)描述了使用关键词_Atomic指定的类型。

这些构造派生类型的方法可以递归使用。


算术类型和指针类型统称为标量类型(scalar types)。数组类型和结构类型统称为聚合类型(aggregate types)。聚合类型不包括联合类型,因为联合类型对象一次只能包含一个成员。

未知大小的数组类型是不完整类型;对于该类型的标识符,通过在后面的声明中指定大小(具有内部链接或者外部链接)来完成。未知内容的结构类型或者联合类型是不完整类型;对于该类型的所有声明,通过在同一范围内稍后声明具有其定义内容的相同结构或者联合标记来完成。

extern int arr[];   //这里数组arr是不完整类型,通常出现在头文件中。
...
int arr[3];         //这里数组arr是完整类型,通常在源文件中进行定义式声明。

如果一个类型不是不完整类型,也不是变长数组类型,该类型具有常量大小。


数组类型、函数类型、指针类型统称为派生声明符类型(derived declarator types)。从类型T派生的声明符类型是通过将数组类型、函数类型或者指针类型派生用于T,来构造从T派生的声明符类型。

类型的特征在于其类型类别(type category)。类型类别可能是派生类型的最外层派生类型,也可能是类型本身(如果该类型不包含派生类型。)。


到目前为止涉及的类型都是非限定类型(unqualified type)。每个非限定类型都有多个对应的限定类型(qualified type)版本。限定类型就是在非限定类型前添加一个或者多个限定符,例如:constvolatilerestrict。同一类型的限定版本和非限定版本是属于相同类型类别的不同类型;但它们具有相同的表示形式和对齐要求。派生类型不受派生它的类型限定符(如果存在)限定。

/*
** 第一个const类型限定符是限定指针指向的对象,不是限定指针的;
** 第二个const类型限定符是限定指针的。
*/
const int * const ptr;

关键词_Atomic也是限定符。如果存在_Atomic限定符,对应类型则为原子类型。原子类型的大小、表示形式、对齐要求不必与对应的非限定类型相同;因此只要允许原子版本类型与其它限定版本的类型一起使用,ISO/IEC 9899:2018标准就明确地使用原子类型、限定类型、非限定类型加以区分。如果没有特别提到原子类型,限定类型和非限定类型不包括原子类型。


void类型指针pointer to void)应与字符类型指针(pointer to a character type)具有相同的表示形式和对齐要求。指向兼容类型的限定版本或者非限定版本的指针应具有相同的表示形式和对齐要求。所有结构类型的指针应具有相同的表示形式和对齐要求。所有联合类型的指针应具有相同的表示形式和对齐要求。其他类型的指针不需要具有相同的表示形式或者对齐要求。

主要参考资料:

1、ISO/IEC 9899:2018

2、open-std.org : Completeness of types

3、wiki.sei.cmu.edu : Use only explicitly signed or unsigned char type for numeric values

4、en.cppreference.com : Type