📘 本篇聚焦 C++ 中对象生命周期的核心机制:构造函数种类、explicit 限制、资源管理规则(Rule of Five)、静态成员变量、对象内存布局等。通过十组更丰富、更实用的问题,夯实你对类的行为机制的理解。
🔁 Day 2 快速回顾
核心点 | 简要记忆 |
---|---|
运算符重载 | 让类对象支持 + - == << 等操作 |
公有继承 | 表达 is-a 关系 |
多态 | 虚函数 + 父类指针/引用 + 重写 |
抽象类 | 含纯虚函数,不能直接创建对象 |
深拷贝 | 拷贝堆资源,避免指针悬挂或重复释放 |
🔟 Day 3:构造机制与对象行为十问十答(加强版)
✅ 1. C++ 中有哪些构造函数类型?各有什么用?
类型 | 特点与用途 |
---|---|
默认构造 | 无参数,创建默认对象 |
有参构造 | 传入参数初始化成员 |
拷贝构造 | T(const T&) ,复制对象 |
移动构造 | T(T&&) ,转移资源所有权 |
📌 使用建议:一旦类管理资源,建议写全四个构造函数(再加析构,即 Rule of Five)
✅ 2. 拷贝构造函数是如何被隐式调用的?陷阱有哪些?
class MyClass {
public:
MyClass() {}
MyClass(const MyClass &); // 拷贝构造
};
MyClass a;
MyClass b = a; // 拷贝构造
📌 触发时机:对象传参/返回、用一个对象初始化另一个
⚠️ 常见陷阱:类中有指针资源时,忘记自定义拷贝构造 → 会发生浅拷贝问题(两个对象指向同一块内存)
✅ 3. 移动构造函数的意义何在?什么时候会被调用?
MyClass a;
MyClass b = std::move(a); // 移动构造
📌 右值引用参数 T&&
被用来转移资源
📌 移动比拷贝更高效,避免资源复制,尤其对 STL 非常重要
📌 推荐加上 noexcept
,以便标准库优化使用(如 vector 的 realloc)
✅ 4. explicit
构造函数解决什么问题?
class Number {
public:
explicit Number(int x);
};
Number n1 = 5; // ❌ 错误(禁用隐式转换)
Number n2(5); // ✅ 正确
📌 没有 explicit 时,构造函数可能被当作“类型转换函数”
📌 加上 explicit 可防止模糊的代码行为或误用
✅ 5. 构造函数初始化列表 VS 构造体赋值,有什么区别?
class A {
const int x;
public:
A(int val) : x(val) {} // ✅ 推荐写法
};
📌 const 成员、引用成员必须用初始化列表
📌 效率更高:直接初始化 vs 构造后再赋值
✅ 6. 实现 Rule of Five 的类 FileHandle(带 buffer 指针)
class FileHandle {
char* buffer;
public:
FileHandle();
~FileHandle();
FileHandle(const FileHandle&);
FileHandle& operator=(const FileHandle&);
FileHandle(FileHandle&&) noexcept;
FileHandle& operator=(FileHandle&&) noexcept;
};
📌 适用于类中包含 new[]
、malloc
等动态资源的场景
📌 防止资源悬挂、重复释放、浅拷贝等问题
✅ 7. 设计类 Number,含 explicit 构造并对比隐式调用效果
class Number {
public:
explicit Number(int v); // 禁止隐式 int → Number 转换
};
void print(Number n);
// print(5); ❌ 错误,不能隐式转换
// print(Number(5)); ✅ 显式构造
📌 让你的 API 更可控,防止语义模糊
📌 推荐所有单参构造函数默认加上 explicit
✅ 8. 静态计数类 Logger:记录构造与析构次数
class Logger {
static int constructed;
static int destructed;
public:
Logger();
~Logger();
static void printStats();
};
int Logger::constructed = 0;
int Logger::destructed = 0;
📌 静态成员不属于对象,整个类共享一份
📌 构造/析构中自增变量可用于统计对象生命周期情况
✅ 9. 使用 sizeof() 检查类大小与成员顺序的关系
class A { char c; int x; };
class B { int x; char c; };
std::cout << sizeof(A) << " " << sizeof(B);
📌 成员顺序会引起 padding(补齐),影响内存占用
📌 char + int
可能占 8 字节,int + char
仍可能是 8,但内存访问更高效
✅ 10. new/delete 的内部机制:谁调用构造/析构?
MyClass* p = new MyClass(); // 分配 + 调构造
delete p; // 调析构 + 释放
📌 new 调用构造函数,delete 调用析构函数
📌 如果构造函数抛异常,内存会被释放,但不会调用析构
📚 今日总结口诀
构造有五类,规则别违背;
explicit 阻隐转,类型更清晰;
列表初始快,赋值反而慢;
静态共享数,非成员慎访问;
new/delete 成对用,vtable 影响 size;
copy 要深拷,move 省资源;
成员顺序调,对齐能省钱;
析构清资源,异常要注意。
🧠 强化练习
- 实现一个
BufferPool
类,管理一个堆指针数组,实现 Rule of Five; - 用
sizeof()
分析 class 成员顺序变换下的大小对比; - 写一个类含
explicit
构造和operator int()
,观察何时被隐式转换调用; - 拓展 Logger 类,统计“当前活动对象数量”并在每次创建/销毁时输出;
🚀 Day 4 预告
虚函数表机制(vtable)、对象切割问题(slicing)、RTTI、typeid、dynamic_cast 全解析
👀 一次性搞懂 C++ 多态运行时原理!