白话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(),前者是父类的成员函数,可见子类可以访问父类的成员函数,此外也可以证明可以顺利访问自己的成员函数,同时,由于没有报语法错误,也可以证明子类访问父类的数据成员和自己的数据成员。