ISO/IEC 14882:2011之条款3.9——类型
3.9 类型
1、[注:3.9及其子条款关于类型的表示对于实现是强制要求的。有两种类型:基本类型和复合类型。类型描述了对象(1.8)、引用(8.3.2)或函数(8.3.5)。——注结束]
2、对于平凡可拷贝的类型T的任一对象(除了一个基类子对象之外),该对象是否持有类型T的一个有效值,组成该对象的基本字节(1.7)可以被拷贝到char或unsigned char的一个数组中[注:比如通过使用库函数(17.6.1.2)std::memcpy或std::memmove]。如果char或unsigned char的数组的内容被拷贝回到该对象中,那么该对象随后将持有其原始值。[例:
#define N sizeof(T) char buf[N]; T obj; // obj被初始化为其原始值 std::memcpy(buf, &obj, N); // 在这两个对std::memcpy的调用之间, // obj可能被修改 std::memcpy(&obj, buf, N); // 在这个点,标量类型的obj的每个子对象持有其原始值
——例结束]
3、对于任一平凡可拷贝的类型T,如果两个指向T的指针指向距离较远的类型T对象obj1和obj2[译者注:这里的距离远是指obj1的地址与obj2的地址的差的绝对值比较大],即obj1和obj2都不是一个基类子对象,那么如果组成obj1的基本字节(1.7)被拷贝到obj2中[注:比如通过使用库函数(17.6.1.2)std::memcpy或std::memmove],那么obj2将随后持有与obj1相同的值。[例:
T *t1p; T *t2p; // 给定,t2p指向了一个初始化好的对象⋯⋯ std::memcpy(t1p, t2p, sizeof(T)); // 在这个点,*t1p中的平凡可拷贝类型的每个子对象含有与*t2p中相应子对象相同的值
——例结束]
4、类型T的一个对象的对象表示是由类型T的对象所采纳的N个unsigned char对象的序列,这里N为sizeof(T)。一个对象的值表示是持有类型T的值的比特组。对于平凡可拷贝的类型,值表示是确定一个值的对象表示中的一组比特,而该值是一组实现定义的值的一个独立的元素[注:此意图为,C++的存储模型与ISO/IEC 9899 C编程语言相兼容。]。
5、已被声明但没被定义的一个类,或一个未知大小或不完整元素类型的数组,是一个不完全定义的对象类型[注:一个不完全定义的对象类型的一个实例的大小和布局是未知的。]。不完全定义的对象类型和void类型是不完整类型(3.9.1)。对象不应该被定义为具有一个不完整类型。
6、一个类类型(诸如“类X”)可能在一个翻译单元的某个点是不完整的,而在后面可能会完整;“类X”类型在这两个点都是同一个类型。一个数组对象的声明类型可能是不完整的类类型的一个数组,并从而是不完整的;如果这个类类型在此翻译单元中后面变为完整的,那么这个数组类型也变为完整的;此数组类型在那两个点都是同一个类型。一个数组对象的已被声明的类型在一个翻译单元中的某个点可能是未知大小的一个数组,并从而是不完整的,而在后面可能变得完整;在那两个点上的数组类型(“T的未知边界的数组”和“N个T的数组”)是不同的类型。一个指向未知大小的数组指针类型,或者被一个typedef声明所定义为未知大小的一个数组的一个类型,不可能是完整的。[例:
class X; // X是一个不完整的类型 extern X* xp; // xp是指向一个不完整类型的一个指针 extern int arr[]; // arr的类型是不完整的 typedef int UNKA[]; // UNKA是一个不完整的类型 UNKA* arrp; // arrp是一个指向一个不完整类型的指针 UNKA** arrpp; void foo() { xp++; // 不良形式的:X是不完整的 arrp++; // 不良形式的:不完整的类型 arrpp++; // OK:UNKA*是已知的 } struct X { int i; }; // 现在X是一个完整的类型 int arr[10]; // 现在arr的类型是完整的 X x; void bar() { xp = &x; // OK:类型为“指向X的指针” arrp = &arr; // 不良形式的:不同的类型 xp++; // OK:X是完整的 arrp++; // 不良形式的:UNKA不可能是完整的 }
——例结束]
7、[注:在不完整类型的上下文中所描述的声明和表达式的规则是禁止的。——注结束]
8、一个对象类型是一个(可能被cv限定)并非一个函数类型[译者注:比如,void(void)、int(void)、int(int)等等。函数类型的变量不能直接使用,但可以通过在类型后加*,使其成为指针类型便可使用。],也不是一个引用类型,也不是一个void类型的类型。
9、算术类型(3.9.1)、枚举类型、指针类型、指向成员的指针类型、std::nullptr_t,以及这些类型的cv限定版本(3.9.3),总称为标量类型。标量类型、POD类(条款9),这些类型的数组以及这些类型的cv限定版本总称为POD类型。标量类型、平凡可拷贝的类类型(条款9),这些类型的数组,以及这些类型的cv限定版本(3.9.3)总称为平凡可拷贝类型。标量类型、平凡可拷贝类型(条款9)、这些类型的数组和这些类型的cv限定版本(3.9.3)总称为平凡类型。标量类型、标准布局的类类型(条款9)、这些类型的数组以及这些类型的cv限定版本(3.9.3)总称为标准布局类型。
10、一个类型是一个字面量类型,如果它是:
——一个标量类型;或
——一个引用类型;或
——具有以下全部属性的一个类类型(条款9):
——它具有一个平凡析构器,
——每个在brace-or-equal-initializer中的,为非静态数据成员的构造器调用和完整表达式(如果有)是一个常量表达式(5.19),
——它是一个聚合类型(8.5.1)或至少含有一个constexpr构造器或构造器模板,它们不是一个拷贝或搬移构造器,并且
——它含有全部非静态数据成员以及字面量类型的基类;或
——字面量类型的数组。
11、如果两个类型T1和T2具有相同的类型,那么T1和T2是布局相兼容的类型。[注:布局兼容的枚举在7.2中描述]。布局兼容的标准布局的结构体和标准布局的联合体在9.2中描述。——注结束]
3.9.1 基本类型
1、声明为字符(char)的对象应该足够得大以存储实现的基本字符集的任一成员。如果一个来自该集合的字符被存储在一个字符对象中,那么那个字符对象的整型值与那个字符的单字符字面量形式的值相同。一个char对象是否可以持有一个负数值是由实现定义的。字符可以被显式地声明为unsgiend或signed。朴素的char、signed char和unsigned char是三个不同的类型。一个char、一个signed char和一个unsigned char占据相同的存储量并且都有相同的对齐要求(3.11);即,它们都有相同的对象表示。对于字符类型,对象表示的所有比特都参与到值表示中。对于无符号字符类型,值表示的所有可能的位模式来表示数字。这些要求对于其它类型不需要持有。在任一特定的实现中,一个朴素的char对象可以接受与一个signed char或一个unsigned char相同的值;而接受哪个类型是由实现定义的。
2、有五种标准的带符号整型类型:“signed char”,“short int”,“int”,“long int”和“long long int”。在这个列表中,每个类型提供了至少与在其前面的类型一样多的存储量。也可以有实现定义的扩展带符号整型类型。标准的和扩展的带符号整型类型总称为带符号整型类型。朴素int类型具有由执行环境的架构所建议的自然大小[注:即足够大,以包含在INT_MIN和INT_MAX范围内的任一值,这两个宏在<climits>头文件中定义。];提供其它带符号的整数类型以满足特殊需要。
3、对于每个标准带符号整数类型,存在一个相应的(但不同的)标准无符号整数类型:“unsigned char”,“unsigned short int”,“unsigned int”,“unsigned long int”和“unsigned long long int”,每个占用与其相应带符号版本的相同的存储量,并具有相同的对齐要求(3.11)[注:见7.1.6.2关于在类型和指定类型的type-specifier序列之间的相关性];即,每个带符号整型具有与其相应的无符号整数类型相同的对象表示。同样地,对于每个扩展带符号整数类型,存在一个相应的扩展无符号整数类型,与其存储量和对齐要求相同。标准和扩展无符号整数类型总称为无符号整数类型。一个带符号整数类型的非负值的范围是相应无符号整数类型的子范围,并且每个相应带符号/无符号类型的值表示应该是相同的。标准带符号整数类型和标准无符号整数类型总称为标准整数类型,而扩展带符号整数类型和扩展无符号整数类型总称为扩展整数类型。
4、声明为unsigned的无符号整型,应该遵守算术模2n规则,这里n是那个整数特定大小的值表示的比特个数。[注:这暗示着无符号算术不会溢出,因为无法用无符号整数类型来表示的结果用模去减,而除数比无符号整数类型所能表示的最大值要更大。]。
5、类型wchar_t是一个独立的类型,其值可以为在所支持的本地之中所指定的最大扩展字符集的所有成员表示独立的编码(22.3.1)。类型wchar_t应该具有与其它整数类型的其中之一相同的大小、符号和对齐要求(3.11),称为其基本类型。类型char16_t和char32_t表示与uint_least16_t和uint_least32_t(分别在<stdint.h>中声明)具有相同的大小、符号和对齐的独立类型,称为基本类型。
6、类型bool的值要么是true,要么是false。[注:以由此国际标准所描述的方法而使用一个bool值,比如检查一个未被初始化的自动对象的值,可能导致它行为就如它既非true也非false。][注:没有signed、unsigned、short或long的bool类型或值。——注结束]类型bool的值参与整型晋升(4.5)。
7、类型bool、char、char16_t、char32_t、wchar_t,以及带符号和无符号整数类型总称为整型类型[注:从而,枚举(7.2)不是整型;然而,枚举可以被晋升为整型类型,就如4.5中所指定的那样。]。整型类型的一个同义词是整数类型。整型类型的表示应该用纯二进制数值系统来定义值。[注:使用二进制数0和1的整数表示的一个位置表示,在此表示中,后续比特的值表示是累加的,从1开始,并且被后续2的幂的整数相乘,除了可能对最高位置的比特。(适应于系统处理系统的美国国家词典)][例:此国际标准允许2的补,1的补和整型类型的带符号的大小表示。例结束]
8、有三种浮点类型:float、double和long double。类型double提供至少与float一样高的精度,而long double至少提供与double一样高的精度。类型float的值的集合是类型doube的值的集合的子集;类型double的值的集合是long double类型的值的集合的一个子集。浮点类型的值表示是实现定义的。整型和浮点类型总称为算术类型。标准模板std::numeric_limits(18.3)的特化,对于一个实现来说,应该指定每种算术类型的最大和最小值。
9、void类型有一个空集值。void类型是一个不完整的类型,从而不会被完成。它被用作为不返回一个值的函数的返回类型。任一表达式可以被显式地转为类型cv void(5.4)。类型void的一个表达式应该仅被用作为一条表达式语句(6.2),一个逗号表达式的一个操作符(5.18),?:的第二个或第三个操作数(5.16),typeid或decltype的操作数,对于一个返回类型void的函数的一条返回语句中的表达式(6.6.3),或是作为转为类型cv void的一个显式转换的操作数。
10、类型std::nullptr_t的一个值是一个空指针常量(4.10)。这样的值参与指针和指向成员的指针的转换(4.10,4.11)。sizeof(std::nullptr_t)应该等于sizeof(void*)。
11、[注:即使实现定义了两个或更多的基本类型具有相同的值表示,它们仍然是不同的类型。——注结束]
3.9.2 复合类型
1、复合类型可以通过以下方式被构造:
——一个给定类型的对象数组,8.3.4;
——函数,它含有给定类型的形参以及返回void或一个给定类型的引用或对象,8.3.5;
——指向void类型或一个给定类型的对象或函数(包括类的静态成员)的指针,8.3.1;
——一个给定类型的对象或函数的引用,8.3.2。有两种引用类型:
——左值引用
——右值引用
——包含一列各种类型的对象(条款9),一组类型,枚举和操作这些对象的函数(9.3)的类,并且含有一组访问这些实体的限制(条款11)
——联合体,它们是在不同时间含有不同对象能力的类,9.5;
——枚举,它们由一组命名的常量值组成。每个独立的枚举构成了一个不同的枚举类型,7.2。
——指向非静态类型成员的指针[注:静态类成员是对象或函数,而指向它们的指针是指向对象或函数的普通指针。],它们标识了在一个给定类的对象内的一个给定类型的成员,8.3.3。
2、这些构建类型的方法可以被递归地应用;在8.3.1、8.3.4、8.3.5和8.3.2中提到了限制。
3、一个指向void的指针类型或指向一个对象的指针类型被称为一个对象指针类型。[注:一个指向void的指针并不返回一个指向对象的指针类型,由于void并不是一个对象。——注结束]可以指派一个函数的一个指针的类型被称为一个函数指针类型。一个指向类型T的对象的一个指针被称作为“指向T的指针”。[例:一个指向类型int的一个对象的指针被称为“指向int的指针”,而一个指向类X的一个对象的指针被称为一个“指向X的指针”。——例结束]除了指向静态成员的指针,本文所谓的“指针”并不应用于指向成员的指针。指向不完整类型的指针被允许,不过对于用它们能做的事情是有所限制的(3.11)。一个对象指针类型的一个有效值要么表示存储器中一个字节的地址(1.7),要么表示一个空指针(4.10)。如果类型T的一个对象被放在地址A处,一个指向类型cv T*的指针(其值为地址A)被称为指向那个对象的指针,而无论这个值是如何被获得的。[注:比如,一个对象的地址越过了一个数组的末尾(5.7)会被认为是指向了一个可能处于那个地址的具有那个数组元素类型的一个不相关的对象。对于指向动态存储周期的对象的指针还有进一步的限制;见3.7.4.3。——注结束]指针类型的值表示是实现定义的。指向布局兼容的cv限定和cv非限定版本(3.9.3)的指针类型应该具有相同的值表示和对齐要求(3.11)。[注:指向过对齐类型[译者注:比如需要指向一个16字节对齐的对象(可能是数组或一个结构体等)。而这种类型可能有编译器扩充类型,像__m128、__m256等等](3.11)的指针没有特别的限制,不过它们的有效值表示的范围被扩充的对齐要求所限制。本国际标准仅指定了获得这么一个指针的两种方式:获取带有一个过对齐类型的一个有效对象的地址,以及使用一种运行时指针对齐函数。一个实现可以提供其它获得一个过对齐类型的一个有效指针值的方法。——注结束]
4、一个指向cv限定的(3.9.3)或cv非限定的void的指针可以被用于指向未知类型的对象的指针。这样的一个指针应该能够持有持有任一对象指针。一个cv void*类型的对象应该具有与cv char*相同的值表示和对齐要求。
3.9.3 CV限定
1、在3.9.1和3.9.2中提到的类型是一个cv限定类型。每种cv非限定的完整或不完整的对象类型或void(3.9)有三个其类型相应的cv限定版本:一个const-qualified版本,一个volatile-qualified版本,和一个const-volatile-qualified版本。术语对象类型(1.8)包括当对象被创建时指定的cv限定符。在一个decl-specifier-seq中出现的一个const指定符声明了const-qualified对象类型的一个对象;这样的对象被称为是一个const对象。在一个decl-specifier-seq中出现的一个volatile指定符声明了一个volatile-qualified对象类型的对象;这样的对象被称为一个volatile对象。在一个decl-specifier-seq中同时出现cv-qualifier声明了一个const-volatile-qualified对象类型的对象;这样的对象被称为是一个const volatile对象。一个类型的cv限定或cv非限定版本是不同的类型;然而,它们应该具有相同的表示和对齐要求(3.9)[注:相同的表示和对齐要求意味着作为函数的实参、函数的返回值,以及联合体的非静态数据成员的可互换性。]
2、一个复合类型(3.9.2)不被其子类型的cv限定符(如果有)所cv限定(该复合类型由这些子类型所组成)。任一应用于一个数组类型的cv限定符影响数组元素类型,而不是数组类型(8.3.4)。
3、一个被const限定的类对象的每个非静态、非mutable、非引用数据成员是被const限定的;一个被volatile限定的类对象的每个非静态、非引用数据成员是被volatile限定的;而对于一个被const-volatile限定的类的成员类似。见8.3.5和9.3.2注意具有cv限定符的函数类型。
4、在cv限定符上有一个部分优先次序,这样一个类型可以被称为比另一个更被cv限定。下表展示了构成该次序的关系:
无cv限定符 < const
无cv限定符 < volatile
无cv限定符 < const volatile
const < const volatile
volatile < const volatile
5、在本国际标准中,在类型的声明中所使用的记号cv(或cv1、cv2等等)表示cv限定符的任意集合,即{const}、{volatile}、{const, volatile}的其中之一或空集。应用于依附于基本元素类型的一个数组的类型的cv限定符,该记号为“cv T”,而T是一个数组类型,引用其元素被这样cv限定的一个数组。这样的数组类型可以被称为是比起其它基于基本元素类型的cv限定的类型更多(或更少)被cv限定的。