《C++ Primer》(第5版)的章节,主要讲解了 聚合类(Aggregate Class) 和 字面值常量类(Literal Class),特别是与 constexpr 构造函数相关的知识。下面我将为你逐节详解、补充说明、提供完整示例代码和更多案例,帮助你深入理解这些概念。
📘 7.5.5 聚合类(Aggregate Class)
✅ 定义
聚合类是一种特殊的类,它允许用户使用花括号初始化列表直接初始化其成员变量,而无需显式调用构造函数。
🔍 成为聚合类的四个条件:
- 所有成员都是 public 的
- 没有定义任何构造函数(包括默认构造函数)
- 没有类内初始值(即不能在声明时给成员赋初值)
- 没有基类,也没有 virtual 函数
⚠️ 注意:从 C++11 开始,如果类有默认构造函数但未被用户定义(编译器自动生成),仍可能是聚合类;但如果用户自定义了构造函数(即使是空的),就不再是聚合类。
💡 示例:合法的聚合类
struct Data {
int ival;
string s;
};
✅ 符合所有条件 → 是聚合类
初始化方式:
Data val1 = { 0, "Anna" }; // 正确:按顺序初始化
// 等价于:
// val1.ival = 0;
// val1.s = string("Anna");
Data val2 = { "Anna", 1024 }; // ❌ 错误!类型不匹配且顺序错乱
📌 初始化顺序必须与成员声明顺序一致!
⚠️ 缺点(书中提到三点):
- 要求所有成员是 public → 破坏封装性。
- 初始化责任交给用户 → 容易出错或遗漏。
- 添加/删除成员后需更新所有初始化语句 → 维护成本高。
🧩 更多案例:
案例1:带数组的聚合类
struct Point {
int x;
int y;
int z[3];
};
Point p = {1, 2, {3, 4, 5}}; // 合法
案例2:嵌套聚合类
struct Inner {
int a;
double b;
};
struct Outer {
Inner inner;
char c;
};
Outer o = {{10, 3.14}, 'X'}; // 合法
案例3:非聚合类(违反规则)
class NonAggregate {
public:
int x;
private:
int y; // ❌ 不是所有成员 public
};
class WithConstructor {
public:
int x;
WithConstructor() {} // ❌ 定义了构造函数
};
class WithBase {
public:
int x;
};
class Derived : public WithBase { // ❌ 有基类
int y;
};
📘 7.5.6 字面值常量类(Literal Class)
✅ 定义
字面值常量类是指可以在编译期求值的类对象,常用于 constexpr 上下文(如数组大小、模板参数等)。
🔍 成为字面值常量类的条件:
如果一个类不是聚合类,但满足以下所有条件,则它是字面值常量类:
- 所有数据成员必须是字面值类型(如 int, double, pointer, 或其他字面值常量类)
- 至少有一个 constexpr 构造函数
- 如果有类内初始值,必须是常量表达式(或使用 constexpr 构造函数初始化)
- 析构函数必须是默认定义的(不能自定义)
📝 注:聚合类如果所有成员是字面值类型,也自动成为字面值常量类。
💡 示例:字面值常量类 + constexpr 构造函数
class Debug {
public:
constexpr Debug(bool h = true, bool i = false, bool o = false)
: hw(h), io(i), other(o) {}
constexpr bool any() const { return hw || io || other; }
void set_hw(bool b) { hw = b; } // 非 constexpr 成员函数
void set_io(bool b) { io = b; }
void set_other(bool b) { other = b; }
private:
bool hw; // 硬件错误
bool io; // IO 错误
bool other; // 其他错误
};
使用场景:
constexpr Debug d1(true, false, true); // 编译期创建
constexpr bool hasError = d1.any(); // 编译期计算
int arr[d1.any() ? 10 : 5]; // 合法:用于数组大小
✅
constexpr构造函数必须能在编译期执行,所以它的函数体通常为空或只包含简单赋值。
🧩 更多案例:
案例1:简单的字面值常量类
class Rational {
public:
constexpr Rational(int n = 0, int d = 1)
: num(n), den(d > 0 ? d : 1) {}
constexpr int numerator() const { return num; }
constexpr int denominator() const { return den; }
private:
int num;
int den;
};
constexpr Rational r(3, 4);
constexpr int n = r.numerator(); // 3
constexpr int d = r.denominator(); // 4
案例2:含指针的字面值常量类(C++11起支持)
class StringView {
public:
constexpr StringView(const char* p, size_t len)
: ptr(p), length(len) {}
constexpr const char* data() const { return ptr; }
constexpr size_t size() const { return length; }
private:
const char* ptr;
size_t length;
};
constexpr StringView sv("Hello", 5);
constexpr size_t len = sv.size(); // 5
⚠️ 注意:虽然指针本身是字面值类型,但指向的内容不一定能在编译期访问(除非是字符串字面量)。
🔄 constexpr 构造函数的特殊规则:
- 不能有返回语句(因为构造函数无返回值)
- 唯一可执行语句是成员初始化列表中的赋值
- 可以重载,也可以有默认参数
- 可以是 default 或删除形式
class Empty {
public:
constexpr Empty() = default; // 合法
};
class NoCopy {
public:
constexpr NoCopy() = delete; // 合法,但不能实例化
};
🎯 综合对比表:
| 特性 | 聚合类 | 字面值常量类(非聚合) |
|---|---|---|
| 是否需要构造函数 | ❌ 不允许 | ✅ 必须有 constexpr 构造函数 |
| 成员可见性 | ✅ 全部 public | 任意(public/private均可) |
| 是否支持类内初始值 | ❌ 不允许 | ✅ 允许(必须是常量表达式) |
| 是否有基类/virtual | ❌ 不允许 | ✅ 允许(但析构函数必须默认) |
| 初始化方式 | {} 列表 | 构造函数调用 |
| 是否可用于 constexpr | ✅ 若成员是字面值类型 | ✅ 必须满足所有条件 |
🧪 实战练习:判断下列类是否为聚合类 / 字面值常量类
// A
struct A {
int x;
double y;
};
// B
class B {
public:
int x;
B() {} // 自定义构造函数
};
// C
class C {
public:
constexpr C(int v) : val(v) {}
constexpr int get() const { return val; }
private:
int val;
};
// D
struct D {
int x = 10; // 类内初始值
int y;
};
// E
class E {
public:
virtual ~E() {} // virtual 析构函数
int x;
};
✅ 答案:
- A:✅ 聚合类 + 字面值常量类(成员是字面值类型)
- B:❌ 不是聚合类(有构造函数),也不是字面值常量类(无 constexpr 构造函数)
- C:❌ 不是聚合类(有构造函数),✅ 是字面值常量类(满足所有条件)
- D:❌ 不是聚合类(有类内初始值),❌ 不是字面值常量类(无 constexpr 构造函数)
- E:❌ 不是聚合类(有 virtual 函数),❌ 不是字面值常量类(析构函数非默认)
📚 延伸知识点(书中未详述但重要):
1. C++17 对聚合类的扩展:
- 允许有基类(只要基类也是聚合类)
- 允许有类内初始值(但仍不能使用
{}初始化,需用构造函数)
→ 实际上 C++17 放宽了部分限制,但核心思想不变。
2. std::aggregate_initialization(C++20):
标准库提供了工具来检测一个类型是否是聚合类:
#include <type_traits>
static_assert(std::is_aggregate_v<Data>, "Data should be aggregate");
3. constexpr 与 consteval(C++20):
consteval:强制函数必须在编译期求值constexpr:可在编译期或运行期求值
✍️ 总结
- 聚合类适合简单数据结构,便于批量初始化,但牺牲封装性和安全性。
- 字面值常量类用于编译期计算,提升性能和安全,常用于元编程、配置、数学库等。
constexpr构造函数是实现字面值常量类的关键,需严格遵守语法规则。- 实际开发中,优先使用构造函数+封装,仅在必要时使用聚合类或字面值常量类。
如果你希望我继续讲解后续章节(如移动语义、智能指针、STL容器等),或者想针对某个例子做更深入剖析,欢迎随时告诉我!📚💻
关注我,后面更精彩。
 聚合类(Aggregate Class) 和 字面值常量类(Literal Class)&spm=1001.2101.3001.5002&articleId=158347292&d=1&t=3&u=99210a9327d14801a3a181986c60b268)
268

被折叠的 条评论
为什么被折叠?



