C++类细节梳理之派生和继承 目录继承派生的概念语法与访问控制语法构造与析构顺序派生与构造函数派生与成员函数改变访问控制类型兼容对象的类型转换多重继承继承派生的概念当定义的一个B类拥有某个已经写好的A类的全部特点此外还多出了A类没有的特点就可以把A作为一个基类B类为A类的一个派生类这样A类派生了B类B类继承了A类派生类是通过基类扩充和修改得到的基类所有成员自动成为派生类的成员这样就不用将重复用到的地方在写一遍提高程序的可重用性也方便代码管理语法与访问控制语法class 派生类名 : 继承访问控制 基类类名{ 成员访问控制: 成员声明列表; };继承访问控制可为privatepublicprotected若省却则默认为privateprotected具有private和public双重角色类的外部不可访问如同private派生类可以访问如同public影响继承成员访问控制方式的两个因素分别是定义派生类时指定的继承访问控制该成员在基类中所具有的成员访问控制基类中成员的访问控制继承访问控制派生类中继承成员的访问控制publicpublicpublicprotectedprotectedprivate不可访问publicprotectedprotectedprotectedprotectedprivate不可访问publicprivateprivateprotectedprivateprivate不可访问注意“私有”和“不可访问”的区别私有可通过派生类本身访问不可访问成员就算是派生类也不可以访问构造与析构顺序基类构造函数不会被继承派生类需要声明自己的构造函数。派生类的构造函数只需初始化本类新增的成员就行了基类的成员的初始化通过派生类构造函数初始化器中完成自动使用无参构造行数若有参则需要显示申明构造函数的调用次序基类的构造函数多重继承调用顺序按照声明从左到右然后本对象成员的构造函数最后调用本类的构造函数析构函数的调用次序本类的析构函数然后本类对象的析构函数最后调用其基类的析构函数class baseA{ public: baseA(); ~baseA(); }; class baseB{ public: baseB(); ~baseB(); }; class C : public baseA{ public: baseB b; C(); ~C(); }; baseA::baseA() { cout base A endl; } baseA::~baseA() { cout ~ base A endl; } baseB::baseB() { cout base B endl; } baseB::~baseB() { cout ~ base B endl; } C::C() { cout C endl; } C::~C() { cout ~ C endl; } int main() { C c; return 0; }输出为base A base B C ~ C ~ base B ~ base A利用这一特性可以实现instanceof函数返回该对象的类型名字。#include iostream #include string #include typeinfo using namespace std; class Object{ public: string name; Object(){ name Object; } ~Object(){} string getname(){ return name; } }; class Animal : public Object{ public: Animal(){ name Animal; } ~Animal(){} }; class Dog:public Animal{ public: Dog(){ name Dog; } ~Dog(){} }; class Cat:public Animal{ public: Cat(){ name Cat; } ~Cat(){} }; string instanceof(Object obj){ return obj.getname(); }该类的构造函数最后调用则name将会存入该类的名字派生与构造函数派生类不可以继承基类的私有成员构造函数和析构函数运算符重载这里暂时回避上面提到过若基类构造函数带参数则定义派生类构造函数时仅能通过初始化列表显示调用基类构造函数class MyPoint { private: double x, y; public: MyPoint(){ x 0; y 0; } MyPoint(double x, double y){ this-x x; this-y y; } }; class ThreeDPoint : public MyPoint { private: double z; public: ThreeDPoint(){ z 0; } ThreeDPoint(double x, double y, double z) : MyPoint(x, y){ this-z z; } };同样的对于拷贝构造函数也要用初始化列表调用基类的构造函数当然基类也要有拷贝构造函数的实现Human(const char* _languages, const char* _sound, int _age) : Creature(_sound, _age){ ... } Human(const char* _languages, const Creature _creature) : Creature(_creature){ ... } Human(const Human other) : Creature(other){ ... }当拷贝构造函数中给出的是基类的对象那么使用基类的拷贝构造函数是理所应当的但是若拷贝构造函数中只给出了派生类的对象此时我们也可以将此对象直接作为基类拷贝构造函数的对象这是因为类型兼容见下派生类转换为基类是安全的因此将派生类对象直接作为基类拷贝构造函数的对象时会安全的进行隐式转换将派生类变为基类从而完成拷贝构造再再再值得注意的是“”的运算符重载当然前提是基类也要有的运算符重载Creature Creature::operator(const Creature other){ ... return *this; } Human Human::operator(const Human other){ if(this other){ return *this; } Creature::operator(other); ... return *this; }首先判断是否是自己赋值给自己this和other都是对象的地址若相等则说明是同一个对象然后调用基类的重载运算符来更新基类与拷贝构造函数类似的这里同样可以传入派生类的对象。具体调用方法见代码首先写一个基类的命名空间后面接重载运算符的名称“operator”这一坨可看作函数的名称最后内写传入的参数派生与成员函数重载具有相同的作用域在同一个类中函数名字相同参数类型顺序或数目不同覆盖基类有virtual在前面函数名字相同隐藏函数名字相同但参数列表有差异函数名字参数列表相同且基类函数前面没有virtual继承没有被覆盖或隐藏的基类函数包括在基类中重载的函数隐藏可以修改和赋予成员函数新的功能改变访问控制基类的public和protected成员因为使用了protected或private继承访问控制而导致在派生类中访问方式发生改变这时可以使用“访问声明”恢复为原来的访问控制using 基类名::成员名运用这个方法也可以直接调用基类的构造函数类型兼容类型兼容主要表现为向上兼容可以将派生类的对象直接赋值给基类对象反之则不行。值得注意的是该派生类需要public继承才适用于上述规则class base{ public: void display(){ cout base endl; } }; class a : public base{ public: void display(){ cout a endl; } }; class b : public a{ public: void display(){ cout b endl; } }; void fun(base* p)//此处进行类型转换为base { p-display();} int main(){ base x; a y; b z; dase *ptr; ptr x; fun(ptr); ptr y; fun(ptr); ptr z; fun(ptr); }输出为base base base对象的类型转换static_cast用来转换类型int a 0; average static_castfloat(a);const_cast用来转换数据是否为constdynamic_cast用来安全的向下转换如果出现问题指针会为空指针引用会报错reinterpret_cast直接进行类型转换而不检查使用时要慎重多重继承当派生类有多个基类时运用class 派生类名 : 继承访问控制1 基类名1, 继承访问控制2 基类名2, ... { 成员声明: }除了菱形继承外同一个类不能被继承两次及以上上面这两种都是不行的而对于菱形继承由于它们有一部分是相同的编译器就会不知道该具体指的是谁这时可以在前面加上他们的类名::对象名或者在继承访问控制前面加上virtual代表虚基类。虚基类和普通基类基本一样只有在派生类重复继承了某一基类时才表现出来菱形继承virtual虚继承保证了虚基类的唯一副本只被初始化一次类之间共享该副本加入虚基类后构造函数调用次序最先调用虚基类的构造函数有参时要用初始化列表基类的构造函数多重继承调用顺序按照声明从左到右然后本对象成员的构造函数最后调用本类的构造函数析构函数顺序相反#include iostream using namespace std; class Writing { public: Writing() { coutWriting constructorendl; } ~Writing(){ cout~Writingendl; } }; class Racing { public: Racing(){ coutRacing constructorendl; } ~Racing(){ cout~Racingendl; } }; class Weekend : virtual public Writing, virtual public Racing{ public: Weekend() { coutWeekend constructorendl; } ~Weekend(){ cout~Weekendendl; } }; class Workday : virtual public Racing, virtual public Writing{ public: Workday(){ coutWorkday constructorendl; } ~Workday(){ cout~Workdayendl; } }; class Choseday : public Weekend, public Workday{ public: Choseday() { coutChoseday constructorendl; } ~Choseday(){ cout~Chosedayendl; } };由于虚基类构造函数是先于派生类构造函数的所以有参数的构造函数需要用初始化列表列出调用虚基类的构造函数class Creature { public: Creature(const int hands, const int legs) { _hands hands; _legs legs; } virtual ~Creature() {} private: int _hands; int _legs; }; class Beast : virtual public Creature { public: Beast(const int hands, const int legs, const string name) : Creature(hands, legs) { _name name; } ~Beast() {} private: string _name; };