继承继承基础继承与派生classBase{// ... base class members};classDerived:access-specifierBase{// ... derived class members};其中 access-specifier 可以是 public这是最常见的表示派生类是一个基类、private 或 protected表示派生类有一个基类。下面的继承层次结构表明Carp 类是从 Fish 类派生而来的classFish// base class{// ... Fishs members};classCarp:publicFish// derived class{// ... Carps members};阅读介绍继承的文献时您将遇到“从…继承而来”和“从…派生而来”等术语它们的含义相同。同样基类也被称为超类从基类派生而来的类称为派生类也叫子类。访问限定符protected与 public 和 private 一样protected 也是一个访问限定符。将属性声明为 protected 的时相当于允许派生类和友元类访问它但禁止在继承层次结构外部包括 main( )访问它。一种更好的 Fish 类设计它使用关键字 protected 将成员属性只暴露给派生类:0:#includeiostream1:usingnamespacestd;2:3:classFish4:{5:protected:6:boolisFreshWaterFish;// accessible only to derived classes7:8:public:9:voidSwim()10:{11:if(isFreshWaterFish)12:coutSwims in lakeendl;13:else14:coutSwims in seaendl;15:}16:};17:18:classTuna:publicFish19:{20:public:21:Tuna()22:{23:isFreshWaterFishfalse;// set protected member in base24:}25:};26:27:classCarp:publicFish28:{29:public:30:Carp()31:{32:isFreshWaterFishfalse;33:}34:};35:36:intmain()37:{38:Carp myLunch;39:Tuna myDinner;40:41:coutAbout my foodendl;42:43:coutLunch: ;44:myLunch.Swim();45:46:coutDinner: ;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:#includeiostream1: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:coutSwims in lakeendl;16:else17:coutSwims in seaendl;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:coutAbout my foodendl;39:40:coutLunch: ;41:myLunch.Swim();42:43:coutDinner: ;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(){coutCarp swims real slowendl;Fish::Swim();// invoke base class function using operator::}};在派生类中隐藏基类的方法覆盖的一种极端情形是Tuna::Swim( )可能隐藏 Fish::Swim( )的所有重载版本使得调用这些重载版本会导致编译错误因此称为被隐藏Tuna::Swim( )隐藏了重载方法 Fish::Swim(bool) 0:#includeiostream1:usingnamespacestd;2:3:classFish4:{5:public:6:voidSwim()7:{8:coutFish swims... !endl;9:}10:11:voidSwim(boolisFreshWaterFish)// overloaded version12:{13:if(isFreshWaterFish)14:coutSwims in lakeendl;15:else16:coutSwims in seaendl;17:}18:};19:20:classTuna:publicFish21:{22:public:23:voidSwim()24:{25:coutTuna swims real fastendl;26:}27:};28:29:intmain()30:{31:Tuna myDinner;32:33:coutAbout my foodendl;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(){coutTuna swims real fastendl;}};解决方案 3在 Tuna 类中覆盖 Fish::Swim( )的所有重载版本如果需要可通过 Tuna::Fish(…) 调用方法 Fish::Swim( )classTuna:publicFish{public:voidSwim(boolisFreshWaterFish){Fish::Swim(isFreshWaterFish);}voidSwim(){coutTuna swims real fastendl;}};C 继承中构造、析构完整执行顺序一、单一继承父类 → 子类1. 构造函数执行顺序先父后子基类父类构造函数先执行子类自身成员变量按声明顺序构造派生类子类构造函数体执行原理子类依赖父类的成员必须先把父类完整构造好再构造子类。#includeiostreamusingnamespacestd;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(){coutA构造;}~A(){coutA析构;}};structB{B(){coutB构造;}~B(){coutB析构;}};classTest{A a;// 先声明B b;// 后声明public:Test(){coutTest构造;}~Test(){coutTest析构;}};构造顺序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*pnewSon();deletep;// 先Son析构再Base析构return0;}私有继承与保护继承注派生类不能继承父类的private,不论是什么继承方式。前面介绍的都是公有继承私有继承的不同之处在于指定派生类的基类时使用关键字 privateclassBase{// ... 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::privatepublic 公有继承publicprotected不可访问protected 保护继承protectedprotected不可访问private 私有继承privateprivate不可访问记忆public继承protected/public权限原样保留不收紧protected继承protected/public 进子类统一变成protectedprivate继承protected/public降级为子类私有阻断下层派生类访问三、多层继承访问规则Motor → Car → RaceCar场景1Car : private Motor题目示例Motor的public/protected → 全部变为Car的私有成员无论RaceCar以何种方式继承Car都不能访问Car的私有成员最终结论RaceCar 无法访问 Motor 的任何 public/protected核心原理继承链中最严格的private继承会锁住上层所有成员下层派生类无法穿透场景2Car : public MotorMotor的public保留为Car的publicMotor的protected保留为Car的protectedRaceCar继承Car后仍可正常访问Motor的public/protected整条链路无权限收紧访问权限可向下传递只有基类自己的成员函数能直接操作自身 private子类想操作基类 private只能调用基类提供的public/protected成员函数中转三种继承的区别只影响基类 public/protected 在子类中的访问等级完全不改变基类 private 的访问限制。切除问题如果程序员像下面这样做结果将如何呢Derived objDerived;Base objectBaseobjDerived;如果程序员像下面这样做结果又将如何呢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:#includeiostream1:usingnamespacestd;2:3:classMammal4:{5:public:6:voidFeedBabyMilk()7:{8:coutMammal: Baby says glug!endl;9:}10:};11:12:classReptile13:{14:public:15:voidSpitVenom()16:{17:coutReptile: Shoo enemy! Spits venom!endl;18:}19:};20:21:classBird22:{23:public:24:voidLayEggs()25:{26:coutBird: Laid my eggs, am lighter now!endl;27:}28:};29:30:classPlatypus:publicMammal,publicBird,publicReptile31:{32:public:33:voidSwim()34:{35:coutPlatypus: 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 禁止继承从 C11 起编译器支持限定符 final。被声明为 final 的类不能用作基类。如下Platypus 类表示一种进化得很好的物种因此您可能想将其声明为 final 的从而禁止继承它。可像下面这样做classPlatypusfinal:publicMammal,publicBird,publicReptile{public:voidSwim(){coutPlatypus: Voila, I can swim!endl;}};除用于类外还可将 final 用于成员函数来控制多态行为。
c++复习自存--继承
发布时间:2026/7/6 7:45:50
继承继承基础继承与派生classBase{// ... base class members};classDerived:access-specifierBase{// ... derived class members};其中 access-specifier 可以是 public这是最常见的表示派生类是一个基类、private 或 protected表示派生类有一个基类。下面的继承层次结构表明Carp 类是从 Fish 类派生而来的classFish// base class{// ... Fishs members};classCarp:publicFish// derived class{// ... Carps members};阅读介绍继承的文献时您将遇到“从…继承而来”和“从…派生而来”等术语它们的含义相同。同样基类也被称为超类从基类派生而来的类称为派生类也叫子类。访问限定符protected与 public 和 private 一样protected 也是一个访问限定符。将属性声明为 protected 的时相当于允许派生类和友元类访问它但禁止在继承层次结构外部包括 main( )访问它。一种更好的 Fish 类设计它使用关键字 protected 将成员属性只暴露给派生类:0:#includeiostream1:usingnamespacestd;2:3:classFish4:{5:protected:6:boolisFreshWaterFish;// accessible only to derived classes7:8:public:9:voidSwim()10:{11:if(isFreshWaterFish)12:coutSwims in lakeendl;13:else14:coutSwims in seaendl;15:}16:};17:18:classTuna:publicFish19:{20:public:21:Tuna()22:{23:isFreshWaterFishfalse;// set protected member in base24:}25:};26:27:classCarp:publicFish28:{29:public:30:Carp()31:{32:isFreshWaterFishfalse;33:}34:};35:36:intmain()37:{38:Carp myLunch;39:Tuna myDinner;40:41:coutAbout my foodendl;42:43:coutLunch: ;44:myLunch.Swim();45:46:coutDinner: ;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:#includeiostream1: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:coutSwims in lakeendl;16:else17:coutSwims in seaendl;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:coutAbout my foodendl;39:40:coutLunch: ;41:myLunch.Swim();42:43:coutDinner: ;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(){coutCarp swims real slowendl;Fish::Swim();// invoke base class function using operator::}};在派生类中隐藏基类的方法覆盖的一种极端情形是Tuna::Swim( )可能隐藏 Fish::Swim( )的所有重载版本使得调用这些重载版本会导致编译错误因此称为被隐藏Tuna::Swim( )隐藏了重载方法 Fish::Swim(bool) 0:#includeiostream1:usingnamespacestd;2:3:classFish4:{5:public:6:voidSwim()7:{8:coutFish swims... !endl;9:}10:11:voidSwim(boolisFreshWaterFish)// overloaded version12:{13:if(isFreshWaterFish)14:coutSwims in lakeendl;15:else16:coutSwims in seaendl;17:}18:};19:20:classTuna:publicFish21:{22:public:23:voidSwim()24:{25:coutTuna swims real fastendl;26:}27:};28:29:intmain()30:{31:Tuna myDinner;32:33:coutAbout my foodendl;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(){coutTuna swims real fastendl;}};解决方案 3在 Tuna 类中覆盖 Fish::Swim( )的所有重载版本如果需要可通过 Tuna::Fish(…) 调用方法 Fish::Swim( )classTuna:publicFish{public:voidSwim(boolisFreshWaterFish){Fish::Swim(isFreshWaterFish);}voidSwim(){coutTuna swims real fastendl;}};C 继承中构造、析构完整执行顺序一、单一继承父类 → 子类1. 构造函数执行顺序先父后子基类父类构造函数先执行子类自身成员变量按声明顺序构造派生类子类构造函数体执行原理子类依赖父类的成员必须先把父类完整构造好再构造子类。#includeiostreamusingnamespacestd;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(){coutA构造;}~A(){coutA析构;}};structB{B(){coutB构造;}~B(){coutB析构;}};classTest{A a;// 先声明B b;// 后声明public:Test(){coutTest构造;}~Test(){coutTest析构;}};构造顺序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*pnewSon();deletep;// 先Son析构再Base析构return0;}私有继承与保护继承注派生类不能继承父类的private,不论是什么继承方式。前面介绍的都是公有继承私有继承的不同之处在于指定派生类的基类时使用关键字 privateclassBase{// ... 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::privatepublic 公有继承publicprotected不可访问protected 保护继承protectedprotected不可访问private 私有继承privateprivate不可访问记忆public继承protected/public权限原样保留不收紧protected继承protected/public 进子类统一变成protectedprivate继承protected/public降级为子类私有阻断下层派生类访问三、多层继承访问规则Motor → Car → RaceCar场景1Car : private Motor题目示例Motor的public/protected → 全部变为Car的私有成员无论RaceCar以何种方式继承Car都不能访问Car的私有成员最终结论RaceCar 无法访问 Motor 的任何 public/protected核心原理继承链中最严格的private继承会锁住上层所有成员下层派生类无法穿透场景2Car : public MotorMotor的public保留为Car的publicMotor的protected保留为Car的protectedRaceCar继承Car后仍可正常访问Motor的public/protected整条链路无权限收紧访问权限可向下传递只有基类自己的成员函数能直接操作自身 private子类想操作基类 private只能调用基类提供的public/protected成员函数中转三种继承的区别只影响基类 public/protected 在子类中的访问等级完全不改变基类 private 的访问限制。切除问题如果程序员像下面这样做结果将如何呢Derived objDerived;Base objectBaseobjDerived;如果程序员像下面这样做结果又将如何呢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:#includeiostream1:usingnamespacestd;2:3:classMammal4:{5:public:6:voidFeedBabyMilk()7:{8:coutMammal: Baby says glug!endl;9:}10:};11:12:classReptile13:{14:public:15:voidSpitVenom()16:{17:coutReptile: Shoo enemy! Spits venom!endl;18:}19:};20:21:classBird22:{23:public:24:voidLayEggs()25:{26:coutBird: Laid my eggs, am lighter now!endl;27:}28:};29:30:classPlatypus:publicMammal,publicBird,publicReptile31:{32:public:33:voidSwim()34:{35:coutPlatypus: 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 禁止继承从 C11 起编译器支持限定符 final。被声明为 final 的类不能用作基类。如下Platypus 类表示一种进化得很好的物种因此您可能想将其声明为 final 的从而禁止继承它。可像下面这样做classPlatypusfinal:publicMammal,publicBird,publicReptile{public:voidSwim(){coutPlatypus: Voila, I can swim!endl;}};除用于类外还可将 final 用于成员函数来控制多态行为。