代码改变世界

白话C++系列(18) -- 继承方式、隐藏

2016-05-16 21:53  Keiven_LY  阅读(743)  评论(0编辑  收藏  举报

继承方式

这节课来学习继承的方式。既然有继承的关系,就一定会有以某种方式来继承的问题。C++中有三种继承方式,如下:

公有继承

当两个类具有继承关系,并且是以公有继承的方式来继承的话,那么基类中的public成员将会被继承到派生类中的public下面;基类中的protected成员也会被继承到派生类的protected下面,但基类的private成员将会无法访问,也就是说,没有在派生类的private下面继承基类的private成员。这样也就造成了派生类无法像访问自己的private成员那样去访问基类的private成员。

基类成员访问属性

继承方式

派生类成员访问属性

private成员

 

public

无法访问

protected成员

protected

public成员

public

 

 

 

 

 

 

保护继承和私有继承

当两个类具有继承关系,并且是以保护继承的方式来继承的话,那么基类中的public成员和protected成员都会被继承到派生类的protected下面,但基类的private成员会被继承,但也会无法访问。

基类成员访问属性

继承方式

派生类成员访问属性

private成员

 

protected

无法访问

protected成员

protected

public成员

protected

 

 

 

 

 

 

当两个类具有继承关系,并且是以私有继承的方式来继承的话,那么基类中的public成员和protected成员都会被继承到派生类的private下面,但基类的private成员会被继承,不过也会无法访问。

基类成员访问属性

继承方式

派生类成员访问属性

private成员

 

private

无法访问

protected成员

private

public成员

private

 

 

 

 

 

 

在这三种继承方式当中,以private方式最为特殊,为了说明这种特殊性,我们来看前面学习过的例子。

在这个例子中,我们可以看到,在线段类中有两个坐标类的对象(一个是A点m_coorA,一个是B点m_coorB)。那么这样的一种关系呢,大家会发现,线段类只能够访问到A点和B点这两个对象的公有数据成员和公有成员函数。说道这,我们再回过头来看一看刚刚前面学过的私有继承。在私有继承中,子类的对象也只能访问父类的公有数据成员和公有的成员函数,大家是不是也觉得这两点是不是很相似呢?在C++中,我们把这个例子中的线段类和坐标类的这种关系就称之为Has a关系,也就是说在线段中有一个坐标点,这种关系是一种包含关系。而在私有继承中也是一种包含关系,即当我们定义了一个子类的对象的时候,子类对象中就包含了一个父类对象(因为它只能访问父类中公有数据成员和公有的成员函数),所以,从本质上来说,私有继承也是一种Has a的关系。

继承中的特殊关系

在C++中,有两个非常重要的概念,那就是覆盖和隐藏。本节主要讲解隐藏。那么什么是隐藏呢?

隐藏

下面通过一个例子来说明隐藏这个概念。

假设我们先定义了一个父类A,并在父类A中定义了一个成员函数ABC(),然后,我们又定义了一个子类B,子类B公有继承了父类A,并且在子类B中也定义了一个同名的成员函数ABC()。这个时候,大家会发现,A中有一个ABC(),B中也有一个ABC(),而B继承了A之后,理论上在B中就应该拥有了A中所定义的成员函数ABC()。这时,子类B中的ABC()函数就会隐藏掉父类A中的ABC()函数。隐藏的特性也主要体现在,当实例化B的对象的时候,使用该对象只能够直接的访问到子类B中的ABC()成员函数,而无法访问父类A中的ABC()成员函数。从使用的体验上来说,父类A中的ABC()函数就似乎已经被隐藏以来了,但是实际上,父类A中的ABC()函数确实被继承到了子类B中,并且可以通过特殊的手段访问到父类A中的ABC()函数,所以,我们就将这种特性称为隐藏。当然,同名的隐藏不仅限于成员函数,从语法的角度来说,其实,同名的数据成员也具有隐藏的特征。比如说父类A中有一个数据成员x,子类B中也有一个数据成员x,那么,当它们具有继承关系后,父类中的x就会被隐藏起来,只不过因为存在父子关系的两个类之间的数据成员如果同名实在没有什么实际的意义,所以在实际的使用当中,十分罕见。综上所述,我们归纳为三个关键字:父子关系、成员同名、隐藏。那么这三个关键字呢就已经描述了隐藏的概念。接下来看一个代码的实例。

在这个例子中,我们定义了一个Person人这个类,在这个类中有一个成员函数play(),也就是玩耍的意思(作为人来说,都喜欢玩耍),另外还有一个数据成员叫做名字。另外,还定义了一个Soldier类(士兵),并且公有继承了Person这个类,在士兵这个类中,也定义了有一个同名的成员函数play()(注意,肯定士兵的玩法与普通人类的玩法会有所不同),当然,士兵这个类中还有其他的成员函数(work())以及数据成员(Code)(因为,对于士兵来说,都会有各自的编号)。接下来,我们看一下真正访问的时候,注意红色标记的代码。

当我们使用Soldier这个类去实例化一个soldier对象之后,那么我们使用这个对象直接去调用play()的时候,那么它将调用到Soldier这个类自己定义的play()函数。而如果我们想要通过soldier这个对象来调用到他的父类中的play()函数,我们就必须通过第二行红色代码方式(soldier.Person::play();)。如果我们在定义数据成员的时候,将父类的数据成员与子类的数据成员定义成同名的话,比如说在Person类中定义了一个string类型的编号(string code;),在Soldier类中也定义了一个int类型的编号(int code;),这种定义习惯非常不可取,在概念上容易混淆,如果想要去访问它呢,我们往往会在成员函数中去访问,因为这两个数据成员都定义在了protected限定符的下面,用实例化的对象无法直接访问到(五路子类还是父类),那它使用在各自的成员函数中。我们来看一下,如果在Soldie的成员函数中去使用code的话呢(code = “1234”;),这个code访问到的就是Soldier这个类本身定义的code,而如果要访问从Person这个类继承下来的这个code数据成员呢,就必须使用(Person::code = “5678”;)。

隐藏代码实践

题目描述:

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

/* 继承关系中的隐藏

要求:

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

          2. Soldier类,数据成员:无,成员函数:构造函数、play()、work()

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

程序框架:

头文件(Person.h

#include<string>
usingnamespace std;

classPerson
{
public:
    Person();
    void play();
protected:
    string m_strName;
};

源程序(Person.cpp

#include"Person.h"
#include<iostream>
usingnamespace std;

Person::Person()
{
    m_strName = "Mery";
}
voidPerson::play()
{
    cout <<"Person---play()"<< endl;
    cout << m_strName << endl;
}

头文件(Soldier.h

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

classSoldier:publicPerson
{
public:
    Soldier();
    void play();
    void work();
protected:
};

源程序(Soldier.cpp

#include"Soldier.h"
#include<iostream>
usingnamespace std;

Soldier::Soldier()
{
}
voidSoldier::play() 
{
    cout <<"Soldier---play()"<< endl;
}
voidSoldier::work()
{
    cout <<"work()"<< endl;
}

主调函数(demo.cpp

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

usingnamespace std;

int main()
{
    Soldier soldier;
    soldier.play();
    soldier.work();
    system("pause");
    return 0;
}

运行结果:

从运行结果来看,当我们调用play()这个函数的时候,打印出来的是Soldier类的play()函数,当我们调用work()这个函数的时候,打印出来的是Soldier类的work()函数。如果我们想要去打印出Person这个类的play()函数怎么办呢?则就需要用这样的调用方式(soldier.Person::play()),我们将这个调用添加到main函数中,如下:

int main()
{
    Soldier soldier;
    soldier.play();
    soldier.work();
    soldier.Person::play();
    system("pause");
    return 0;
}

运行结果:

从运行结果来看,除了打印出我们之前的信息之外,还打印出了Person这个类中的play()函数,并且将Mery这个名字(Person的构造函数的原因)也一并打印出来了。此外,还要向大家说明另外一个问题,什么问题呢?我们来想一想,当前的play函数不仅同名,而且它的参数也是相同的(都是无參的),那如果当他的参数不同的时候,是不是也能形成隐藏的效果呢?我们来试一试。

首先,我们修改一下Soldier这个类下的play函数,我们给这个play函数传入一个参数(void play(int x)),这样Soldier类中的play函数与Person类中的play函数就变成了同名不同參的函数了。这样,我们修改一下main函数,如下:

int main()
{
    Soldier soldier;
    soldier.play(7);
    soldier.work();
    soldier.play();
    system("pause");
    return 0;
}

点击运行,报错如下:

原因是main函数中标红语句少一个参数,换句话说,我们实例化的soldier要调用play函数,只能调用Soldier这个类下面的play,而不能调用继承来的play函数,不管继承来的play与Soldier这个类下面的原有的play是不是参数相同,也就是说,它们之间无法形成重载,只能进行隐藏。如果想要调用基类的paly,必须使用刚才上面的调用方式(soldier.Person::play())。

下面我们继续一个例子,在这个例子当中,我们在Soldier.h中多声明一个数据成员,使这个数据成员与Person.h下面的数据成员同名,从而这两个同名的数据成员也有了继承关系。我们如果在work这个函数中去访问的时候,如果我们直接给m_strName赋值的话,它只能给Soldier这个类下面的m_strName赋值,而不能给基类Person下面的m_strName赋值,我们来试一试。修改Soldier.cpp如下:

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

Soldier::Soldier()
{
}
void Soldier::play(intx) 
{
    cout << m_strName << endl;
    cout <<"Soldier---play()"<< endl;
}
void Soldier::work()
{
    m_strName = "Jim";
    cout <<"work()"<< endl;
}

主调函数:

int main()
{
    Soldier soldier;
    soldier.work();
    soldier.play(7);
    soldier.Person::play();
    system("pause");
    return 0;
}

运行结果:

从运行结果来看,打印出来的数据成员是Jim,这就意味着,在Soldier.cpp的work函数中给m_strName赋值的Jim,那么这个m_strName就是Soldier这个类中的数据成员,而不是基类Person中的数据成员。所以,当子类和父类的数据成员同名的时候,子类的数据成员直接使用也只能使用它自身的,而无法使用继承下来的同名的数据成员。如果想要使用继承下来的数据成员,怎么办呢?我们只能这样:(Person::m_strName = “Keiven”),在打印的时候也必须使用这种方式(cout << Person::m_strName << endl;)。我们将这两个代码添加到Soldier.cpp中,如下:

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

Soldier::Soldier()
{
}
void Soldier::play(intx) 
{
    cout << m_strName << endl;
    cout <<Person::m_strName << endl;
    cout <<"Soldier---play()"<< endl;
}
void Soldier::work()
{
    m_strName = "Jim";
    Person::m_strName = "Keiven";
    cout <<"work()"<< endl;
}

然后运行,运行结果如下:

我们看到,当实例化的soldier调用了work函数后,那么,在work中已经给子类和父类的数据成员m_strName分别赋了值,于是又调用了Soldier中的play函数,就打印出了子类和父类的数据成员m_strName的值(Jim和Keiven)。