《C++ Primer》(第5版) 聚合类(Aggregate Class) 和 字面值常量类(Literal Class)

《C++ Primer》(第5版)的章节,主要讲解了 聚合类(Aggregate Class)字面值常量类(Literal Class),特别是与 constexpr 构造函数相关的知识。下面我将为你逐节详解、补充说明、提供完整示例代码和更多案例,帮助你深入理解这些概念。


📘 7.5.5 聚合类(Aggregate Class)

✅ 定义

聚合类是一种特殊的类,它允许用户使用花括号初始化列表直接初始化其成员变量,而无需显式调用构造函数。

🔍 成为聚合类的四个条件:

  1. 所有成员都是 public 的
  2. 没有定义任何构造函数(包括默认构造函数)
  3. 没有类内初始值(即不能在声明时给成员赋初值)
  4. 没有基类,也没有 virtual 函数

⚠️ 注意:从 C++11 开始,如果类有默认构造函数但未被用户定义(编译器自动生成),仍可能是聚合类;但如果用户自定义了构造函数(即使是空的),就不再是聚合类。


💡 示例:合法的聚合类

struct Data {
    int ival;
    string s;
};

✅ 符合所有条件 → 是聚合类

初始化方式:
Data val1 = { 0, "Anna" };        // 正确:按顺序初始化
// 等价于:
// val1.ival = 0;
// val1.s = string("Anna");

Data val2 = { "Anna", 1024 };     // ❌ 错误!类型不匹配且顺序错乱

📌 初始化顺序必须与成员声明顺序一致!


⚠️ 缺点(书中提到三点):

  1. 要求所有成员是 public → 破坏封装性。
  2. 初始化责任交给用户 → 容易出错或遗漏。
  3. 添加/删除成员后需更新所有初始化语句 → 维护成本高。

🧩 更多案例:

案例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 上下文(如数组大小、模板参数等)。

🔍 成为字面值常量类的条件:

如果一个类不是聚合类,但满足以下所有条件,则它是字面值常量类:

  1. 所有数据成员必须是字面值类型(如 int, double, pointer, 或其他字面值常量类)
  2. 至少有一个 constexpr 构造函数
  3. 如果有类内初始值,必须是常量表达式(或使用 constexpr 构造函数初始化)
  4. 析构函数必须是默认定义的(不能自定义)

📝 注:聚合类如果所有成员是字面值类型,也自动成为字面值常量类。


💡 示例:字面值常量类 + 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. constexprconsteval(C++20):

  • consteval:强制函数必须在编译期求值
  • constexpr:可在编译期或运行期求值

✍️ 总结

  • 聚合类适合简单数据结构,便于批量初始化,但牺牲封装性和安全性。
  • 字面值常量类用于编译期计算,提升性能和安全,常用于元编程、配置、数学库等。
  • constexpr 构造函数是实现字面值常量类的关键,需严格遵守语法规则。
  • 实际开发中,优先使用构造函数+封装,仅在必要时使用聚合类或字面值常量类。

如果你希望我继续讲解后续章节(如移动语义、智能指针、STL容器等),或者想针对某个例子做更深入剖析,欢迎随时告诉我!📚💻

关注我,后面更精彩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张工在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值