c++复习自存--继承
继承
继承基础
继承与派生
classBase{// ... base class members};classDerived:access-specifierBase{// ... derived class members};其中 access-specifier 可以是 public(这是最常见的,表示派生类是一个基类)、private 或 protected(表示派生类有一个基类)。
下面的继承层次结构表明,Carp 类是从 Fish 类派生而来的:
classFish// base class{// ... Fish's members};classCarp:publicFish// derived class{// ... Carp's members};阅读介绍继承的文献时,您将遇到“从…继承而来”和“从…派生而来”等术语,它们的含义相同。
同样,基类也被称为超类;从基类派生而来的类称为派生类,也叫子类。
访问限定符protected
与 public 和 private 一样,protected 也是一个访问限定符。将属性声明为 protected 的时,相当于允许派生类和友元类访问它,但禁止在继承层次结构外部(包括 main( ))访问它。
一种更好的 Fish 类设计,它使用关键字 protected 将成员属性只暴露给派生类:
0:#include<iostream>1:usingnamespacestd;2:3:classFish4:{5:protected:6:boolisFreshWaterFish;// accessible only to derived classes7:8:public:9:voidSwim()10:{11:if(isFreshWaterFish)12:cout<<"Swims in lake"<<endl;13:else14:cout<<"Swims in sea"<<endl;15:}16:};17:18:classTuna:publicFish19:{20:public:21:Tuna()22:{23:isFreshWaterFish=false;// set protected member in base24:}25:};26:27:classCarp:publicFish28:{29:public:30:Carp()31:{32:isFreshWaterFish=false;33:}34:};35:36:intmain()37:{38:Carp myLunch;39:Tuna myDinner;40:41:cout<<"About my food"<<endl;42:43:cout<<"Lunch: ";44:myLunch.Swim();45:46:cout<<"Dinner: ";47:myDinner.Swim();48:49:// uncomment line below to see that protected members50:// are not accessible from outside the class hierarchy51:// myLunch.isFreshWaterFish = false;52:53:return0;54:}基类初始化,向基类传递参数
包含初始化列表的派生类构造函数:
0:#include<iostream>1:usingnamespacestd;2:3:classFish4:{5:protected:6:boolisFreshWaterFish;// accessible only to derived classes7:8:public:9:// Fish constructor10:Fish(boolisFreshWater):isFreshWaterFish(isFreshWater){}11:12:voidSwim()13:{14:if(isFreshWaterFish)15:cout<<"Swims in lake"<<endl;16:else17:cout<<"Swims in sea"<<endl;18:}19:};20:21:classTuna:publicFish22:{23:public:24:Tuna():Fish(false){}// constructor initializes base25:};26:27:classCarp:publicFish28:{29:public:30:Carp():Fish(true){}31:};32:33:intmain()34:{35:Carp myLunch;36:Tuna myDinner;37:38:cout<<"About my food"<<endl;39:40:cout<<"Lunch: ";41:myLunch.Swim();42:43:cout<<"Dinner: ";44:myDinner.Swim();45:46:return0;47:}为了最大限度地提高安全性,对于派生类不需要访问的基类属性,别忘了将其声明为私有的。
在派生类中覆盖基类的方法
如果派生类实现了从基类继承的函数,且返回值和特征标相同,就相当于覆盖了基类的这个方法,如下面的代码所示:
classBase{public:voidDoSomething(){// implementation code… Does something}};classDerived:publicBase{public:voidDoSomething(){// implementation code… Does something else}};因此,如果使用 Derived 类的实例调用方法 DoSomething( ),调用的将不是 Base 类中的这个方法。
要调用 Base::DoSomething( ),只能在 main( )中使用作用域解析运算符显式地调用它。
调用基类中被覆盖的方法
派生类 Tuna 通过实现 Swim( )覆盖了 Fish 类的 Swim( )函数,其结果如下:
Tuna myDinner;myDinner.Swim();// will invoke Tuna::Swim()如果要在 main( )中调用 Fish::Swim( ),需要使用作用域解析运算符(::),如下所示:
myDinner.Fish::Swim();// invokes Fish::Swim() using instance of Tuna在派生类中调用基类的方法
通常,Fish::Swim( )包含适用于所有鱼类(包括金枪鱼和鲤鱼)的通用实现。如果要在 Tuna::Swim( )和 Carp::Swim( )的实现中重用 Fish::Swim( )的通用实现,可使用作用域解析运算符(::),如下面的代码所示:
classCarp:publicFish{public:Carp():Fish(true){}voidSwim(){cout<<"Carp swims real slow"<<endl;Fish::Swim();// invoke base class function using operator::}};在派生类中隐藏基类的方法
覆盖的一种极端情形是,Tuna::Swim( )可能隐藏 Fish::Swim( )的所有重载版本,使得调用这些重载版本会导致编译错误(因此称为被隐藏),
Tuna::Swim( )隐藏了重载方法 Fish::Swim(bool) :
0:#include<iostream>1:usingnamespacestd;2:3:classFish4:{5:public:6:voidSwim()7:{8:cout<<"Fish swims... !"<<endl;9:}10:11:voidSwim(boolisFreshWaterFish)// overloaded version12:{13:if(isFreshWaterFish)14:cout<<"Swims in lake"<<endl;15:else16:cout<<"Swims in sea"<<endl;17:}18:};19:20:classTuna:publicFish21:{22:public:23:voidSwim()24:{25:cout<<"Tuna swims real fast"<<endl;26:}27:};28:29:intmain()30:{31:Tuna myDinner;32:33:cout<<"About my food"<<endl;34:35:// myDinner.Swim(false);//failure: Tuna::Swim() hides Fish::Swim(bool)36:myDinner.Swim();37:38:return0;39:}要通过 Tuna 实例调用 Fish::Swim(bool),可采用如下解决方案。
- 解决方案 1:在 main( )中使用作用域解析运算符(::):
myDinner.Fish::Swim();- 解决方案 2:在 Tuna 类中,使用关键字 using 解除对 Fish::Swim( )的隐藏:
classTuna:publicFish{public:usingFish::Swim;// unhide all Swim() methods in class FishvoidSwim(){cout<<"Tuna swims real fast"<<endl;}};- 解决方案 3:在 Tuna 类中,覆盖 Fish::Swim( )的所有重载版本(如果需要,可通过 Tuna::Fish(…) 调用方法 Fish::Swim( )):
classTuna:publicFish{public:voidSwim(boolisFreshWaterFish){Fish::Swim(isFreshWaterFish);}voidSwim(){cout<<"Tuna swims real fast"<<endl;}};C++ 继承中构造、析构完整执行顺序
一、单一继承(父类 → 子类)
1. 构造函数执行顺序(先父后子)
- 基类(父类)构造函数先执行
- 子类自身成员变量按声明顺序构造
- 派生类(子类)构造函数体执行
原理:子类依赖父类的成员,必须先把父类完整构造好,再构造子类。
#include<iostream>usingnamespacestd;classBase{public:Base(){cout<<"基类构造\n";}};classSon:publicBase{public:Son(){cout<<"子类构造\n";}};intmain(){Son s;return0;}输出:
基类构造 子类构造2. 析构函数执行顺序(先子后父,和构造完全相反)
- 派生类(子类)析构函数体先执行
- 子类成员变量逆序销毁
- 基类(父类)析构函数最后执行
// 接上例,增加析构classBase{public:Base(){cout<<"基类构造\n";}~Base(){cout<<"基类析构\n";}};classSon:publicBase{public:Son(){cout<<"子类构造\n";}~Son(){cout<<"子类析构\n";}};intmain(){Son s;return0;}输出:
基类构造 子类构造 子类析构 基类析构二、包含成员对象(类中有其他类成员)
规则:先构造成员,再执行自身构造函数体;析构相反
执行流程:
- 父类构造(如有继承)
- 类内所有成员变量,按声明从上到下依次构造(和初始化列表顺序无关)
- 当前类构造函数体
析构完全逆序: - 当前类析构函数体
- 成员变量按声明倒序销毁
- 父类析构(如有继承)
示例:
structA{A(){cout<<"A构造";}~A(){cout<<"A析构";}};structB{B(){cout<<"B构造";}~B(){cout<<"B析构";}};classTest{A a;// 先声明B b;// 后声明public:Test(){cout<<"Test构造";}~Test(){cout<<"Test析构";}};构造顺序:A → B → Test
析构顺序:Test → B → A
三、多重继承(多个父类)
构造顺序:按继承声明顺序,不是初始化列表顺序
classA{};classB{};// 继承顺序:先A,后BclassC:publicA,publicB{public:// 哪怕列表先写B(),构造依然先A后BC():B(),A(){}};构造:A → B → C
析构:C → B → A
四、虚继承(菱形继承)
公共虚基类最先构造,只构造一次;其余普通父类按继承顺序。
析构时虚基类最后销毁。
五、多态重点考点:虚析构函数
如果用父类指针指向子类对象,父类析构不加virtual只会调用父类析构,子类析构不执行,内存泄漏!
classBase{public:virtual~Base(){}// 必须virtual};classSon:publicBase{};intmain(){Base*p=newSon();deletep;// 先Son析构,再Base析构return0;}私有继承与保护继承
注:派生类不能继承父类的private,不论是什么继承方式。
前面介绍的都是公有继承,私有继承的不同之处在于,指定派生类的基类时使用关键字 private:
classBase{// ... base class members and methods};classDerived:privateBase// private inheritance{// ... derived class members and methods};私有继承意味着在派生类的实例中,基类的所有公有成员和方法都是私有的——不能从外部访问。
换句话说,即便是 Base 类的公有成员和方法,也只能被 Derived 类使用,而无法通过 Derived 实例来使用它们。
一、基础规则(所有继承通用)
任何派生类,永远无法直接访问基类 private 私有成员;
仅能直接访问基类public、protected成员。
基类private成员会存在于子类内存,只能通过基类提供的接口间接操作。
二、三种继承方式权限转换(父类Motor → 子类Car)
| 继承方式 | Motor::public 在Car内 | Motor::protected 在Car内 | Motor::private |
|---|---|---|---|
| public 公有继承 | public | protected | 不可访问 |
| protected 保护继承 | protected | protected | 不可访问 |
| private 私有继承 | private | private | 不可访问 |
记忆
- public继承:protected/public权限原样保留,不收紧
- protected继承:protected/public 进子类统一变成protected
- private继承:protected/public降级为子类私有,阻断下层派生类访问
三、多层继承访问规则(Motor → Car → RaceCar)
场景1:Car : private Motor(题目示例)
- Motor的public/protected → 全部变为Car的私有成员
- 无论RaceCar以何种方式继承Car,都不能访问Car的私有成员
- 最终结论:RaceCar 无法访问 Motor 的任何 public/protected
- 核心原理:继承链中最严格的private继承会锁住上层所有成员,下层派生类无法穿透
场景2:Car : public Motor
- Motor的public保留为Car的public,Motor的protected保留为Car的protected
- RaceCar继承Car后,仍可正常访问Motor的public/protected
- 整条链路无权限收紧,访问权限可向下传递
只有基类自己的成员函数,能直接操作自身 private;
子类想操作基类 private,只能调用基类提供的public/protected成员函数中转;
三种继承的区别,只影响基类 public/protected 在子类中的访问等级,完全不改变基类 private 的访问限制。
切除问题
如果程序员像下面这样做,结果将如何呢?
Derived objDerived;Base objectBase=objDerived;如果程序员像下面这样做,结果又将如何呢?
voidUseBase(Base input);...Derived objDerived;UseBase(objDerived);// copy of objDerived will be sliced and sent它们都将 Derived 对象复制给 Base 对象,一个是通过显式地复制,另一个是通过传递参数。在这
些情形下,编译器将只复制 objDerived 的 Base 部分,即不是整个对象。换句话说,Derived 的数据成员包含的信息将丢失。这种无意间裁减数据,导致 Derived 变成 Base 的行为称为切除(slicing)。
要避免切除问题,不要按值传递参数,而应以指向基类的指针或 const 引用的方式传递。
多继承
使用多继承模拟具备哺乳动物、鸟类和爬行动物特征的鸭嘴兽:
0:#include<iostream>1:usingnamespacestd;2:3:classMammal4:{5:public:6:voidFeedBabyMilk()7:{8:cout<<"Mammal: Baby says glug!"<<endl;9:}10:};11:12:classReptile13:{14:public:15:voidSpitVenom()16:{17:cout<<"Reptile: Shoo enemy! Spits venom!"<<endl;18:}19:};20:21:classBird22:{23:public:24:voidLayEggs()25:{26:cout<<"Bird: Laid my eggs, am lighter now!"<<endl;27:}28:};29:30:classPlatypus:publicMammal,publicBird,publicReptile31:{32:public:33:voidSwim()34:{35:cout<<"Platypus: Voila, I can swim!"<<endl;36:}37:};38:39:intmain()40:{41:Platypus realFreak;42:realFreak.LayEggs();43:realFreak.FeedBabyMilk();44:realFreak.SpitVenom();45:realFreak.Swim();46:47:return0;48:}使用 final 禁止继承
从 C++11 起,编译器支持限定符 final。被声明为 final 的类不能用作基类。如下:Platypus 类表示一种进化得很好的物种,因此您可能想将其声明为 final 的,从而禁止继承它。可像下面这样做:
classPlatypusfinal:publicMammal,publicBird,publicReptile{public:voidSwim(){cout<<"Platypus: Voila, I can swim!"<<endl;}};除用于类外,还可将 final 用于成员函数来控制多态行为。
