代码改变世界

白话C++系列(20) -- 多继承和多重继承

2016-05-22 22:11  Keiven_LY  阅读(1069)  评论(0编辑  收藏  举报

多继承和多重继承

多重继承

什么是多重继承呢?如果有这样三个类:人类、士兵类、步兵类。其中,士兵类继承了人类,步兵类继承了士兵类,我们就称这三个类之间存在多重继承的关系(如下所示)。

如果这三个类在继承的时候,都使用的是public方式,那么它们也存在着以下关系:士兵是一个人,步兵是一个士兵,步兵也是一个人(如下)

那么,具体到代码上,我们可以这样来写:

多继承

知道了多重继承,那么多继承又是什么呢?

所谓多继承,我们先来看一个例子,先定义一个工人类,然后定义一个农民类。如果还有一个类,它不仅继承了工人类,而且还继承了农民类,在这里我们暂且叫这个类为农民工类。那么在这种情况下,我们发现,一个子类同时有两个父类(或者说一个派生类同时有两个基类),那么这样的关系,我们称之为多继承。它与多重继承是完全不同的。

在多继承的情况下,如果农民工在继承工人和农民的时候,都是以public方式继承的话,那么它们还存在着这样一种关系:农民工是一个工人,农民工是一个农民,但是,工人和农民这两个类本身是平行的,如下:

那么,具体到代码上,我们可以这样来写:

多重继承编码实践

题目描述:

/* ****************************************************************************  */

/*

多重继承

要求:1.Person类,数据成员: m_strName,成员函数:构造函数,析构函数,play()

          2.Soldier类,数据成员:m_iAge,成员函数:构造函数,析构函数,work()

          3.Infantry类,数据成员:无,成员函数:构造函数,析构函数,attack()

          4.定义函数test1(Person p)  test2(Person &p)  test3(Person *p)

*/

/* ***************************************************************************  */

程序框架:

头文件(Person.h

#include<string>
using namespace std;

class Person
{
public:
    Person(string name = "Jim");
    virtual ~Person();
    void play();
protected:
    string m_strName;
};

源程序(Person.cpp

#include"Person.h"
#include<iostream>
using namespace std;

Person::Person(string name)
{
    m_strName = name;
    cout <<"Person()"<< endl;
}
Person::~Person()
{
    cout <<"~Person()"<< endl;
}
void Person::play()
{
    cout <<"Person---play()"<< endl;
    cout << m_strName << endl;
}

头文件(Soldier.h

#include"Person.h"//这里如果不包含这个头文件,编译时就会出现“Person”未定义基类

class Soldier:public Person
{
public:
    Soldier(string name = "James", int age = 20); //这里也给定默认参数值
    ~Soldier();
    void work();
protected:
    int m_iAge;
};

源程序(Soldier.cpp

#include"Soldier.h"
#include<iostream>
using namespace std;

Soldier::Soldier(string name, int age)
{
    m_strName = name;
    m_iAge = age;
    cout <<"Soldier()"<< endl;
}
Soldier::~Soldier()
{
    cout <<"~Soldier()"<< endl;
}
void Soldier::work()
{
    cout << m_strName << endl;
    cout << m_iAge << endl;
    cout <<"Soldier--work()"<< endl;
}

头文件(Infantry.h

//步兵类
#include"Soldier.h"

class Infantry:public Soldier
{
public:
    Infantry(string name = "Jack", int age = 30);
    ~Infantry();
    void attack();
};

源程序(Infantry.cpp

#include<iostream>
#include"Infantry.h"
using namespace std;

Infantry::Infantry(string name, int age)
{
    m_strName = name;
    m_iAge = age;
    cout <<"Infantry()"<< endl;
}
Infantry::~Infantry()
{
    cout <<"~Infantry()"<< endl;
}
void Infantry::attack()
{
    cout << m_strName << endl;
    cout << m_iAge << endl;
    cout <<"Infantry--attack()"<< endl;
}
#include<iostream>
#include<stdlib.h>
#include"Infantry.h"
using namespace std;

void test1(Personp)
{
    p.play();
}

void test2(Person &p)
{
    p.play();
}
void test3(Person *p)
{
    p->play();
}

int main()
{
    Infantry infantry;

    system("pause");
    return 0;
}

在main函数中,此时我们只实例化一个步兵对象,看一看程序结果:

程序打印出三行,分别是Person类的构造函数,Soldier类的构造函数,以及Infantry类的构造函数,这就意味着,我们在实例化步兵这个对象时,先执行了Person构造函数,又执行了Soldier构造函数,最后执行了Infantry本身的构造函数。为什么会是这样呢?其实作为最底层的子类,它如果想要实例化一个自己的对象,那它就必须执行其继承链上的每一个类,而在Infantry的继承链中有Person类还有Soldier类,所以要先执行Person的构造函数,再执行Soldier的构造函数,最后才执行自己本身的构造函数。当然,在销毁的时候,其析构函数会按照构造函数的逆序执行,也就是说,先执行Infantry的析构函数,再执行Soldier的析构函数,最后执行Person的析构函数。

接下来我们通过对test1、test2和test3这三个函数的调用来说明一个子类的对象可以作为函数参数传入这三个函数当中。大家注意,这三个函数要求的参数是Person的对象、引用、指针。那么,作为步兵这样的一个子类,传入进去之后呢,也应该是没有问题的,我们来一起实践一下。

在main函数中,分别调用test1、test2和test3函数,如下:

int main()
{
    Infantry infantry;
    test1(infantry);
    test2(infantry);
    test3(&infantry);

    system("pause");
    return 0;
}

我们来看一下运行结果:

从运行结果可以看到,前面三行是实例化步兵对象的结果。然后,由于test1、test2和test3这三个函数都在其中调用了play函数,所以,我们可以看到有三个”Person---play()”被打印出来,打印出来的同时,还将Jack也打印出来了。而Jack就来自于Infantry构造函数中的默认参数。通过这个打印结果,我们可以得到这样的结论:无论继承关系有多少层,它们只要保持着直接或者间接的继承关系,那么子类都可以与自己的直接父类或者间接父类称之为is-a的关系,并且能够通过父类的指针对直接子类或间接子类的对象进行相应的操作。

多继承代码实践

题目描述:

/* ****************************************************************************  */

/*

多继承

要求:1.Farmer类,数据成员: m_strName【农民姓名】,成员函数:构造函数,析构函数,sow()【播种函数】

          2.Worker类,数据成员:m_strCode【工人工号】,成员函数:构造函数,析构函数,carry()【搬运函数】

          3.MIgrantWorker类,数据成员:无,成员函数:构造函数,析构函数

*/

/* ***************************************************************************  */

程序框架:

头文件(Worker.h

#include<string>
using namespace std;

class Worker
{
public:
    Worker(string code = "001");
    virtual ~Worker(); //虚析构函数
    void carry();
protected:
    string m_strCode;
};

源程序(Worker.cpp

#include"Worker.h"
#include<iostream>
using namespace std;


Worker::Worker(string code)
{
    m_strCode = code;
    cout <<"Worker()"<< endl;
}
Worker::~Worker()
{
    cout <<"~Worker()"<< endl;
}
void Worker::carry()
{
    cout << m_strCode << endl;
    cout <<"Worker---carry()"<< endl;
}

头文件(Farmer.h

#include<string>
using namespace std;

class Farmer
{
public:
    Farmer(string name = "Jack");
    virtual ~Farmer();
    void sow();
protected:
    string m_strName;
};

源程序(Farmer.cpp

#include"Farmer.h"
#include<iostream>
using namespace std;

Farmer::Farmer(string name)
{
    m_strName = name;
    cout <<"Farmer()"<< endl;
}

Farmer::~Farmer()
{
    cout <<"~Farmer()"<< endl;
}
void Farmer::sow()
{
    cout << m_strName << endl;
    cout <<"Farmer---sow()"<< endl;
}

头文件(MigrantWorker.h

#include"Worker.h"
#include"Farmer.h"

class MigrantWorker:public Farmer, public Worker//农民工类继承农民类和工人类
{
public:
    MigrantWorker(string name,string code);
    ~MigrantWorker();
};

源程序(MigrantWorker.cpp

#include"MigrantWorker.h"
#include<iostream>
using namespace std;

//采用初始化列表的方式将农民工的姓名和工号分别传递给农民的m_strName和工人的m_strCode
MigrantWorker::MigrantWorker(string name, string code):Farmer(name),Worker(code)
{
    cout <<"MigrantWorker()"<< endl;
}
MigrantWorker::~MigrantWorker()
{
    cout <<"~MigrantWorker()"<< endl;
}

主调程序(demo.cpp

#include<iostream>
#include<stdlib.h>
#include"MigrantWorker.h"
using namespace std;

int main()
{
    //采用堆的方式实例化农民工对象
    MigrantWorker *p = new MigrantWorker("Merry", "100"); //姓名将来传给农民类,工号传给工人类
    p->carry();
    p->sow();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

我们来看一下运行结果:

从运行结果来看:前面有三个构造函数的执行,分别是农民的构造函数、工人的构造函数以及农民工的构造函数。这就是说,在实例化一个子类的对象时,它会先调用父类的构造函数,如果它有多个父类,那么它会依次调用每一个父类的构造函数,其调用顺序与初始化列表的顺序是一样的。再来看其他的打印结果,先是打印出工号100,接着打印出Worker中的carry函数(这是由于对象p调用了carry这个函数),然后打印出姓名Merry,接着打印出Farmer中的sow函数(这是由于对象p调用了sow这个函数)。最后三行打印出了析构函数,其打印顺序与构造函数的打印顺序正好相反。