当前位置: 首页 > news >正文

c++类派生2

一、派生类与基类的构造函数关系

构造函数的作用是初始化对象的成员。派生类对象包含基类子对象派生类新增子对象两部分,因此派生类构造时必须先初始化基类子对象,再初始化自身新增成员。

核心规则

  1. 默认行为:派生类的构造函数(无论无参还是有参),默认自动调用基类的无参构造函数
  2. 显式调用有参构造:如果基类没有无参构造(仅定义了有参构造),或者需要用特定参数初始化基类成员,必须在派生类构造函数的初始化列表中显式调用基类的有参构造函数。
  3. 执行顺序基类构造函数 → 派生类成员对象的构造函数(若有) → 派生类构造函数体

示例代码

#include <iostream> #include <string> using namespace std; // 基类:人 class Person { protected: string name; int age; public: // 基类无参构造 Person() : name("未知"), age(0) { cout << "Person 无参构造函数执行" << endl; } // 基类有参构造 Person(string n, int a) : name(n), age(a) { cout << "Person 有参构造函数执行:" << name << ", " << age << endl; } }; // 派生类:学生 class Student : public Person { private: string id; // 学号(派生类新增成员) public: // 1. 派生类无参构造:默认调用基类无参构造 Student() : id("000000") { cout << "Student 无参构造函数执行" << endl; } // 2. 派生类有参构造:显式调用基类有参构造初始化继承的成员 Student(string n, int a, string i) : Person(n, a), id(i) { cout << "Student 有参构造函数执行:学号" << id << endl; } void show() { cout << "姓名:" << name << ",年龄:" << age << ",学号:" << id << endl; } }; int main() { cout << "===== 创建无参学生对象 =====" << endl; Student s1; s1.show(); cout << "\n===== 创建有参学生对象 =====" << endl; Student s2("张三", 18, "2024001"); s2.show(); return 0; }

输出结

关键说明

  • 如果基类只定义了有参构造(没有无参构造),派生类构造函数必须在初始化列表显式调用基类有参构造,否则编译报错。
  • 初始化列表的执行顺序只与成员声明顺序有关,与初始化列表中书写顺序无关(基类构造永远先于派生类)。

二、派生类与基类的析构函数关系

析构函数的作用是释放对象占用的资源,其执行顺序与构造函数完全相反:先清理派生类自身的资源,再清理基类子对象的资源。

核心规则

  1. 派生类的析构函数执行完毕后,自动调用基类的析构函数
  2. 析构函数没有参数,因此无法重载,也不需要在派生类析构中显式调用基类析构(编译器自动完成)。
  3. 重要:当用基类指针 / 引用指向派生类对象时,必须将基类析构函数声明为virtual(虚析构函数),否则删除对象时只会调用基类析构,导致派生类资源泄漏。

示例代码(基于上例扩展)

// 给Person和Student添加析构函数 class Person { // ... 其他成员不变 public: ~Person() { cout << "Person 析构函数执行:" << name << endl; } }; class Student : public Person { // ... 其他成员不变 public: ~Student() { cout << "Student 析构函数执行:学号" << id << endl; } }; int main() { cout << "===== 创建有参学生对象 =====" << endl; Student s("李四", 20, "2024002"); s.show(); cout << "===== 对象即将销毁 =====" << endl; return 0; // 程序结束时自动销毁局部对象 }

输出结果

===== 创建有参学生对象 ===== Person 有参构造函数执行:李四, 20 Student 有参构造函数执行:学号2024002 姓名:李四,年龄:20,学号:2024002 ===== 对象即将销毁 ===== Student 析构函数执行:学号2024002 Person 析构函数执行:李四

三、派生类与基类的拷贝构造函数关系

拷贝构造函数用于用一个已存在的对象初始化新对象,继承体系中拷贝构造的执行逻辑与普通构造类似。

核心规则

  1. 默认行为:派生类的默认拷贝构造函数,会自动调用基类的拷贝构造函数,完成基类子对象的拷贝。
  2. 显式定义派生类拷贝构造:如果手动定义派生类的拷贝构造函数,编译器默认调用基类的无参构造函数(而非拷贝构造),此时必须在初始化列表中显式调用基类的拷贝构造,否则基类成员会被默认初始化,导致数据丢失。

示例代码

#include <iostream> #include <string> using namespace std; class Person { protected: string name; int age; public: Person(string n, int a) : name(n), age(a) {} // 基类拷贝构造 Person(const Person& p) : name(p.name), age(p.age) { cout << "Person 拷贝构造执行" << endl; } void show() { cout << name << ", " << age; } }; class Student : public Person { private: string id; public: Student(string n, int a, string i) : Person(n, a), id(i) {} // 错误写法:显式定义派生类拷贝构造,未调用基类拷贝构造 // Student(const Student& s) : id(s.id) { // cout << "Student 拷贝构造执行(错误版)" << endl; // } // 正确写法:显式调用基类拷贝构造 Student(const Student& s) : Person(s), id(s.id) { cout << "Student 拷贝构造执行(正确版)" << endl; } void show() { Person::show(); cout << ", 学号:" << id << endl; } }; int main() { Student s1("王五", 19, "2024003"); cout << "原对象:"; s1.show(); cout << "\n拷贝生成新对象:" << endl; Student s2(s1); // 调用拷贝构造 cout << "新对象:"; s2.show(); return 0; }

输出结果(正确版)

原对象:王五, 19, 学号:2024003 拷贝生成新对象: Person 拷贝构造执行 Student 拷贝构造执行(正确版) 新对象:王五, 19, 学号:2024003

错误版输出对比

如果使用注释中的错误写法,输出会变成:

原对象:王五, 19, 学号:2024003 拷贝生成新对象: Student 拷贝构造执行(错误版) 新对象:, 0, 学号:2024003

可以看到基类的nameage被默认初始化(空字符串和 0),数据完全丢失。

四、派生类与基类的赋值运算符重载关系

赋值运算符重载用于对象之间的赋值操作(=),其继承规则与拷贝构造类似,但需要手动处理基类部分的赋值。

核心规则

  1. 默认行为:派生类的默认赋值运算符重载函数,会自动调用基类的赋值运算符,完成基类子对象的赋值。
  2. 显式定义派生类赋值重载:如果手动定义派生类的赋值运算符,编译器不会自动调用基类的赋值运算符,必须通过基类名::operator=显式调用,否则基类成员不会被正确赋值。
  3. 赋值重载需要遵循自赋值检查返回自身引用的通用规则。

示例代码

#include <iostream> #include <string> using namespace std; class Person { protected: string name; int age; public: Person(string n, int a) : name(n), age(a) {} // 基类赋值运算符重载 Person& operator=(const Person& p) { if (this != &p) { // 自赋值检查 name = p.name; age = p.age; cout << "Person 赋值运算符执行" << endl; } return *this; } void show() { cout << name << ", " << age; } }; class Student : public Person { private: string id; public: Student(string n, int a, string i) : Person(n, a), id(i) {} // 派生类赋值运算符重载 Student& operator=(const Student& s) { if (this != &s) { // 自赋值检查 Person::operator=(s); // 显式调用基类赋值运算符 id = s.id; // 赋值派生类新增成员 cout << "Student 赋值运算符执行" << endl; } return *this; } void show() { Person::show(); cout << ", 学号:" << id << endl; } }; int main() { Student s1("赵六", 21, "2024004"); Student s2("临时", 0, "000000"); cout << "赋值前s2:"; s2.show(); s2 = s1; // 调用赋值运算符 cout << "赋值后s2:"; s2.show(); return 0; }

输出结果

赋值前s2:临时, 0, 学号:000000 Person 赋值运算符执行 Student 赋值运算符执行 赋值后s2:赵六, 21, 学号:2024004

关键说明

如果注释掉Person::operator=(s);,赋值后 s2 的nameage仍为 "临时" 和 0,只有id被正确赋值。

五、静态成员的继承性

静态成员属于类本身,而非某个对象,存储在静态全局区,整个程序中只有一份副本。继承体系中静态成员的规则如下:

核心规则

  1. 共享性:基类的静态成员被所有派生类共享,无论派生多少层,静态成员只有一份。
  2. 访问性:派生类可以直接访问基类的公有 / 保护静态成员,访问方式为基类名::静态成员派生类名::静态成员(效果相同)。
  3. 隐藏性:派生类可以定义与基类同名的静态成员,此时派生类的静态成员会隐藏基类的同名静态成员(通过作用域解析符::仍可访问基类版本)。

示例代码

#include <iostream> using namespace std; class Base { public: static int count; // 基类静态成员变量 static void showCount() { // 基类静态成员函数 cout << "Base::count = " << count << endl; } }; int Base::count = 0; // 静态成员类外初始化 class Derived : public Base { public: static int count; // 派生类重定义同名静态成员,隐藏基类版本 static void showCount() { // 派生类重定义同名静态函数 cout << "Derived::count = " << count << endl; cout << "Base::count = " << Base::count << endl; // 显式访问基类静态成员 } }; int Derived::count = 100; // 派生类静态成员初始化 int main() { // 基类静态成员访问 Base::count = 10; Base::showCount(); // 输出 Base::count = 10 // 派生类访问基类静态成员 Derived::Base::showCount(); // 输出 Base::count = 10 cout << "通过Derived访问Base::count = " << Derived::Base::count << endl; // 派生类自身静态成员 Derived::showCount(); // 输出 Derived::count = 100 和 Base::count = 10 // 验证共享性:修改基类静态成员,所有派生类都能看到 Base::count = 20; Derived::showCount(); // 输出 Base::count = 20 return 0; }

输出结果

plaintext

Base::count = 10 Base::count = 10 通过Derived访问Base::count = 10 Derived::count = 100 Base::count = 10 Derived::count = 100 Base::count = 20

六、友元的继承性

友元的作用是让外部函数 / 类访问类的私有 / 保护成员,但友元关系不具有继承性

核心规则

  1. 基类的友元不能直接访问派生类的私有 / 保护成员,除非派生类显式声明该函数 / 类为自己的友元。
  2. 基类的友元可以访问派生类中从基类继承的成员(如果该成员在基类中是公有的,或友元通过基类指针 / 引用访问)。

示例代码

#include <iostream> #include <string> using namespace std; class Derived; // 前向声明 class Base { private: int base_private; protected: int base_protected; public: Base(int a, int b) : base_private(a), base_protected(b) {} friend void showBase(const Base& b); // 声明友元函数 }; class Derived : public Base { private: int derived_private; // 派生类私有成员 public: Derived(int a, int b, int c) : Base(a, b), derived_private(c) {} // 如果取消下面注释,友元函数就能访问derived_private // friend void showBase(const Base& b); }; // 友元函数实现 void showBase(const Base& b) { cout << "基类私有成员:" << b.base_private << endl; cout << "基类保护成员:" << b.base_protected << endl; // 错误:友元不能访问派生类的私有成员 // const Derived& d = static_cast<const Derived&>(b); // cout << "派生类私有成员:" << d.derived_private << endl; } int main() { Derived d(10, 20, 30); showBase(d); // 派生类对象可以传给基类引用,友元能访问基类部分 return 0; }

输出结果

基类私有成员:10 基类保护成员:20

如果取消注释中访问derived_private的代码,编译会报错,因为showBase是 Base 的友元,不是 Derived 的友元。

七、final 类(最终类)

final是 C++11 引入的关键字,用于修饰类或虚函数,核心作用是禁止继承或重写

核心规则

  1. final 类:在类名后加final关键字,表示该类不能被任何类继承。
  2. final 虚函数:在虚函数声明后加final,表示该虚函数不能在派生类中被重写(覆盖)。

示例代码

#include <iostream> using namespace std; // final类:不能被继承 class FinalClass final { public: void show() { cout << "这是final类的成员函数" << endl; } }; // 错误:不能继承final类 // class Derived : public FinalClass {}; class Base { public: virtual void func() final { // final虚函数:不能被重写 cout << "Base::func() final函数" << endl; } }; class Derived : public Base { public: // 错误:不能重写final虚函数 // void func() override { cout << "Derived::func()" << endl; } }; int main() { FinalClass fc; fc.show(); Base b; b.func(); return 0; }

关键说明

  • final 类通常用于设计不希望被扩展的类(比如工具类、封装好的底层类),防止错误继承破坏类的封装性。
  • final 虚函数用于锁定基类的某个行为,确保派生类不能修改该行为的实现。
http://www.jsqmd.com/news/818120/

相关文章:

  • 英文论文怎么降AI?实测从88%降至20%的5大方法(附工具实测)
  • 电子签章厂商必须要有 CA 牌照吗?—— 基于法律与行业现实的深度辨析
  • 2026 成都专业 GEO 优化公司甄选|权威测评 5 家标杆服务商 - GEO优化
  • 大模型调用效率翻倍:Token 聚合平台到底有多好用,一篇讲透
  • 开放标准如何加速多媒体设备开发:从接口契约到端到端实践
  • 终极指南:在macOS上轻松运行Windows程序的完整解决方案
  • HS2-HF Patch完全指南:为Honey Select 2打造终极游戏体验
  • LVS验证在IC设计中的关键作用与Calibre nmLVS-Recon创新方法
  • 终极指南:5分钟解锁小爱音箱完整音乐自由
  • 计算机网络八股文:高频面试题全解析
  • 26-cv-785 便携式多功能检测仪器专利维权!
  • 在Windows任务栏实时看股票:TrafficMonitor插件如何改变你的投资习惯?
  • 第十周:光电效应
  • 佛山夏令营哪家好:军博营地实力领跑 - 17322238651
  • 有没有稳定无广告的免费文档转换器?这款全能工具解决大部分办公格式难题
  • 数据运维如何搭建体系?数据运维怎样保障数据稳定?
  • 如何打造个人音乐云:Android平台的最佳Subsonic客户端DSub完全指南
  • 从零开始玩转BeagleBone Black:手把手教你配置Cloud9在线开发环境与BoneScript
  • 从 “地区 + 行业” 到 “任意组合条件”:招标采购导航网的自定义订阅语法解析
  • 线程池学习(二)线程池理解
  • 2026赣州市全南县黄金回收白银回收铂金回收店铺实力排行榜TOP5; K金+金条+银条+首饰回收靠谱门店及联系方式推荐_转自TXT - 盛世金银回收
  • 终极企业级开源方案:ArduRemoteID无人机远程识别完整解决方案
  • 【限时开放倒计时】Midjourney Pro 3.0新增AI构图辅助与多轮迭代记忆功能——首批1000席已冻结注册
  • OpenClaw Shield:为AI智能体构建纵深防御安全体系
  • 【独家首发】Midjourney官方未公开的配额继承规则:家庭共享、账号迁移、停用恢复的3个灰色地带
  • 企业如何通过Taotoken实现内部AI应用的API访问控制与审计
  • 避坑指南:ArcGIS中Landsat8水体提取,你的NDWI阈值真的设对了吗?
  • Ametal嵌入式平台实战:从零构建智能温湿度监测节点
  • 进销存表格功能拆解:如何用进销存表格解决库存积压与账目混乱难题
  • Chrome网页文本批量替换神器:告别繁琐编辑,效率提升300%