C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
联合体声明
联合体是特殊的类类型,它在一个时刻只能保有其一个非静态数据成员。
联合体声明的类说明符与类或结构体的声明相似:
union attr 类头名 { 成员说明 }
|
|||||||||
attr(C++11) | - | 任何数量属性的可选序列 |
类头名 | - | 被定义的联合体的名字。可选地前附 嵌套名说明符(名字与作用域解析运算符的序列,以作用域解析运算符结尾)。可忽略名字,该情况下联合体为无名 |
成员说明 | - | 访问说明符、成员对象和成员函数的声明与定义的列表。 |
联合体可拥有成员函数(包含构造函数和析构函数),但不能有虚函数。
联合体不能有基类且不能用作基类。
联合体不能拥有引用类型的非静态数据成员。
(C++11 前) | |
若联合体含有带非平凡特殊成员函数(复制/移动构造函数,复制/移动赋值,或析构函数)的非静态数据成员,则联合体中的该函数默认被弃置,且需要程序员显式定义它。 若联合体含有带非平凡默认构造函数的非静态数据成员,则该联合体的默认构造函数默认被弃置,除非联合体的变体成员拥有一个默认成员初始化器。 |
(C++11 起) |
正如结构体的声明中一般,联合体的默认成员访问是 public。
解释
联合体的大小仅足以保有其最大的数据成员。其他数据成员分配于该最大成员的一部分相同的字节。分配的细节是实现定义的,且从并非最近写入的联合体成员进行读取是未定义行为。许多编译器作为非标准语言扩展,实现读取联合体的不活跃成员的能力。
#include <iostream> #include <cstdint> union S { std::int32_t n; // 占用 4 字节 std::uint16_t s[2]; // 占用 4 字节 std::uint8_t c; // 占用 1 字节 }; // 整个联合体占用 4 字节 int main() { S s = {0x12345678}; // 初始化首个成员,s.n 现在是活跃成员 // 于此点,从 s.s 或 s.c 读取是未定义行为 std::cout << std::hex << "s.n = " << s.n << '\n'; s.s[0] = 0x0011; // s.s 现在是活跃成员 // 在此点,从 n 或 c 读取是 UB 但大多数编译器都对其有定义 std::cout << "s.c is now " << +s.c << '\n' // 11 或 00,取决于平台 << "s.n is now " << s.n << '\n'; // 12340011 或 00115678 }
可能的输出:
s.n = 12345678 s.c is now 0 s.n is now 115678
各个成员都如同它是类的仅有成员一样进行分配。
若联合体的成员是拥有用户定义的构造函数和析构函数的类,则为了切换其活跃成员,通常需要显式析构函数和布置 new: 运行此代码 #include <iostream> #include <string> #include <vector> union S { std::string str; std::vector<int> vec; ~S() {} // 需要知道哪个成员活跃,仅在联合体式的类中可行 }; // 整个联合体占有 max(sizeof(string), sizeof(vector<int>)) 的内存 int main() { S s = {"Hello, world"}; // 在此点,从 s.vec 读取是未定义行为 std::cout << "s.str = " << s.str << '\n'; s.str.~basic_string(); new (&s.vec) std::vector<int>; // 现在,s.vec 是联合体的活跃成员 s.vec.push_back(10); std::cout << s.vec.size() << '\n'; s.vec.~vector(); } 输出: s.str = Hello, world 1 |
(C++11 起) |
若两个联合体成员均为标准布局类型,则在任何编译器上检验其公共子序列都是良好定义的。
成员生存期
联合体成员的生存期始于该成员被设为活跃(active)时。若另一成员先前为活跃,则其生存期终止。
当联合体的活跃成员通过形式为 E1 = E2
的复制表达式(使用内建赋值运算符或平凡的赋值运算符)所切换时,对于 E1
中的各个成员访问和数组下标子表达式中出现的,其类型并非拥有非平凡或弃置的默认构造函数的类的每个联合体成员 X,若 X 的修改在类型别名使用规则下会具有未定义行为,则在所指名的存储中隐式创建一个 X 类型的对象;不进行初始化,且其生存期的开始按顺序晚于其左右的操作数的值计算,而早于赋值。
union A { int x; int y[4]; }; struct B { A a; }; union C { B b; int k; }; int f() { C c; // 不开始任何联合体成员的生存期 c.b.a.y[3] = 4; // OK:"c.b.a.y[3]" 指名联合体成员 c.b 与 c.b.a.y; // 这创建对象以保有联合体成员 c.b 和 c.b.a.y return c.b.a.y[3]; // OK:c.b.a.y 指代新创建的对象 } struct X { const int a; int b; }; union Y { X x; int k; }; void g() { Y y = { { 1, 2 } }; // OK,y.x 为活跃联合体成员 (9.2) int n = y.x.a; y.k = 4; // OK:结束 y.x 的生存期,y.k 是联合体的活跃成员 y.x.b = n; // 未定义行为:y.x.b 在其生存期外被修改, // "y.x.b" 指名 y.x,但 X 的默认构造函数被弃置, // 故联合体成员 y.x 的生存期不会隐式开始 }
匿名联合体
匿名联合体是不同时定义任何变量(包括联合体类型的对象、引用或指向联合体的指针)的无名的联合体定义。
union { 成员说明 } ;
|
|||||||||
匿名联合体有更多限制:它们不能有成员函数,不能有静态数据成员,且所有数据成员必须为公开。所允许的声明仅有非静态数据成员和 static_assert 声明 (C++14 起)。
匿名联合体的成员被注入到其外围作用域中(而且必须不与其中声明的其他名字冲突)。
int main() { union { int a; const char* p; }; a = 1; p = "Jennifer"; }
命名空间作用域的匿名联合体必须声明为 static,除非它们出现于无名命名空间。
联合体式的类
联合体式的类(union-like class)是至少拥有一个匿名联合体为其成员的(非联合)类,或者是联合体。联合体式的类拥有一组变体成员(variant member):
- 其成员匿名联合体的非静态数据成员;
- 另外,若联合体式的类是联合体,则为其并非匿名联合体的非静态数据成员。
联合体式的类可用于实现带标签联合体(tagged union)。
#include <iostream> // S 拥有一个非静态数据成员(tag),三个枚举项成员(CHAR、INT、DOUBLE), // 和三个变体成员(c、i、d) struct S { enum{CHAR, INT, DOUBLE} tag; union { char c; int i; double d; }; }; void print_s(const S& s) { switch(s.tag) { case S::CHAR: std::cout << s.c << '\n'; break; case S::INT: std::cout << s.i << '\n'; break; case S::DOUBLE: std::cout << s.d << '\n'; break; } } int main() { S s = {S::CHAR, 'a'}; print_s(s); s.tag = S::INT; s.i = 123; print_s(s); }
输出:
a 123
C++ 标准库包含 std::variant,它可取代联合体和联合体式的类的大多数用途。上例可重写为 运行此代码 #include <variant> #include <iostream> int main() { std::variant<char, int, double> s = 'a'; std::visit([](auto x){ std::cout << x << '\n';}, s); s = 123; std::visit([](auto x){ std::cout << x << '\n';}, s); } 输出: a 123 |
(C++17 起) |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1940 | C++14 | 匿名联合体仅允许非静态数据成员 | 亦允许 static_assert |