继承与虚继承

允许程序员在保持原有的特性基础上进行扩展,增加功能,这样产生新的类,称作是派生类。继承呈现了面向对象程序设计的层析结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用

1、定义基类:

基类有两种成员函数:一种是希望其派生类进行覆盖覆盖(virtual虚函数,动态绑定),另一种是派生类直接继承而不要改变的函数。

2、定义派生类:

派生类继承了基类的:1)所有成员变量,包括static静态成员变量。2)作用域,但是友元关系没有继承。

2.1 派生类向基类的类型转换:一个派生类包括两部分:从基类继承的对象和自己定义的子对象。

由于派生类对象中含有基类继承的部分(继承的关键),我们可以把派生类对象当成基类对象来使用。

Base item;// 基类对象item
Derived bulk;// 派生类对象bulk

Base *p=&item;// p指向Base对象
p=&bulk;// p指向派生类对象bulk的Base部分

2.2 派生类构造/析构函数:首先初始化基类部分,再按照声明顺序初始化派生类的成员。析构时先析构派生类,再析构基类。

3、类型转换与继承(只对指针或引用有效)

只存在派生类向基类的类型转化,因为每个派生类对象都包含一个基类对象

Derived bulk;// 派生类对象
Base* item=&bulk;// item指向派生类对象bulk的基类部分。

不存在基类向派生类的类型转化

Base base;// 基类对象
Derived* bulk=&base// 错误,基类可能不是派生类的一部分。

4、抽象基类

纯虚函数:基类中声明但是没有实现,需要在派生类中实现

Class Base{
   double net_price() const=0;// 纯虚函数,基类中没定义
};

含有纯虚函数的类是抽象基类。

性质:不能创建抽象基类的对象,必须是在派生类中给出net_price的定义后,可以创建派生类的对象。

5、访问控制与继承

5.1 protected 关键字访问类的对象,派生类成员函数只能访问派生类对象中的基类部分,而不能直接访问基类对象。

Class Base{
   protected:
         int a;
}
class Derived:public Base{
   friend void func(Derived&);// 正确,派生类函数只能访问派生类对象中的基类部分
   friend void func(Base&);// 错误,派生类函数不能直接访问基类对象
}

虚继承

目的:解决多继承(菱形继承)时很容易产生命名冲突。

类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A-->B-->D 这一路,另一份来自 A-->C-->D 这一条路。

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

为了解决这个问题,C++提供了虚基类,使得在派生类中只保留间接基类的一份成员。

#include <iostream>
using namespace std;

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

class B : virtual public A {// 加virtual 虚继承
protected:
    int b;
public:
    B(int a, int b) : A(a), b(b) {}
};

class C : virtual public A {// 加virtual
protected:
    int c;
public:
    C(int a, int c) : A(a), c(c) {}
};

class D : virtual public B, virtual public C {// 两个虚继承virtual 
private:
    int d;
public:
    D(int a, int b, int c, int d) : A(a), B(a, b), C(a, c), d(d) {}// 注意还要初始化虚基类A
    void display();
};

void D::display()
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
    cout << "d = " << d << endl;
}

int main()
{
    (new D(1, 2, 3, 4))->display();
    return 0;
}

PS:

1、注意D的构造函数,除了对直接基类B、C初始化,还需要对虚基类A进行初始化。(由于虚基类在派生类中只有一份成员变量,所以对这份成员变量的初始化必须由派生类直接给出。)

2、C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

发布了176 篇原创文章 · 获赞 84 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/try_again_later/article/details/100523363