类型
对象、引用、函数(包括函数模板特化)和表达式具有称为类型的性质,它限制了对这些实体所容许的操作,并给原本寻常的位序列提供了语义含义。
类型的分类
C++ 类型系统由以下类型组成:
- 基础类型(参阅 std::is_fundamental):
- void 类型(参阅 std::is_void);
- std::nullptr_t 类型(C++11 起) (参阅 std::is_null_pointer);
- 算术类型(参阅 std::is_arithmetic):
- 浮点类型(float、double、long double 及其 cv 限定版本)(参阅 std::is_floating_point);
- 整数类型(包括 cv 限定版本,参阅 std::is_integral):
- bool 类型;
- 字符类型:
- 窄字符类型:
- 通常字符类型(char、signed char、unsigned char)
- char8_t 类型(C++20 起)
- 宽字符类型(char16_t、char32_t、 (C++11 起)wchar_t);
- 有符号整数类型(short int、int、long int、long long int (C++11 起));
- 无符号整数类型(unsigned short int、unsigned int、unsigned long int、unsigned long long int (C++11 起));
- 复合类型(参阅 std::is_compound):
- 引用类型(参阅 std::is_reference):
-
- 到对象的左值引用类型;
- 到函数的左值引用类型;
|
(C++11 起) |
- 指针类型(参阅 std::is_pointer):
- 数组类型(参阅 std::is_array);
- 函数类型(参阅 std::is_function);
- 枚举类型(参阅 std::is_enum);
- 类类型:
- 非联合体类型(参阅 std::is_class);
- 联合体类型(参阅 std::is_union)。
对于除引用和函数以外的每个类型,类型系统还支持该类型的三个附加 cv 限定版本(const 、 volatile 及 const volatile)。
根据类型的各项性质,将它们分组到不同的类别之中:
- 对象类型是非函数类型、非引用类型且非 void 类型的(可有 cv 限定的)类型(参阅 std::is_object);
- 标量类型是(可有 cv 限定的)算术、指针、成员指针、枚举和 std::nullptr_t (C++11 起) 类型(参阅 std::is_scalar);
- 平凡类型(参阅 std::is_trivial)、 POD 类型(参阅 std::is_pod)、字面类型(参阅 std::is_literal_type)和其他类别,列于类型特征库中,或作为具名类型要求。
类型的命名
通过以下方式来声明一个指代类型的名字:
在 C++ 程序中对没有名字的类型常常需要被涉指;为此而设的语法被称为 类型标识。命名类型 T 的类型标识的语法与省略了标识符的对 T 类型的变量或函数的声明语法完全一致,但声明语法中的 声明说明符序列 被限制为 类型说明符序列 ,另外仅当类型标识出现于非模板类型别名声明的右侧时才可以定义新类型。
int* p; // 声明一个指向 int 的指针 static_cast<int*>(p); // 类型标识为 "int*" int a[3]; // 声明一个含有 3 个 int 的数组 new int[3]; // 类型标识为 "int[3]" (称作 new-类型标识) int (*(*x[2])())[3]; // 声明一个含有 2 个函数指针的数组 // 这些函数指针指向的函数返回指向(含有 3 个 int 的数组)的指针 new (int (*(*[2])())[3]); // 类型标识为 "int (*(*[2])())[3]" void f(int); // 声明一个接收 int 并返回 void 的函数 std::function<void(int)> x = f; // 类型模板形参为类型标识 "void(int)" std::function<auto(int) -> void> y = f; // 同上 std::vector<int> v; // 声明一个含有 int 的 vector sizeof(std::vector<int>); // 类型标识为 "std::vector<int>" struct { int x; } b; // 创建一个新类型并声明该类型的一个对象 b sizeof(struct{ int x; }); // 错误:不能在 sizeof 表达式中定义新类型 using t = struct { int x; }; // 创建一个新类型并声明 t 为该类型的一个别名 sizeof(static int); // 错误:存储类说明符并不是类型说明符序列的一部分 std::function<inline void(int)> f; // 错误:函数说明符也不是
声明文法的 声明符 部分在移除了名字后被称为 抽象声明符 。
类型标识可用于下列情形:
- 指定转型表达式中的目标类型;
- 作为
sizeof
、alignof
、alignas
、new
和typeid
的实参; - 在类型别名声明的右侧;
- 作为函数声明的尾随返回类型;
- 作为模板类型形参的默认实参;
- 作为模板类型形参的模板实参;
- 在动态异常规定中。
类型标识经过一些修改可用于下列情形:
本节未完成 原因:8.2[dcl.ambig.res] ,若能紧凑地总结 |
本节未完成 原因:提及并链接到 decltype 和 auto |
详述类型说明符
详述类型说明符能用于指代先前声明过的类名(类、结构体或联合体)或先前声明过的枚举名,即使该名字被非类型声明隐藏。它们亦可用于声明新的类名。
详见详述类型说明符。
静态类型
对程序进行编译时分析所得到的表达式的类型被称为表达式的静态类型。程序执行时静态类型不会更改。
动态类型
若某个泛左值表达式指代某个多态对象,则其最终派生对象的类型被称为其动态类型。
// 给定 struct B { virtual ~B() {} }; // 多态类型 struct D: B {}; // 多态类型 D d; // 最终派生对象 B* ptr = &d; // (*ptr) 的静态类型为 B // (*ptr) 的动态类型为 D
对于纯右值表达式,动态类型始终与静态类型相同。
不完整类型
下列类型是不完整类型:
下列语境都要求类型 T
完整:
- 返回类型为
T
或参数类型为T
的函数的定义或调用; -
T
类型对象的定义; -
T
类型非静态数据成员的声明; -
T
类型对象或元素类型为T
的数组的 new 表达式; - 对
T
类型泛左值实施的左值到右值转换; - 到
T
类型的隐式或显式转换; - 到
T*
或T&
类型的 标准转换、dynamic_cast
或static_cast
,不包括从空指针常量或从 void 的指针进行的转换; - 对
T
类型表达式运用的类成员访问运算符; - 对
T
类型运用的typeid
、sizeof
或alignof
运算符; - 对指向
T
的指针运用的算术运算符; - 带有基类
T
的类的定义; - 对
T
类型的左值的赋值; - 捕获
T
、T&
或T*
类型的异常的 catch 子句。
(通常在必须知道 T
的大小和布局时要求它完整。)
如果翻译单元中出现了这些情况中的任何情况,该类型的定义就必须在相同翻译单元中出现。否则不必出现。
不完整定义的对象类型可以变完整:
- 类类型(例如 class X)可在翻译单元中的一处不完整而在其之后补充完整;类型 class X 在这两处相同:
class X; // X 是不完整类型 extern X* xp; // xp 是指向不完整类型的指针 void foo() { xp++; // 非良构: X 不完整 } struct X { int i; }; // 现在 X 是完整类型 X x; void bar() { xp = &x; // OK:类型是“指向 X 的指针” xp++; // OK:X 完整 }
- 数组对象的声明类型可以是不完整类类型的数组,从而它不完整;若类类型在翻译单元中的后面完整,则该数组类型变得完整;两处的数组类型相同。
- 数组对象的声明类型可为未知边界数组,从而在翻译单元的一处不完整,并在之后变完整;两处的数组类型(“含有 T 的未知边界数组”与“含有 N 个 T 的元素数组”)不同。
未知边界数组的指针的类型,或由 typedef 声明定义为未知边界数组的类型,无法变得完整。
extern int arr[]; // arr 的类型不完整 typedef int UNKA[]; // UNKA 是不完整类型 UNKA* arrp; // arrp 是指向不完整类型的指针 UNKA** arrpp; void foo() { arrp++; // 错误:不完整类型 arrpp++; // OK:UNKA* 的大小已知 } int arr[10]; // 现在 arr 的类型完整了 void bar() { arrp = &arr; // 错误:类型不同 arrp++; // 错误:UNKA 无法变得完整 }