代码改变世界

白话C++系列(17) -- 继承

2016-05-09 20:26  Keiven_LY  阅读(808)  评论(0编辑  收藏  举报

C++远征之继承篇

为什么要有继承?

我们先从下面这个例子开始讲起。

我们在这里定义了一个人的类(Person),其中有数据成员:姓名和年龄,还有一个成员函数吃饭(eat)。我们还定义了一个工人的类(Worker),我们知道工人是人类的一种,所以其肯定也有自己的姓名和年龄,肯定也要吃饭。当然,对于工人来说,其还要工作,因为工作才会有薪水,所以,我们发现,作为工人来说,其比人类具有更多的属性,并且具有所有的人类特性。那么既然人类中的所有数据成员和成员函数放在工人的类中都适用,那能不能在这种情况下,就让工人类和人类产生某种关系,从而在定义工人类的时候,不重复写这些代码,而减轻程序员的工作呢?其实,这在C++中是可以的,几遍如此,也是有前提条件的,其前提条件就是发生关系的这两个类必须具备包含关系(如下)

有了这样的概念关系,我们就可以将程序代码优化成如下形式:

这样在定义工人类的时候,就不需要再定义人类共有的属性,只需要定义自己特有的属性。这就是说工人类继承了人这个类。工人类就是人这个类的派生类,人这个类就是工人类的基类;也可以把人这个类和工人类分别称为父类和子类。

内存中的对象

在上面的内容中,我们已经初步讲了继承的语法,下面将为大家讲述在内存中作为对象来说,子类和父类(或者说派生类和基类)是怎样的关系。我们还是以刚才的人这个类和工人类为例,我们来看一下,在内存中人这个类如果实例化一个对象,那么它就会有两个数据成员(一个是姓名,一个是年龄),而工人类在实例化一个对象后,虽然我们在工人类中去定义姓名和年龄,但是因为工人类继承了人这个类,所以由工人类实例化出一个对象后,就已经包含了人这个类中的两个数据成员(姓名和年龄),同时还有一个自己特有的数据成员(薪水)。这就是人这个类和工人类实例化对象后在内存中的分布(如下)

继承代码实践

题目描述:

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

/*  继承

要求:

        1.定义Person类

        数据成员:姓名(m_strName)和年龄(m_iAge)

        成员函数:构造函数、析构函数、eat()函数

        2.定义Worker类

        公有继承Person类,特有数据成员(工资  m_iSalary)

        成员函数:构造函数、析构函数、work()函数

    目的:

        1.实例化Worker对象的时候,到底是先调用谁的构造函数,

          在销毁Worker这个对象的时候,又是先调用谁的析构函数

        2.子类继承了父类后,观察是否继承了父类的数据成员和成员函数

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

程序框架:

头文件(Person.h

#include<string>
using namespace std;

class Person
{
public:
    Person();
    ~Person();
    void eat();
    string m_strName;
    int  m_iAge;
};

源程序(Person.cpp

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

Person::Person()
{
    cout <<"Person()"<< endl;
}
Person::~Person()
{
    cout <<"~erson()"<< endl;
}

voidPerson::eat()
{
    cout <<"eat"<< endl;
}

头文件(Worker.h

#include"Person.h"

class Worker:public Person//Worker公有继承Person类
{
public:
    Worker();
    ~Worker();
    void work();
    int m_iSalary;
};

源程序(Worker.cpp

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

Worker::Worker()
{
    cout <<"Worker()"<< endl;
}

Worker::~Worker()
{
    cout <<"~Worker()"<< endl;
}
void Worker::work()
{
    cout <<"work()"<< endl;
}

主调程序(demo.cpp):

根据程序的第1个目的是看实例化Worker对象的时候,到底是先调用谁的构造函数,在销毁Worker这个对象的时候,又是先调用谁的析构函数,所以我们在这里只在堆上实例化一个Worker对象,然后再销毁它

#include<iostream>
#include<stdlib.h>
#include"Worker.h"

using namespace std;

int main()
{
    Worker *p = new Worker();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

运行结果:

从运行结果来看,我们实例化了一个Worker类的对象(也就是子类或派生类),首先执行的是父类(也就是基类)的构造函数。可见,如果想要实例化一个派生类,必然先要实例化一个基类(当然,这种实例化是隐性的)。而在销毁的时候,先执行的是子类的析构函数,后执行的是父类的析构函数,可见析构函数的执行顺序与构造函数的执行顺序刚好相反。

根据第2个目的:子类继承了父类后,观察是否继承了父类的数据成员和成员函数

#include<iostream>
#include<stdlib.h>
#include"Worker.h"

using namespace std;

int main()
{
    Worker *p = new Worker();
    p->m_strName = "Keiven";
    p->m_iAge = 20;
    p->eat();
    p->m_iSalary = 5000;
    p->work();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

运行结果:

从运行结果看到,除了打印出构造函数和析构函数的相关调用之外,还打印出eat()和work(),前者是父类的成员函数,可见子类可以访问父类的成员函数,此外也可以证明可以顺利访问自己的成员函数,同时,由于没有报语法错误,也可以证明子类访问父类的数据成员和自己的数据成员。