【C++】继承详解——基类/派生类、作用域、默认函数、菱形继承(超详细)
文章目录一、继承开篇二、继承的概念及定义1. 继承是什么2. 继承定义格式3. 继承后成员访问权限变化超级重要三、基类和派生类的赋值转换切片/切割四、继承中的作用域隐藏 / 重定义1. 成员变量隐藏2. 成员函数隐藏五、派生类的默认成员函数超级重点1. 构造函数2. 拷贝构造3. 赋值重载4. 析构函数5. 写一个不能被继承的类六、继承与友元七、继承与静态成员八、多继承与菱形继承C最复杂的点1. 概念2. 菱形继承的两大问题3. 虚继承解决菱形继承问题九、继承与组合面试高频1. 继承白箱复用2. 组合黑箱复用十、继承总结一、继承开篇前面我们学了类和对象、模板今天我们进入面向对象最核心的特性之一继承继承是类层次的复用可以让我们在不改动原有类的基础上扩展新功能是C面试、工程开发必学知识点。如果还没学过类与对象、封装建议先看前面的文章打牢基础再来学继承会轻松很多本篇内容非常细我会一一把继承概念、定义、赋值转换、作用域隐藏、默认成员函数、友元、静态、多继承、菱形继承、虚继承、继承与组合全部讲透二、继承的概念及定义1. 继承是什么继承就是在已有类的基础上扩展属性和函数生成新的类。原来的类基类 / 父类新的类派生类 / 子类没有继承之前我们写Student和Teacher会出现大量重复代码姓名、地址、电话、身份认证函数全都要写两遍非常冗余而有了继承我们就可以把公共部分抽到Person让Student和Teacher继承它就能直接复用不用重复写定义如下classPerson{public:voididentity(){cout身份认证: _nameendl;}protected:string _name张三;string _address;string _tel;int_age18;};// Student 公有继承 PersonclassStudent:publicPerson{public:voidstudy(){}protected:int_stuid;// 学号};// Teacher 公有继承 PersonclassTeacher:publicPerson{public:voidteaching(){}protected:string _title;// 职称};然后我们就可以直接使用intmain(){Student s;Teacher t;s.identity();// 直接用父类的函数t.identity();return0;}这就是继承最直观的作用复用代码减少冗余。2. 继承定义格式最常用的是公有继承class派生类:继承方式 基类继承方式有三种public最常用protectedprivate3. 继承后成员访问权限变化超级重要这张表一定要背下来基类成员public继承protected继承private继承publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可见不可见不可见我给你总结成最简单的口诀基类 private 成员无论怎么继承在子类都不可见访问权限 取更小的那个public protected private实际开发大部分用public 继承另外两种几乎不用示例如下classPerson{public:voidPrint(){cout_nameendl;}protected:string _name;private:int_age;};// 公有继承classStudent:publicPerson{protected:int_stunum;};Print()在子类里是public_name在子类里是protected_age在子类里不可见三、基类和派生类的赋值转换切片/切割public 继承下派生类对象可以赋值给基类对象/指针/引用这叫切片 / 切割把派生类里“属于基类”的那部分切出来用示例类如下classPerson{protected:string _name;string _sex;int_age;};classStudent:publicPerson{public:int_No;};切片的使用如下intmain(){Student sobj;// 子类对象 → 父类指针/引用/对象 都可以Person*ppsobj;Personrpsobj;Person pobjsobj;// 父类 → 子类 不行// sobj pobj; 报错return0;}记住子类可以自动转父类父类不能转子类这在后面的多态很重要我们先暂时把切片介绍到这里到多态我们再详细讲解切片的作用四、继承中的作用域隐藏 / 重定义继承里有一个超级重要的规则同名隐藏。规则基类和子类是两个独立作用域同名成员变量/函数会构成隐藏子类会屏蔽父类的同名成员直接访问只会访问到子类的成员想访问父类同名成员必须加类名::1. 成员变量隐藏classPerson{protected:string _name小凡;int_num111;// 身份证号};classStudent:publicPerson{public:voidPrint(){cout姓名:_nameendl;cout身份证:Person::_numendl;// 必须指定类域cout学号:_numendl;}protected:int_num999;// 学号和父类同名构成隐藏};这里父类和子类都有_num成员那么就会构成隐藏默认访问子类的_num而不能直接访问到父类的_num除非使用访问时加::2. 成员函数隐藏只要函数名相同就构成隐藏不管参数这也是最容易踩的坑classA{public:voidfunc(){coutfunc()endl;}};classB:publicA{public:voidfunc(inti){coutfunc(int)iendl;}};这里父类和子类都有func函数虽然参数不同但是还是构成了隐藏默认调用子类如果你的本意不是这样就注意不要让父类和子类的成员函数名相同上述代码的调用结果B b;b.func(10);// 正确// b.func(); // 报错父类func被隐藏了b.A::func();// 必须加类域才能访问结论继承体系里尽量不要写同名成员 除非你本意就是要隐藏父类成员函数五、派生类的默认成员函数超级重点子类的6个默认成员函数必须调用父类对应的函数。我给你总结最核心的7条子类构造必须先调用父类构造子类拷贝构造必须先调用父类拷贝构造子类赋值重载必须先调用父类赋值重载子类析构自动调用父类析构不用我们写构造顺序先父后子析构顺序先子后父析构函数名会被编译器统一处理不加virtual就构成隐藏1. 构造函数如果父类没有默认构造子类必须在初始化列表显式调用。classPerson{public:Person(constchar*name):_name(name){coutPerson()endl;}protected:string _name;};classStudent:publicPerson{public:// 必须初始化列表调用父类构造Student(constchar*name,intnum):Person(name),_num(num){coutStudent()endl;}protected:int_num;};2. 拷贝构造Student(constStudents):Person(s)// 切片调用父类拷贝构造,_num(s._num){coutStudent拷贝构造endl;}3. 赋值重载Studentoperator(constStudents){if(this!s){Person::operator(s);// 必须指定类域否则隐藏_nums._num;}return*this;}4. 析构函数子类析构结束后自动调用父类析构我们不用写。~Student(){cout~Student()endl;// 自动调用 ~Person()}5. 写一个不能被继承的类两种方法C98把父类构造私有子类构造无法调用就不能实例化C11final 关键字// C11 最简单classBasefinal{};// 报错无法继承final类// class Derive : public Base// {};六、继承与友元友元关系不能继承父类的友元函数不能访问子类的私有/保护成员classPerson{public:friendvoidDisplay(constPersonp,constStudents);protected:string _name;};classStudent:publicPerson{protected:int_stuNum;};voidDisplay(constPersonp,constStudents){coutp._nameendl;// cout s._stuNum endl; 报错访问不到}想访问必须再把Display声明为Student的友元。七、继承与静态成员基类的 static 成员整个继承体系只有一份所有子类和父类共享同一个静态变量classPerson{public:staticint_count;};intPerson::_count0;classStudent:publicPerson{};使用intmain(){Person p;Student s;// 地址相同是同一个变量coutp._countendl;couts._countendl;// 都可以访问Person::_count;Student::_count;return0;}八、多继承与菱形继承C最复杂的点1. 概念单继承一个子类只有一个父类多继承一个子类有两个或以上父类菱形继承多继承的特殊情况有共同祖先classPerson{};classStudent:publicPerson{};classTeacher:publicPerson{};// 菱形继承classAssistant:publicStudent,publicTeacher{};2. 菱形继承的两大问题数据冗余Person成员存两份二义性访问_name不知道是哪一个Assistant a;// a._name; 报错不明确可以指定类域解决二义性但解决不了冗余a.Student::_name;a.Teacher::_name;3. 虚继承解决菱形继承问题在中间层加virtual继承classStudent:virtualpublicPerson{};classTeacher:virtualpublicPerson{};这样Person成员只存一份无冗余、无二义性Assistant a;a._namepeter;// 正确注意虚继承只用来解决菱形继承平时不要乱用。九、继承与组合面试高频最后我们讲一个设计思想优先使用组合少用继承。继承is-a 关系Student is a Person组合has-a 关系Car has a Tire1. 继承白箱复用父类对子类透明耦合度高父类改子类全受影响2. 组合黑箱复用内部不可见耦合度低维护性极强优先使用示例classTire{};classCar{protected:Tire _t1;Tire _t2;Tire _t3;Tire _t4;};stack 和 vector 更适合组合不适合继承。十、继承总结继承是类层次复用减少冗余继承方式大部分只用public基类private成员在子类不可见子类可以赋值给父类切片父类不能转子类同名成员构成隐藏必须加类域访问子类默认函数必须调用父类对应函数构造先父后子析构先子后父友元不能继承static全类族共享菱形继承用虚继承解决设计优先组合少用继承那么今天关于C 继承就全部讲完了内容非常多大家多敲代码多体会。有什么不懂欢迎私信问我我会及时做出解答下一篇我们开始学习多态面向对象最后的核心知识点敬请期待吧bye~
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2611792.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!