本博客将记录:新经典课程知识点的第15节的笔记!

本小节的知识点分别是继承的构造函数、多重继承、虚继承。

今天总结的知识分为以下5个点:

(1)继承的构造函数
(2)多重继承
    (2.1)多重继承概述
    (2.2)静态成员变量
    (2.3)派生类构造函数与析构函数
    (2.4)从多个父类继承构造函数
(3)类型转换

(4)虚基类、虚继承(虚派生)
(5)总结

(1)继承的构造函数:

(我觉得还是少用为妙,因为这样写了会隐藏了子类的构造函数的细节实现,不利于日后维护代码!为了防止自己以后读别人写的代码遇到这样的用法时不发懵,了解一下即可)

继承时,子类只能继承其直接基类(父类)的构造函数。默认(也即编译器自动给我们生成的)、拷贝、移动构造函数都是不能被继承的。那么怎么写继承的构造函数呢?

        格式:using 类名::类名(构造函数名);

        注意:

        ①如果基类的构造函数中带有默认的参数值,那么编译器一遇到using A::A;时,就会帮我们在子类中构造出多个构造函数来:

具体的构造规则如下:

a)带有all参数的构造函数

b)其余的构造函数,每个分别省略掉一个默认参数。

比如:

class A {
public:int a1, a2, a3;A(int i, int j, int k = 5):a1(i),a2(j),a3(k) {}
};
class B :public A {
public:using A::A;//继承A的构造函数。其中的using关键字是让某个变量/函数在当前的作用域内可见的意思。//遇到这条代码时,编译器就会将用基类中的每个构造函数来生成一个与之对应的子类的构造函数//==> B(构造函数形参列表):A(A的构造函数形参列表){}//==> B(int i,int j,int k):A(i,j,k){}//又因为有默认参数,所以//此时的using A::A; ==>//B(int i, int j, int k) :A(i, j, k) {}//B(int i,int j) :A(i,j){}};

        ②如果子类中只含有使用using 类名::类名;继承过来的构造函数时,编译器不认为我们给该子类定义了构造函数的。因此编译器还会为我们自动生成子类的默认构造函数。

(2)多重继承:

    (2.1)多重继承概述:

多重继承:顾名思义,就是一个子类是继承自多个(2个及以上)父类的意思(一个孩子有很多个爹)。

class GrandFather {
public:int m_Age;string m_Name;GrandFather(int age,string name):m_Age(age),m_Name(name){}virtual void showInfo() {cout << "GrandFather's Name: " << this->m_Name << endl;cout << "GrandFather's Age: " << this->m_Age << endl;}virtual ~GrandFather() {}
};
class A :public GrandFather {//类A公有继承自类GrandFather
public:A(int age, string name) :GrandFather(age, name) {}
};
class B{//类B是独立的一个类
public:int m_b;B(int mb):m_b(mb) {}virtual ~B() {}
};
class C:public A,public B {//类C公有继承自类A和类B
public:C(int age, string name,int mb) :A(age, name),B(mb) {}virtual void showInfo() {cout << "C's Name: " << this->m_Name << endl;cout << "C's Age: " << this->m_Age << endl;cout << "C's mb: " << this->m_b << endl;}virtual ~C() {}
};

为了让大家更加清楚多继承的结果关系,我这里画出示意图:

     (2.2)静态成员变量:

在GrandFather类中声明一个静态成员变量并在类外给出定义:

class GrandFather {
public:int m_Age;string m_Name;GrandFather(int age,string name):m_Age(age),m_Name(name){}virtual void showInfo() {cout << "GrandFather's Name: " << this->m_Name << endl;cout << "GrandFather's Age: " << this->m_Age << endl;}virtual ~GrandFather() {}
public:static int grandStaticVar;//类内声明静态成员变量
};
int GrandFather::grandStaticVar = 100;//类外定义一个静态成员变量(也即给该静态成员变量分配内存)
//如果你的后续代码中不需要用到该静态成员变量的话,你就不需要再定义该静态成员变量了!
//如果你后续的代码中使用到该变量,就必须要在类外定义一下该静态成员变量,否则就会链接出错!
//main中:
multiSucc::C cc(123, "lyf",11);
cout << multiSucc::GrandFather::grandStaticVar <<"\t"// == 100
<< multiSucc::A::grandStaticVar << "\t"// == 100
<< multiSucc::C::grandStaticVar << "\t"// == 100
<< cc.grandStaticVar << endl;// == 100cc.grandStaticVar = 110;cout << multiSucc::GrandFather::grandStaticVar << "\t"// == 110<< multiSucc::A::grandStaticVar << "\t"// == 110<< multiSucc::C::grandStaticVar << "\t"// == 110<< cc.grandStaticVar << endl;// == 110

运行结果:

    (2.3)派生类构造函数与析构函数:

a)构造一个派生类对象将同时构造并初始化所有的直接基类的成分。

b)派生类的构造函数初始化列表只初始化它的直接基类,在多继承时也不例外。这样就会一层一层地把参数传递回最原始的基类,并把all的基类成分都构造好。

c)基类的构造顺序跟“派生列表”中基类的出现顺序是保持一致的!析构顺序则相反。

d)每个类的析构函数都只会do自己这个类本身的成分的释放工作(类与类之间的析构函数是互不干扰的)。

class GrandFather {
public:int m_Age;string m_Name;GrandFather(int age,string name):m_Age(age),m_Name(name){cout << "GrandFather的构造函数执行!" << endl;}virtual void showInfo() {cout << "GrandFather's Name: " << this->m_Name << endl;cout << "GrandFather's Age: " << this->m_Age << endl;}virtual ~GrandFather() {cout << "GrandFather的析构函数执行!" << endl;}
};
class A :public GrandFather {
public:A(int age, string name) :GrandFather(age, name) {cout << "A的构造函数执行!" << endl;}virtual ~A() {cout << "A的析构函数执行!" << endl;}
};
class B{
public:int m_b;B(int mb):m_b(mb) {cout << "B的构造函数执行!" << endl;    }virtual ~B() {cout << "B的析构函数执行!" << endl;}
};
class C:public A,public B {//这行就是所谓的“派生列表”
public:C(int age, string name,int mb) :A(age, name),B(mb) {cout << "C的构造函数执行!" << endl;}virtual void showInfo() {cout << "C's Name: " << this->m_Name << endl;cout << "C's Age: " << this->m_Age << endl;cout << "C's mb: " << this->m_b << endl;}virtual ~C() {cout << "C的析构函数执行!" << endl;}
};
//main中:
multiSucc::C cc(123, "lyf", 11);

运行结果:

如果把派生列表修改为class C:public B,public A 则运行结果为:

补充:这里补充一个隐式初始化基类的问题。

在B类中添加默认构造函数
B() {cout << "B的默认构造函数执行!" << endl;
}
将C类中的构造函数修改为:
C(int age, string name, int mb) :A(age, name) {cout << "C的构造函数执行!" << endl;
}

运行结果:

这里在C类的构造函数中没有显式地调用B类的构造函数,但是编译器还是会为我们调用B类的默认构造函数进行隐式地初始化。

    (2.4)从多个父类继承构造函数:

从多个父类中继承构造函数时,如果用using继承过来的构造函数相同(也即参数一样,函数体实现一样的构造函数),这样用就是错误的语法!

面对此种case你只能给该子类自定义一个新的属于该类的构造函数,而无法使用using来帮你节省定义构造函数都工作量了!

class A {
public:A(int tv) {}
};
class B {
public:B(int tv) {}
};
class C : public A,public B {
public:using A::A;//继承A类的构造函数 ==> C(int tv):A(tv){}using B::B;//错误!继承B类的构造函数是 ==> C(int tv):B(tv){}该构造函数和继承自A类的一模一样
};

运行结果:

说明子类C多继承自父类A和父类B的构造函数已经重定义了。因此我们只能自己定义一个属于子类C的构造函数:

class A {
public:A(int tv) {}
};
class B {
public:B(int tv) {}
};
class C : public A,public B {
public:using A::A;//继承A类的构造函数 ==> C(int tv):A(tv){}using B::B;//继承B类的构造函数 ==> C(int tv):B(tv){}C(int tv):A(tv),B(tv){cout << "C类的构造函数执行了!" << endl;}
};
//main中:
C ctest(888);

运行结果:

(3)类型转换:

在前面的单继承中,我们学过,基类指针是可以指向一个派生类(子类)对象的:因为编译器会帮助我们隐式地执行这种派生类到基类的转换。究其原因:因为每个派生类对象都会包含基类对象的成分。

多继承中,基类指针也是可以指向一个派生类(子类)对象的!

因此以下式子都是正确的用法:

//以前述的(2)节中的代码为例子:
using namespace multiSucc;
GrandFather* pg = new C(23,"lzf",23);
A* pa = new C(23, "lzf", 23);
B* pb = new C(23, "lzf", 23);
C myc(23, "lzf", 23);
GrandFather g(myc);

如果以上式子你有所疑惑可以翻阅我的3.11小节的博客总结,这里就不多赘述了!

(4)虚基类、虚继承(虚派生):

派生列表中,同一个基类只能出现一次,但是如下两种特殊情况例外:

a)派生类可以通过它的两个直接基类分别继承自同一个间接基类。

(这也是典型的钻石继承问题,Effective C++ term40给予了我们几条重要的忠告!可翻看!!!)

请看以下代码:

class GrandFather {
public:GrandFather(int age):m_Age(age){ cout << "GrandFather的构造函数执行!" << endl; }virtual ~GrandFather() {cout << "GrandFather的析构函数执行!" << endl;}
};
class A1 :public GrandFather {
public:A1(int age) :GrandFather(age)  { cout << "A1的构造函数执行!" << endl; }virtual ~A1() {cout << "A1的析构函数执行!" << endl;}
};
class A2 :public GrandFather {
public:A2(int age) :GrandFather(age)  { cout << "A2的构造函数执行!" << endl; }virtual ~A2() {cout << "A2的析构函数执行!" << endl;}
};
class C:public A1, public A2{
public:C(int age):A1(age),A2(age) { cout << "C的构造函数执行!" << endl; }virtual ~C() {cout << "C的析构函数执行!" << endl;}
};
int main(){using namespace multiSucc;C ctest(888);return 0;
}

运行结果:

b)直接继承某个基类,然后通过另一个基类间接继承该类。

请看以下代码:

class GrandFather {
public:GrandFather(int age):m_Age(age){ cout << "GrandFather的构造函数执行!" << endl; }virtual ~GrandFather() {cout << "GrandFather的析构函数执行!" << endl;}
};
class A1 :public GrandFather {
public:A1(int age) :GrandFather(age) { cout << "A1的构造函数执行!" << endl; }virtual ~A1() {cout << "A1的析构函数执行!" << endl;}
};
class C:public A1, public GrandFather{
public:C(int age):A1(age),GrandFather(age)  { cout << "C的构造函数执行!" << endl; }virtual ~C() {cout << "C的析构函数执行!" << endl;}
};
int main(){using namespace multiSucc;C ctest(888);return 0;
}

运行结果:

        但是,这两种特殊情况都会导致--》当我们对这同一个基类的成员变量进行读写操作时,会造成不明确的error!如下图所示:

所以,即便这两种继承同一个基类的特殊情况一开始可以把编译器蒙骗住,让你的代码通过了,但是后面你想do事情的时候却手足无措。因为这里你造成爷爷类GrandFather被继承了2次,其成员变量就被构造了2次,而且还是分属于A1和A2的不同父类成分,但是在类C继承过来后两者名字相同,从而造成名字冲突,这是非常不好的代码!这就是多继承时对于基类成员变量的二义性问题。

为了解决这种多继承时所出现的问题,引入了虚基类虚继承这个重要的知识点!

        虚基类(virtual base class): 无论这个基类在后续的继承体系中会出现多少次(被继承多少次),在其派生类中,都只会包含唯一一个共享的基类子内容成分。

在上述的例子中,让A1和A2都虚继承自GrandFather类的话,那么这个GrandFather类就成为了虚基类,此时生成类C的对象并对虚继承自GrandFather类的成员变量do读写操作时就no problem了!

虚继承:用virtual关键字do继承!

        格式:class 子类: virtual 继承类型 基类 | class 子类:继承类型 virtual 基类

举例:class A1 :virtual public GrandFather 表示类A1从GrandFather这个基类中do虚继承

class A2 :public virtual GrandFather 表示类A2从GrandFather这个基类中do虚继承

        注意:

        ①只要子类virtual继承了基类,那么该基类就自动成为“虚基类”了!

        ②只会对孙子类起作用!

        ③该虚基类必须给其all的子类都虚继承过去后,才能确保该虚基类的孙子类能够虚继承其本身,只产生一份虚基类的成分!

在上述的例子代码中,当A1和A2这两个类都虚继承自GrandFather类时,又因此类C继承自A1和A2,那么此时GrandFather类就是虚基类了,那么其孙子类C就只会产生一份虚基类成分的代码或者说孩子类只会生成一份共享的虚基类的实例对象(也即此时孙子类也虚继承了虚基类了)

请看以下代码:

class GrandFather {
public:int m_Age;GrandFather(int age):m_Age(age) {cout << "GrandFather的构造函数执行!" << endl;}virtual ~GrandFather() {cout << "GrandFather的析构函数执行!" << endl;}
};
class A1 : public virtual GrandFather {//表示类A1从GrandFather这个基类中do虚继承
public:A1(int age):GrandFather(age) {cout << "A1的构造函数执行!" << endl;}virtual ~A1() {cout << "A1的析构函数执行!" << endl;}
};
class A2 :virtual public GrandFather {
public:A2(int age) :GrandFather(age) {cout << "A2的构造函数执行!" << endl;}//A2(int age, string name) :GrandFather(age, name) {//    cout << "A2的构造函数执行!" << endl;//}virtual ~A2() {cout << "A2的析构函数执行!" << endl;}
};
class C:public A1, public A2 {
public:C(int age):A1(age), A2(age),GrandFather(age) {//虚继承时,是直接由最底层的孙子来初始化爷爷的成分的!cout << "C的构造函数执行!" << endl;}virtual ~C() {cout << "C的析构函数执行!" << endl;}
};
int main(){using namespace multiSucc;C ctest(888);ctest.m_Age = 99999;return 0;
}

运行结果:

成功运行!不会再产生对继承过来的成员变量m_Age访问的ambiguous不明确很模糊的问题了!也即deal了基类成员变量的二义性问题!

当然,在多继承时,你也可以直接给继承同一个基类的子类用同名成员变量覆盖继承过来的同名变量的确可以达到不用虚继承的效果,但是这样do的前提是破坏了各个子类的构造函数的继承写法(也即每一个孩子类都必须用其直接基类的构造函数来对应地初始化其直接基类的成分!),让他们不去初始化对应继承过来的基类的m_Age,这样会导致每一个子类的对象中都产生多余的一份基类的你没有用到的m_Age,这样既不符合Effective c++的思想,又把多继承写得效率很低呀!!!

        补充说明3个点:
        ①现在是用孙子C类来构造虚基类GrandFather类的成分;若孙子C类又给别的孩子类继承后,则会由C类的孩子去初始化GrandFather类的成分了。换句话说:虚基类(GrandFather)的成分是由最底层的派生类来初始化的!

        ②初始化顺序问题:编译器一定会先初始化虚基类成分,然后再按照派生列表的顺序,来初始化其他类的成分。

比如将上述代码中的C类的初始化列表修改为: class C:public A2, public A1

则运行结果为:

③如果在继承体系中,出现了多个虚继承的情况时,也即含有多个虚基类的情况时,到底哪一个虚基类会先被初始化呢?

答:此时,编译器就会按照你最底一层的类中的派生(继承)列表往回追溯,逐个逐个地看是否这些直接基类含有虚基类,若有的话就会先构造该类的成分,否则就按照继承列表的顺序来构造!(先追溯到哪个虚基类,就先构造哪个虚基类中的对象的成分)

请看以下代码:

class GrandFather {
public:int m_Age;GrandFather(int age):m_Age(age) { cout << "GrandFather的构造函数执行!" << endl; }virtual ~GrandFather() {cout << "GrandFather的析构函数执行!" << endl;}
};
class A1 : public virtual GrandFather {//表示类A1从GrandFather这个基类中do虚继承
public:A1(int age):GrandFather(age) { cout << "A1的构造函数执行!" << endl; }virtual ~A1() {cout << "A1的析构函数执行!" << endl;}
};
class A2 :virtual public GrandFather {//表示类A1从GrandFather这个基类中do虚继承
public:A2(int age) :GrandFather(age) { cout << "A2的构造函数执行!" << endl; }virtual ~A2() {cout << "A2的析构函数执行!" << endl;}
};
class B{
public:int m_b;B(int mb):m_b(mb) { cout << "B的构造函数执行!" << endl; }virtual ~B() {cout << "B的析构函数执行!" << endl;}
};
class BB : public virtual  B{//表示类BB从B这个基类中do虚继承
public:BB(int mb) :B(mb) { cout << "BB的构造函数执行!" << endl; }virtual ~BB() {cout << "BB的析构函数执行!" << endl;}
};
class C:public A1, public A2,public BB {
//A1和A2和BB这个类都是do虚继承的,那么编译器就会按照继承列表按顺序地调用对应的虚基类的构造函数do事情!
public:C(int age):A1(age), A2(age),GrandFather(age),BB(age),B(age) {//虚继承时,实际上是直接由孙子来初始化爷爷的成分的!cout << "C的构造函数执行!" << endl;}virtual ~C() {cout << "C的析构函数执行!" << endl;}
};

运行结果:

从运行结果可见,符合我上述所说的第③点补充说明!

(5)总结:

        1)若非必要,请一定不要选择用多继承的方式来编写你的代码!十分重要到建议:能用单继承解决问题,就不要用多继承!能不用virtual虚继承就尽量不用virtual继承来写代码!

  2若实在必要用到多继承时,务必细心地编写你的代码,并时刻提醒自己到底是否需要用虚继承的方式来do多继承!(当产生成员变量的二义性问题时,就需要用虚继承来do多继承!)

        3)学习多继承这个知识点不是说你一定要去常用它,而是为了让你去阅读别人的代码时不发懵!

        好,那么以上就是这一3.15小节我所回顾的内容的学习笔记,希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~

Th3.15:继承的构造函数、多重继承、虚继承之详述相关推荐

  1. 【C++】继承和派生、虚继承和虚基类、虚基类表和虚基类指针

    继承和派生.虚继承和虚基类.虚基类表和虚基类指针 继承和派生 继承概述 继承基本概念 派生类中的成员 继承的内容 派生类定义 派生类访问控制 对象构造和析构 对象构造和析构的调用顺序 继承中的构造和析 ...

  2. C++ Primer 5th笔记(chap 18 大型程序工具)构造函数与虚继承

    1. 继承体系中的每个类都可能在某个时刻成为" 最低层的派生类". 只要我们能创建虚基类的派生类对象, 该派生类的构造函数就必须初始化它的虚基类. Bear::Bear (std: ...

  3. 钻石问题(菱形继承问题) 和虚继承

    在C++中,什么叫做钻石问题(也可以叫菱形继承问题),怎么避免它? 下面的图表可以用来解释钻石问题. 假设我们有类B和类C,它们都继承了相同的类A.另外我们还有类D,类D通过多重继承机制继承了类B和类 ...

  4. 继承(下)----虚继承

    单继承&多继承 一个子类只有一个直接父类时称这种继承关系为单继承. 一个子类有两个或者两个以上的父类时称这种继承关系为多继承. 菱形继承 ---------特殊的多继承 有很大的缺点: 二义性 ...

  5. 多继承的二义性和虚继承(虚基类)

    一般来说,在派生类中对基类成员的访问是应该是唯一的.但是,由于在多继承的情况下,可能出现基类中某个成员的访问不唯一的情况,这称为对基类成员访问的二义性. 在多继承的情况下,通常有两种可能出现的二义性. ...

  6. 6.12C++:继承基类的构造函数、单继承的构造函数、多继承的构造函数、派生类复制构造函数、派生类的析构函数

    1 继承基类的构造函数 class A{public:A(){}; // A的构造函数 }; class B : public A{public:using A:A: } 2 单继承的构造函数 cla ...

  7. C++基础学习-26继承的构造函数、多重继承、虚继承

    目录 继承的构造函数 多重继承 1.多重继承的概念 2.静态成员变量 3.派生类构造函数与析构函数 4.从多个父类继承构造函数 类型转换 虚基类.虚继承(虚派生) 总结 继承的构造函数 C++语言同时 ...

  8. 多重继承与虚继承编程实验

    多重继承与虚继承编程实验 基本知识 多重继承 多重继承下的类作用域 虚继承 构造函数与虚继承 关于本程序 示例代码 Animal_virtual_baseVers.h virt-inherit.cpp ...

  9. c++primer 笔记03多重继承与虚继承

    18.3多重继承与虚继承 多个直接基类中产生派生类的能力,多重继承的派生类继承了所有父类的属性 18.3.1多重继承 派生类的派生列表中可以包含多个基类,但这些类不能是final的 class Zoo ...

  10. C++基本概念复习之二:多重继承、虚继承、纯虚函数(抽象类)

    一.多重继承: #include <iostream> using namespace std; class Horse { public: Horse(){cout<<&qu ...

最新文章

  1. c语言程序设计入门导论,程序设计入门——C语言
  2. WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance
  3. c++中报错预处理器指令后有意外标记 - 应输入换行符
  4. php与c语言流程控制语句,小蚂蚁学习C语言(7)——C语言流程控制之if
  5. Spinner的使用方法
  6. 华中科技大学 计算机组成原理 上机实验1 2018
  7. 关于二叉堆(优先队列)的其他操作及其应用
  8. c++ 连接两个字符串实现代码 实现类似strcat功能(转)
  9. html5 input select,【Web前端问题】select如何实现既可以像input那样支持输入,又可以从下拉中选择? antd...
  10. 对Session、Cookie的完全理解
  11. IT部门不应忽略的12种数据
  12. 计算机兴趣小组活动教学教案,计算机兴趣小组计划教案.doc
  13. 7-4 华氏度转摄氏度 (5分)_PTA_Java基础练习
  14. FdSanitizer 简介
  15. 以太坊公链节点连接节点超时问题排查
  16. 网易面试总结——面试案例1~面试案例4
  17. 国际标准智商测试题答案
  18. Android学习视频推荐
  19. 制造业和物流行业的WMS系统差别在哪?
  20. 简单的ps去掉图片上不想留的文字

热门文章

  1. 遮挡剔除 Occlusion Culling(转)
  2. MySQL中查询、删除重复记录的方法大全
  3. Java Set集合的详解
  4. powerShell 使用 chcp 65001 之后,还是显示乱码问题解决
  5. (三)打造华丽的即时通信系统主界面,让你的聊天体验更有质感
  6. vue-router学习
  7. 企业主流MySQL高可用集群
  8. 19. 查询表student——查询姓欧阳且全名为三个汉字的学生的姓名
  9. 智能客服系统如何帮助企业提高客户满意度?
  10. python单步调试