
前言面向对象三大特性之一便是继承合理使用继承可以抽取公共代码到基类减少冗余清晰表达「is-a」的业务关系。很多初学者刚接触继承时最容易踩坑的就是构造与析构执行顺序与public/protected/private访问权限本文通过完整可运行示例把底层逻辑、易错点一次性讲透。一、完整示例代码为了直观展示继承中构造、析构顺序以及访问权限的实际效果我们先来看一个完整的 C 示例。这个示例定义了一个基类Person和一个派生类Student通过运行输出可以清晰地看到对象创建和销毁时的调用顺序。示例代码#include iostream #include string using namespace std; class Person { public: Person(string name) : name_(move(name)) { cout 构造 Person name_ \n; } ~Person() { cout 析构 Person name_ \n; } void introduce() const { cout 大家好我是 name_ 。\n; } protected: string name_; }; // 公有继承 Student 是 Person class Student : public Person { public: // 初始化列表显式调用基类构造 Student(string name, int id) : Person(move(name)), id_(id) { cout 构造 Student学号 id_ \n; } ~Student() { cout 析构 Student学号 id_ \n; } void study() const { // protected 基类成员子类可直接访问 cout name_ 正在学习 C。\n; } private: int id_; }; int main() { Student alice(Alice, 1001); alice.introduce(); alice.study(); return 0; }程序标准输出运行上述程序控制台输出如下构造 PersonAlice 构造 Student学号1001 大家好我是Alice。 Alice 正在学习 C。 析构 Student学号1001 析构 PersonAlice二、核心规律构造、析构执行顺序1. 构造顺序先基类后派生类创建派生类Student alice(Alice, 1001)时对象内存中会先包含一份完整的基类Person子对象执行流程调用基类Person构造函数初始化name_执行派生类Student构造函数初始化独有成员id_构造完成对象可正常调用所有成员函数。关键强制规则如果基类没有无参默认构造函数派生类必须在初始化列表显式调用基类构造否则直接编译报错。C运行Student(std::string name, int id) : Person(std::move(name)), id_(id) {}2. 析构顺序先派生类后基类对象生命周期结束销毁时顺序与构造完全相反先执行Student析构释放子类独有资源再执行Person析构清理基类资源。设计目的子类销毁时基类成员依然有效不会出现访问已销毁数据的非法访问。三、三种访问修饰符 public /protected/private示例中name_使用protected专门为继承场景设计三者权限对比表格修饰符类内部访问派生类访问外部代码访问public✅ 允许✅ 允许✅ 允许protected✅ 允许✅ 允许❌ 禁止private✅ 允许❌ 禁止❌ 禁止场景解读protected专为继承设计基类数据不想对外暴露但允许子类直接读写示例中study()可以直接打印name_private标准工程推荐写法生产环境不会把成员设为protected一般私有成员搭配protected读写接口供子类使用更好管控数据修改public仅对外接口函数使用原始数据极少直接公开。四、高频易错点 面试考点1. 误区以为先构造子类再构造父类完全颠倒固定规则构造自上而下基→派析构自下而上派→基面试高频手写输出题考点。2. 忘记初始化无默认构造的基类本例Person只有带参构造若子类初始化列表删除Person(std::move(name))编译器找不到合法基类构造直接抛出编译错误。3. 混淆继承与代码复用继承代表「is-a」语义Student 是 Person、Teacher 是 Person。 单纯想复用代码、无所属关系优先使用组合把对象作为成员变量避免继承带来高耦合。4. 现阶段无需纠结虚析构本文只讲基础生命周期暂不涉及多态后续学习基类指针指向派生类场景时再补充virtual虚析构知识点否则会出现内存泄漏。五、课后实操练习题拓展类新建Teacher公有继承Person新增私有课程名称course_实现构造与打印课程函数功能补充给Student添加printId()成员仅输出学号报错实验删除子类初始化列表里的Person(std::move(name))观察编译器报错顺序思考给Student添加string major_专业成员思考该成员、基类name_谁先构造。参考答案以下是第1题和第4题的简要解答1. Teacher 类实现// Teacher.h 或直接在 main.cpp 中定义 class Teacher : public Person { public: Teacher(std::string name, std::string course) : Person(std::move(name)), course_(std::move(course)) { std::cout 构造 Teacher课程 course_ \n; } ~Teacher() { std::cout 析构 Teacher课程 course_ \n; } void teach() const { std::cout name_ 正在教授 course_ 课程。\n; } private: std::string course_; }; // 使用示例 int main() { Teacher bob(Bob, C程序设计); bob.introduce(); // 继承自 Person bob.teach(); // Teacher 特有 return 0; }要点说明Teacher公有继承Person符合 Teacher is a Person 的语义。构造函数初始化列表中必须显式调用基类Person的构造函数。私有成员course_存储课程名称通过teach()函数输出。由于name_在基类中声明为protected派生类可以直接访问。4. 构造顺序思考题给Student添加string major_专业成员后构造顺序如下class Student : public Person { public: Student(std::string name, int id, std::string major) : Person(std::move(name)), id_(id), major_(std::move(major)) { // 构造函数体 } private: int id_; std::string major_; };构造顺序解析基类成员优先首先调用基类Person构造函数初始化基类成员name_。派生类成员按声明顺序然后按派生类中成员变量的声明顺序初始化。如果声明顺序是int id_;在前string major_;在后则先初始化id_再初始化major_。初始化列表顺序不影响实际初始化顺序实际初始化顺序只取决于成员在类中的声明顺序与初始化列表中写的顺序无关。关键结论基类成员name_总是最先构造然后才是派生类的成员id_和major_按声明顺序。六、核心总结继承核心价值抽取子类公共属性、行为到基类消除重复代码清晰表达「is-a」关系生命周期铁律构造先基类后派生析构先派生后基类权限选择子类要访问但不对外暴露的成员用protected工程开发数据优先private派生类若基类无默认构造初始化列表必须显式调用基类构造函数。