200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > Th3.15:继承的构造函数 多重继承 虚继承之详述

Th3.15:继承的构造函数 多重继承 虚继承之详述

时间:2024-08-11 22:02:04

相关推荐

Th3.15:继承的构造函数 多重继承 虚继承之详述

本博客将记录:新经典课程知识点的第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公有继承自类GrandFatherpublic: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和类Bpublic: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的路上~

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。