菜鸟笔记
提升您的技术认知

C++内功修炼----面向对象之继承

继承的基本概念

继承就是新类从已有类那里得到已有的特性。 类的派生指的是从已有类产生新类的过程。原有的类成为基类或父类,产生的新类称为派生类或子类,为了代码的重用,保留基类的原本结构,并新增派生类的部分,一个派生类可以通过继承获得基类的所有成员,而无需再次定义它们。声明一个派生类对象,即在构造派生类对象时,遵循基类的接口,先构造基类子对象,再构造派生类增加的部分。其中的组成由下图所示,子类继承基类后,可以创建子类对象来调用基类函数,变量等


单一继承:继承一个父类,这种继承称为单一继承,一般情况尽量使用单一继承,使用多重继承容易造成混乱易出问题
多重继承:继承多个父类,类与类之间要用逗号隔开,类名之前要有继承权限,假使两个或两个基类都有某变量或函数,在子类中调用时需要加类名限定符如c.a::i = 1;
菱形继承:多重继承掺杂隔代继承1-n-1模式,此时需要用到虚继承,例如 B,C虚拟继承于A,D再多重继承B,C,否则会出错,当出现菱形继承时,例如下图所示:

要构造一个SleepSofa对象,就要构造一个Sofa和一个Bed子对象,这其中又同时构造了两次Furniture对象,这是不合理的。因此Bed和Sofa类要对Furniture类进行虚继承(virtual public Furniture)来避免这种状况。

继承的基本语法

派生类的声明:

class 派生类名:继承方式 基类名1, 继承方式 基类名2,...,继承方式 基类名n
{
    派生类成员声明;
};

在 c++ 中,一个派生类可以同时有多个基类,这种情况称为多重继承。如果派生类只有一个基类,称为单继承。派生类继承基类中除构造和析构函数以外的所有成员。

继承的方式

 继承方式规定了如何访问基类继承的成员。继承方式有public, private, protected。如果不显示给出继承方式,默认为private继承。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。

  • 公有继承 当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无论派生类的成员还是派生类的对象都无法访问基类的私有成员。
  • 私有继承 当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。
  • 保护继承 保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。

继承的分类

1、普通继承(不包含虚函数)
a、单继承

class Base
{
public:
    Base (int a = 1):base(a){}
    void fun0(){cout << base << endl;}
    int base;
};
class Derive:public Base
{
public:
    Derive (int a = 2):derive(a){}
    void fun1(){cout << base1 << endl;}
    int derive;
};


b、多继承

class Base1
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2
{
public:
    Base2 (int a = 3):base2(a){}
    void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: public Base1, public Base2
{
public:
    Derive (int value = 4):derive (value){}
    void fun3(){cout << derive << endl;}
    int derive;
};

c、菱形继承

class Base
{
public:
    Base (int a = 1):base(a){}
    void fun0(){cout << base << endl;}
    int base;
};
class Base1:public Base
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2:public Base
{
public:
    Base2 (int a = 3):base2(a){}
    void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: public Base1, public Base2
{
public:
    Derive (int value = 4):derive (value){}
    void fun3(){cout << derive << endl;}
    int derive;
};

注:菱形继承存在二义性问题,编译都不通过,只能通过指定特定基类的方式进行访问基类变量。

Derive d;

d.base =3; // 不正确

d.Base1::base = 3; // 正确

2、普通继承(包含虚函数)
a、单继承(包含虚函数)

class Base
{
public:
    Base (int a = 1):base(a){}
    virtual void fun0(){cout << base << endl;}
    int base;
};
class Derive:public Base
{
public:
    Derive (int a = 2):derive(a){}
    virtual void fun0(){};
    virtual void fun1(){cout << derive << endl;}
    int derive;
};

注:派生类中新增的虚函数追加到虚函数表后面。

b、多继承(包含虚函数)

class Base1
{
public:
    Base1 (int a = 2):base1(a){}
    virtual void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2
{
public:
    Base2 (int a = 3):base2(a){}
    virtual void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: public Base1, public Base2
{
public:
    Derive (int value = 4):derive (value){}
    virtual void fun3(){cout << derive << endl;}
    int derive;
};

注:派生类中新增的虚函数,追加到第一个基类的虚函数表的后面。

c、菱形继承(包含虚函数)

class Base
{
public:
    Base (int a = 1):base(a){}
    virtual void fun0(){cout << base << endl;}
    int base;
};
class Base1:public Base
{
public:
    Base1 (int a = 2):base1(a){}
    virtual void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2:public Base
{
public:
    Base2 (int a = 3):base2(a){}
    virtual void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: public Base1, public Base2
{
public:
    Derive (int value = 4):derive (value){}
    virtual void fun3(){cout << derive << endl;}
    int derive;
};

注:分析时,由上到下依次分析。存在二义性问题和内存冗余问题。

 3、虚继承(不包含虚函数)
新增虚基类指针,指向虚基类表,虚基类表中首项存储虚基类指针的偏移量,接下来依次存储虚基类的偏移量(偏移量是相对于虚基类表指针的存储地址)。

a、单虚继承(不包含虚函数)

class Base
{
public:
    Base (int a = 1):base(a){}
    void fun0(){cout << base << endl;}
    int base;
};
class Base1:virtual public Base
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};

b、多虚继承(不包含虚函数)

class Base1
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2
{
public:
    Base2 (int a = 3):base2(a){}
    void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive:virtual public Base1, virtual public Base2
{
public:
    Derive (int value = 4):derive (value){}
    void fun3(){cout << derive << endl;}
    int derive;
};

c、菱形虚继承(不包含虚函数)
第一种形式:

class Base
{
public:
    Base (int a = 1):base(a){}
    void fun0(){cout << base << endl;}
    int base;
};
class Base1:virtual Base
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2:virtual Base
{
public:
    Base2 (int a = 3):base2(a){}
    void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive:virtual public Base1, virtual public Base2
{
public:
    Derive (int value = 4):derive (value){}
    void fun3(){cout << derive << endl;}
    int derive;
};

注:分析派生类的内存分布时,也是由上到下分析。虚继承将基类置于内存末尾,但是置于末尾的顺序也有一定的次序。首先Base先放到末尾,然后Base1放到末尾,最后Base2放到末尾。

第二种形式:

class Base
{
public:
    Base (int a = 1):base(a){}
    void fun0(){cout << base << endl;}
    int base;
};
class Base1:virtual public Base
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};

class Base2:virtual public Base
{
public:
    Base2 (int a = 3):base2(a){}
    void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: public Base1, public Base2
{
public:
    Derive (int value = 4):derive (value){}
    void fun3(){cout << derive << endl;}
    int derive;
};

注:分析的原则,从上到下,依次分析。

4、 虚继承(包含虚函数)
a、单虚继承(包含虚函数)

class Base
{
public:
    Base (int a = 1):base(a){}
    virtual void fun0(){cout << base << endl;}
    int base;
};
class Base1:virtual Base
{
public:
    Base1 (int a = 2):base1(a){}
    virtual void fun1(){cout << base1 << endl;}
    int base1;
};

  与普通的包含虚函数的单继承相比,派生类拥有自己的虚函数表以及虚函数表指针,而不是与基类共用一个虚函数表。注意虚函数表指针和虚基类表指针的存储顺序。

b、多虚继承(包含虚函数)

class Base1
{
public:
    Base1 (int a = 2):base1(a){}
    virtual void fun1(){cout << base1 << endl;}
    int base1;
};

class Base2
{
public:
    Base2 (int a = 3):base2(a){}
    virtual void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive:virtual public Base1, virtual public Base2
{
public:
    Derive (int value = 4):derive (value){}
    virtual void fun3(){cout << derive << endl;}
    int derive;
};

c、菱形虚继承(包含虚函数)
第一种形式:

class Base
{
public:
    Base (int a = 1):base(a){}
    virtual void fun0(){cout << base << endl;}
    int base;
};
class Base1:virtual public Base
{
public:
    Base1 (int a = 2):base1(a){}
    virtual void fun1(){cout << base1 << endl;}
    int base1;
};

class Base2:virtual public Base
{
public:
    Base2 (int a = 3):base2(a){}
    virtual void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive:  public Base1, public Base2
{
public:
    Derive (int value = 4):derive (value){}
    virtual void fun3(){cout << derive << endl;}
    int derive;
};

第二种形式:

class Base
{
public:
    Base (int a = 1):base(a){}
    virtual void fun0(){cout << base << endl;}
    int base;
};
class Base1:virtual public Base
{
public:
    Base1 (int a = 2):base1(a){}
    virtual void fun1(){cout << base1 << endl;}
    int base1;
};

class Base2:virtual public Base
{
public:
    Base2 (int a = 3):base2(a){}
    virtual void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: virtual public Base1,virtual public Base2
{
public:
    Derive (int value = 4):derive (value){}
    virtual void fun3(){cout << derive << endl;}
    int derive;
};

菱形继承中的二义性问题
 

先说说菱形继承的缺点吧。它会造成数据冗余和二义性的问题。你问我怎么才能看得出来呢?那么你就仔细看下面这段代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class A

{

public:

int a;

};

class B:public A

{

public:

int b;

};

class C:public A

{

public:

int c;

};

class D:public B,public C

{

public:

int d;

}


程序没有跑过,为什么?函数调用时不知道该选取哪一个父类作为基类,因为,在类D中具有两个基类,如果你不显示的去
说明应该使用哪一个,那么编译器自己肯定不会去主动选择的。请看类中数据的存储情况


从这张图片中,我们可以大致看到a的数据在类b中有一份,在类c中也有一份,这样的话,数据重复不说,调用也会模糊,为了解决这个问题,我们引入了一个虚基类。只需要在类b和类c的前面加上关键字 virtual ,这些问题都会随之解决。
具体请看内存情况。很明显,现在a只有一份了,但是,又好像多了点什么?经过一番分析,我弄明白了,这里多了两个指针。这俩指针干啥用的呢?

这里又引入了一个新的名词,虚基表指针,就是因为它的出现,才使得菱形继承没有了刚刚的问题。

虚基表指针大概是这么使用的。请看下图。

它指向了一段内存空间,里面存储着其他数据和基类数据的偏移量,这样,当有两个类同时继承了类a时
,我们只需要根据这个数据和其他数据的偏移量就可以判断它属于哪一个类了,这样就可以避免出现二义性的问题
那么我们再来算一算这两种继承方式的派生类的内存大小吧。
第一种方式的内存大小是20,第二种是24,这样算下来的话,好像第一种占用的内存空间会更小啊。实际上,我们定义的只是
最简单的数据类型啊,如果复杂一些的数据类型,那么节省下来的内存空间还是很客观的。因此,虚基类的使用不但解决了以上问题

菱形继承对象模型,如图4: 

在单继承下,基类的public 和protected 成员可以直接被访问,就像它们是派生类的成员一样,对多继承这也是正确的。但是在多继承下,派生类可以从两个或者更多个基类中继承同名的成员。然而在这种情况下,直接访问是二义的,将导致编译时刻错误。 示例:

#include 
using namespace std;

class A
{
public:
void f();
};

class B
{
public:
void f();
void g();
};

class C : public A, public B
{
public:
void g();
void h();
};

int main(){
    C c1;
    // c1.f();    产生二义性问题,访问A中的 f()? or B的 f() ?
    //通过指定成员名,限定消除二义性
    c1.A::f();
    c1.B::f();
}
使用成员名限定法可以消除二义性,但是更好的解决办法是在类C中定义一个同名函数 f(), 类C中的 f() 再根据需要来决定调用 A::f() or B::f(), 这样 c1.f() 将调用 C::f().

当一个派生类从多个基类派生类,而这些基类又有一个共同的基类,则对该基类中说明的成员进行访问时,也可能会出现二义性。 示例:

//  派生类 B1,B2 继承相同的基类 A, 派生类 C 继承 B1, B2
class A
{
public:
int a;
};
class B1 : public A
{
private:
int b1;
};
class B2 : public A
{
private:
int b2;
};
class C : public B1, public B2
{
public:
int f();
private:
int c;
};

int main(){
    C c1;
    c1.a();
    c1.A::a();
    c1.B1::a();
    c1.B2::a();
    return 0;
}
c1.a; c1.A::a; 这两个访问都有二义性,c1.B1::a; c1.B2::a;是正确的: 类C的成员函数 f() 用如下定义可以消除二义性:

int C::f()
{ 
    retrun B1::a + B2::a; 
}
由于二义性的原因,一个类不可以从同一个类中直接继承一次以上。

菱形继承二义性问题的解决方案
 

虚基类
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次。如下图所示:

graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A-->B-->D 这一路,另一份来自 A-->C-->D 这一条路。当D访问从A中继承的数据时,变一起将无法决定采用哪一条路传过来的数据,于是便出现了虚基类。

在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突,而且很少有这样的需求。使用虚基类,可以使得在派生类中只保留间接基类的一份成员。

声明虚基类只需要在继承方式前面加上 virtual 关键字,如下面示例:

#include 
using namespace std;

class A{
protected:
    int a;
public:
    A(int a):a(a){}
};

class B: virtual public A{  //声明虚基类
protected:
    int b;
public:
    B(int a, int b):A(a),b(b){}
};

class C: virtual public A{  //声明虚基类
protected:
    int c;
public:
    C(int a, int c):A(a),c(c){}
};

class D: virtual public B, virtual public C{  //声明虚基类
private:
    int d;
public:
    D(int a, int b, int c, int d):A(a),B(a,b),C(a,c),d(d){}
    void display();
};
void D::display(){
    cout<<"a="<构造函数,与以往的用法有所不同。 以往,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在,由于虚基类在派生类中只有一份成员变量,所以对这份成员变量的初始化必须由派生类直接给出。如果不由最后的派生类直接对虚基类初始化,而由虚基类的直接派生类(如类B和类C)对虚基类初始化,就有可能由于在类B和类C的构造函数中对虚基类给出不同的初始化参数而产生矛盾。所以规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

在上述代码中,类D的构造函数通过初始化表调了虚基类的构造函数A,而类B和类C的构造函数也通过初始化表调用了虚基类的构造函数A,这样虚基类的构造函数岂非被调用了3次?大家不必过虑,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

最后请注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍然会出现对基类的多次继承。

派生类的构造函数

  派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,派生类中新增的成员在派生类的构造函数中初始化。 派生类构造函数的语法:

派生类名::派生类名(参数总表):基类名1(参数表1),基类名(参数名2)....基类名n(参数名n),内嵌子对象1(参数表1),内嵌子对象2(参数表2)....内嵌子对象n(参数表n)
{
   派生类新增成员的初始化语句;
}

注:构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。
 2. 如果基类中没有不带参数的构造函数,那么在派生类的构造函数中必须调用基类构造函数,以初始化基类成员。
3. 派生类构造函数执行的次序:

虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。
基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。
对象的vptr被初始化;
成员对象构造
如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;
派生类自己的构造函数。

派生类的析构函数 

 

派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。

实例:

#include 
#include 
using namespace std;
// 基类 B1
class B1
{
public:
    B1(int i)
    {
        cout<<"constructing B1 "<B1->B3)
class C: public B2, public B1, public B3
{
public:
    C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b)
    {
        //B1,B2的构造函数有参数,B3的构造函数无参数
        //memberB2(d), memberB1(c)是派生类对自己的数据成员进行初始化的过程、
        //构造函数执行顺序, 基类(声明顺序)-> 内嵌成员对象的构造函数(声明顺序) -> 派生类构造函数中的内容
    }
private:
    B1 memberB1;
    B2 memberB2;
    B3 memberB3;
};

int main() 
{ 
    C obj(1,2,3,4);
    return 0; 
}

/* 输出结果 */
/*
constructing B2 2
constructing B1 1
constructing B3
constructing B1 3
constructing B2 4
constructing B3
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
*/

 继承的赋值兼容规则

 

赋值兼容 : 赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。

赋值兼容规则中所指的替代包括:

 继承机制中对象之间如何转换?指针和引用之间如何转换? 

  • 派生类的对象可以赋值给基类对象;
  • 派生类的对象可以初始化基类的引用;
  • 派生类对象的地址可以赋给指向基类的指针。 在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
  1. 向上类型转换

将派生类指针或引用转换为基类的指针或引用被称为向上类型转换,向上类型转换会自动进行,而且向上类型转换是安全的。

  1. 向下类型转换

将基类指针或引用转换为派生类指针或引用被称为向下类型转换,向下类型转换不会自动进行,因为一个基类对应几个派生类,所以向下类型转换时不知道对应哪个派生类,所以在向下类型转换时必须加动态类型识别技术。RTTI技术,用dynamic_cast进行向下类型转换。

继承 中的友元 

友元关系不能继承,也就是说基类友元不能访问子类私有和保护的成员。 
【例】//友元与继承
class Person
{
    friend void Display(Person& p, Student& s);
protected:
        string _name; //姓名
};

class Student : public Person
{
protected:
    int _stuNum; //学号
};

void Display(Person& p, Student& s)
{
    cout << p._name << endl;
    cout << s._name << endl;
    cout << s._stuNum << endl;
}

void Test()
{
    Person p;
    Student s;
    Display(p, s);
}

继承中的静态成员 

基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。即:如果我们重新定义了一个静态成员,所有在基类中的其他重载函数会被隐藏。如果我们改变了基类中一个函数的特征,所有使用函数名字的基类版本都将会被隐藏。 
【例】//继承与静态成员
class Person
{
public:
    Person()
    {
        ++_count;
    }
protected:
    string __name;  //姓名
public:
    static int _count; //统计人的个数
};

int Person::_count = 0;

class Student : public Person
{
protected:
    int _stuNum; //学号
};

class Graduate : public Student
{
protected:
    string _seminarCourse; //研究科目
};

void Test()
{
    Student s1;
    Student s2;
    Student s3;

    Graduate s4;

    cout << "人数" << Person::_count << endl;     Student::_count = 0;     cout << "人数:" << Person::_count << endl;
}

int main()
{
    Test();
    system("pause");
    return 0;
}

继承中的虚函数  

(1)概念:类的成员函数前面加virtual关键字。 
(2)虚函数重写:当在子类中定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写了父类的这个虚函数。如图5所示: 
 
图5 虚函数表示图

(3)总结: 
1)派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外)。 
2)基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。 
3)只有类的成员函数才能定义虚函数。 
4)如果在类外定义虚函数,只能在声明函数是加virtual,在类外定义函数时不能加virtual。 
5)静态成员函数不能定义虚函数。 
6)构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但是最好不要,因为容易在使用时混淆。 
7)不要在构造函数和析构函数里调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。 
8)最好把析构函数声明为虚函数。因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖。看图6: 
【例】

//虚函数
class Person
{
public:
    virtual void BuyTickets()
    {
        cout << "买票" << endl;
    }
protected:
    string _name; // 姓名
};

class Student : public Person
{
public:
    virtual void BuyTickets()
    {
        cout << "买票-半价" << endl;
    }
protected:
    int _num;//学号
};

void Fun(Person& p)
{
    p.BuyTickets();
}

void Test()
{
    Person p;
    Student s;
    Fun(p);
    Fun(s);
}

int main()
{
    Test();
    system("pause");
    return 0;
}

  继承与组合  

什么是组合?

  1. 一个类里面的数据成员是另一个类的对象,即内嵌其他类的对象作为自己的成员;创建组合类的对象:首先创建各个内嵌对象,难点在于构造函数的设计。创建对象时既要对基本类型的成员进行初始化,又要对内嵌对象进行初始化。
  2. 创建组合类对象,构造函数的执行顺序:先调用内嵌对象的构造函数,然后按照内嵌对象成员在组合类中的定义顺序,与组合类构造函数的初始化列表顺序无关。然后执行组合类构造函数的函数体,析构函数调用顺序相反。

组合与继承优缺点?

一:继承

继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。

继承的缺点有以下几点:

①:父类的内部细节对子类是可见的。

②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。

③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。

二:组合

组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。

组合的优点:

①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。

②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。

③:当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。

组合的缺点:①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进行定义。

继承的有关问题 

用C语言实现C++的继承?
#include 
using namespace std;

//C++中的继承与多态
struct A
{
    virtual void fun()    //C++中的多态:通过虚函数实现
    {
        cout<<"A:fun()"<fun();    //调用父类的同名函数
    p1 = &b;      //让父类指针指向子类的对象
    p1->fun();    //调用子类的同名函数


    //C语言模拟继承与多态的测试
    _A _a;    //定义一个父类对象_a
    _B _b;    //定义一个子类对象_b
    _a._fun = _fA;        //父类的对象调用父类的同名函数
    _b._a_._fun = _fB;    //子类的对象调用子类的同名函数

    _A* p2 = &_a;   //定义一个父类指针指向父类的对象
    p2->_fun();     //调用父类的同名函数
    p2 = (_A*)&_b;  //让父类指针指向子类的对象,由于类型不匹配所以要进行强转
    p2->_fun();     //调用子类的同名函数
}

 

多继承的优缺点,作为一个开发者怎么看待多继承

  1. C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
  2. 多重继承的优点很明显,就是对象可以调用多个基类中的接口;
  3. 如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性
  4. 加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。
  5. 使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。

覆盖、重载和隐藏的区别?

覆盖是派生类中重新定义的函数,其函数名、参数列表(个数、类型和顺序)、返回值类型和父类完全相同,只有函数体有区别。派生类虽然继承了基类的同名函数,但用派生类对象调用该函数时会根据对象类型调用相应的函数。覆盖只能发生在类的成员函数中。

隐藏是指派生类函数屏蔽了与其同名的函数,这里仅要求基类和派生类函数同名即可。其他状态同覆盖。可以说隐藏比覆盖涵盖的范围更宽泛,毕竟参数不加限定。

<

p style="margin-left:0pt;">重载是具有相同函数名但参数列表不同(个数、类型或顺序)的两个函数(不关心返回值),当调用函数时根据传递的参数列表来确定具体调用哪个函数。重载可以是同一个类的成员函数也可以是类外函数。