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. 构造函数执行顺序(先父后子)
  1. 基类(父类)构造函数先执行
  2. 子类自身成员变量按声明顺序构造
  3. 派生类(子类)构造函数体执行

原理:子类依赖父类的成员,必须先把父类完整构造好,再构造子类。

#include<iostream>usingnamespacestd;classBase{public:Base(){cout<<"基类构造\n";}};classSon:publicBase{public:Son(){cout<<"子类构造\n";}};intmain(){Son s;return0;}

输出:

基类构造 子类构造
2. 析构函数执行顺序(先子后父,和构造完全相反)
  1. 派生类(子类)析构函数体先执行
  2. 子类成员变量逆序销毁
  3. 基类(父类)析构函数最后执行
// 接上例,增加析构classBase{public:Base(){cout<<"基类构造\n";}~Base(){cout<<"基类析构\n";}};classSon:publicBase{public:Son(){cout<<"子类构造\n";}~Son(){cout<<"子类析构\n";}};intmain(){Son s;return0;}

输出:

基类构造 子类构造 子类析构 基类析构

二、包含成员对象(类中有其他类成员)

规则:先构造成员,再执行自身构造函数体;析构相反
执行流程:

  1. 父类构造(如有继承)
  2. 类内所有成员变量,按声明从上到下依次构造(和初始化列表顺序无关)
  3. 当前类构造函数体
    析构完全逆序:
  4. 当前类析构函数体
  5. 成员变量按声明倒序销毁
  6. 父类析构(如有继承)

示例:

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 私有成员
仅能直接访问基类publicprotected成员。
基类private成员会存在于子类内存,只能通过基类提供的接口间接操作。

二、三种继承方式权限转换(父类Motor → 子类Car)
继承方式Motor::public 在Car内Motor::protected 在Car内Motor::private
public 公有继承publicprotected不可访问
protected 保护继承protectedprotected不可访问
private 私有继承privateprivate不可访问
记忆
  • public继承:protected/public权限原样保留,不收紧
  • protected继承:protected/public 进子类统一变成protected
  • private继承:protected/public降级为子类私有,阻断下层派生类访问
三、多层继承访问规则(Motor → Car → RaceCar)
场景1:Car : private Motor(题目示例)
  1. Motor的public/protected → 全部变为Car的私有成员
  2. 无论RaceCar以何种方式继承Car,都不能访问Car的私有成员
  3. 最终结论:RaceCar 无法访问 Motor 的任何 public/protected
  4. 核心原理:继承链中最严格的private继承会锁住上层所有成员,下层派生类无法穿透
场景2:Car : public Motor
  1. Motor的public保留为Car的public,Motor的protected保留为Car的protected
  2. RaceCar继承Car后,仍可正常访问Motor的public/protected
  3. 整条链路无权限收紧,访问权限可向下传递

只有基类自己的成员函数,能直接操作自身 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 用于成员函数来控制多态行为。