第13章-类继承

首先说明一下为什么要用继承。说白了继承的功能就是让你能在一个类基础上继续开发,添加新功能,添加新数据,修改一些行为等。尤其是当一个类很庞大很复杂的时候,继承一是能够很好的保护已有的类设计,而来能够让你专注于你要增加或修改的部分,而不必过多的考虑已有部分。有了这两条指导规则,就有了理解继承规则的方向:尽量保护好基类,尽量方便快捷的增加新功能。

OK,直接上代码:

//tabtenn1.h
#ifndef CH13TEST_HEAD_H
#define CH13TEST_HEAD_H

#include<string>
using std::string;

class TableTennisPlayer
{
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer(const string& fn = "none", const string& ln = "none", bool ht = false);
    void Name() const;
    bool HasTable() const {return hasTable;}//@1
    void ResetTable(bool v) {hasTable = v;}//@2
};

class RatedPlayer : public TableTennisPlayer//@3
{
private:
    unsigned int rating;//@4
public:
    RatedPlayer(unsigned int r = 0, const string& fn = "none", const string& ln = "none", bool ht = false);//@5
    RatedPlayer(unsigned int r, TableTennisPlayer& tp);//@6
    unsigned int Rating() const {return rating;}//@7
    void ResetRating(unsigned int r) {rating = r;}//@8
};

#endif //CH13TEST_HEAD_H

做一下说明:
基类为TableTennisPlayer类,子类为RatedPlayer类。
基类中没啥好说的,只需要知道@1@2处为内联函数就好了。
继承的子类RatedPlayer:
@3处。:表征RatedPlayer是继承自TableTennisPlayer。public表示为公有派生。派生类对象包含基类对象。使用公有派生,基类公有成员成为子类公有成员,基类私有成员子类只能通过基类公有和保护方法访问。
@4@7@8处。子类可以添加自己的数据和方法
@5@6处。子类需要添加自己的构造函数。并且构造函数必须给新成员和继承的成员提供数据

#include "tabtenn0.h"
#include <iostream>

TableTennisPlayer::TableTennisPlayer(const string &fn, const string &ln, bool ht) : firstname(fn), lastname(ln), hasTable(ht) {}

void TableTennisPlayer::Name() const
{
    std::cout<<lastname<<","<<firstname;
}

RatedPlayer::RatedPlayer(unsigned int r, const string &fn, const string &ln, bool ht) : TableTennisPlayer(fn, ln, ht), rating(r) {}//@9

RatedPlayer::RatedPlayer(unsigned int r, TableTennisPlayer &tp) : TableTennisPlayer(tp), rating(r) {}@10

@9@10处的,子类构造函数
1、必须首先使用基类构造函数创建基类对象。
2、必须通过成员初始化列表调用基类构造函数。
3、同时应初始化新增成员。

成员初始化列表只能用于构造函数!!!

OK,声明和定义都搞定了,看看使用:

#include <iostream>
#include "tabtenn0.h"


int main()
{
    using std::cout;
    TableTennisPlayer player1("Chuck", "Blizzard", true);
    TableTennisPlayer player2("Tara", "Boomdea", false);

    player1.Name();
    if (player1.HasTable())
        cout<<" : has a table \n";
    else
        cout<<" : hasn't a table \n";

    player2.Name();
    if (player2.HasTable())
        cout<<" has a table \n";
    else
        cout<<" hasn't a table \n";

    RatedPlayer rPlayer(1140, "M", "L", true);
    rPlayer.Name();//主要看这三个,发现可以完美调用基类方法。
    rPlayer.HasTable();//
    cout<<rPlayer.Rating();//

    return 0;
}

基类与子类的关系:
1、子类对象可以使用基类方法(方法不能是私有)。上面已经看了。
2、基类指针(或引用)可以指向(或引用)子类对象,而不需要显式转换。但是在调用方法时还是只能调用基类的方法,子类的方法不能通过此指针调用另外,整个过程反过来是不可以的!
3、需要基类类型参数的函数,也可以传入子类对象作为参数。

RatePlayer rplayer;
TableTennisPlayer& rt = rplayer;//基类引用引用子类对象,没毛病。
TableTennisPlayer* pt = &rplayer;//基类指针指向子类对象,没毛病。

TableTennisPlayer player;
RatePlayer& rr = player;//反之,子类引用引用基类对象是不可以的!!!
RatePlayer* pr = &player;//反之,子类指针指向基类对象是不可以的!!!

void show(const TableTennisPlayer& rt);
show(rplayer);//传入子类对象也是可以的。

好吧,如果死记硬背上面的关系可能让你放弃这门语言。。。其实归根结底继承是一种is-a的关系,就是子类is a kind of 基类。子类是一种(加了一些方法和数据)基类。
放张图(不知道CSDN的编辑器怎么缩小图片。。。):
这里写图片描述
子类肯定是基类,基类不一定是子类。结合is-a就很容易理解了。
基类-水果、子类-香蕉;香蕉是水果,水果大概率不是香蕉。

13.3多态公有继承
所有功能都是由需求触发。此功能是为了满足同一方法在基类和子类中有不同行为而产生。也就是说同一个方法的行为会根据调用它的对象是基类还是子类而产生不同的功能
实现方法有两种:
1、在子类中重新定义基类方法
2、使用虚方法

首先说一下第一种,在子类中重新定义基类的方法:

//head.h头文件中定义基类和子类,并有同名函数
class A 
{
...
void show();//基类中有个show函数
...
}

class B : public A
{
...
void show();//子类中还有个show函数
...
}
//head.cpp定义文件中分别定义好
void A::show()
{
    std::cout<<"A class show()\n";
}

void B::show()
{
    std::cout<<"B class show()\n";
}
//using.cpp

A a;
B b;
a.show();//基类对象调用方法。
b.show();//子类对象调用方法。
//显示结果:
A class show()
B class show()

由结果来看,没毛病:
基类对象调用show()时,自动调用的是基类的show()定义;
子类对象调用show()时,自动调用的是子类的show()定义。

看起来是不是完美呢?前面说过,除了用对象本身调用方法,还有指针和引用也能调用方法,这是会不会有问题呢?

//using cpp
A* a_ptr;
B* b_ptr;
A& a_ref;//当然引用定义时要一同初始化,这里仅用于示意。
B& b_ref;

根据之前的继承与类指针的关系可知,有3种指向关系:
1、基类指针a_ptr指向基类对象
2、基类指针a_ptr指向子类对象
3、子类指针b_ptr指向子类对象

//第1种、基类指针a_ptr指向基类对象:
    a_ptr = new A;
    a_ptr->show();
//输出:
A class show()
//结果:从输出来看,没毛病,调用的是基类的show()方法。讲道理也不会有毛病,我基类指针指向基类对象这么整齐的对应,不会有毛病。
//第2种、基类指针a_ptr指向子类对象:
    a_ptr = new B;
    a_ptr->show();
//输出:
A class show()
//结果:貌似有点小问题啊,虽然我指向了子类对象,但是调用的还是基类的方法。。。继续试试看
//第3种、子类指针b_ptr指向子类对象:
    b_ptr = new B;
    b_ptr->show();
//输出:
B class show()
//结果:没毛病,子类指针指子类对象,很规整,不可能有问题。

三种情况来看,指针类型和对象类型对齐的情况下不会有问题,问题出在:
基类指针指向子类时,掉用的方法是基类方法。

此时就涉及到第二种方式:使用虚方法virtual。
直说吧,虚方法就是为了解决上方:方法跟着指针类型走,而不跟着指向对象类型走的问题。即:
若通过指针或引用调用方法,
无vitual,指针或引用类型决定方法。
有vitual,所指对象类型决定方法。
将基类和子类的show()方法加上virtual后做下对比测试:

//同样还是第二种对应:
    a_ptr = new B;
    a_ptr->show();
//输出:
B class show()
//结果:没问题,添加virtual后,虽然a_ptr是基类指针,但是调用方法时,根据指向对象的类型来决定调用哪个方法。

到这里又会问,根据对象本身和根据对象指针有啥区别么,当然有,因为对象才是真正的实体所有的方法和数据也好,最后都是为了更好的操作和利用对象,而类仅仅是对象的一种抽象化声明。
总结一下:
1、若要在子类中重新定义基类的方法,采用virtual虚函数(在类声明中添加,类方法定义时不需要)。
2、基类中声明为virtual后,子类自动成为virtual。(所以子类中的virtual看起来可有可无,但是最好还是写上,最起码能够明显的标示此方法是虚函数)
3、把基类析构函数声明为virtual!!此是为了确保释放子类对象时,按正确的顺序调用析构函数(很重要!暂且记住!)。其实,使用中,基类析构函数都是虚的

多说一句:子类方法可以调用公有基类方法,加上类名就好了:
就是B类中的show()想调用A类中show()时,用A::show()就可以了。假如忘了加的话,就成递归了。。。
另外这种用法很常见,show()就是一种,直接调用基类的显示基类信息后,在此基础上在定义显示子类的数据。

静态联编和动态联编:
静态联编是指编译时就确定使用哪个方法定义、而动态联编是指在运行时才确定。
编译器对非虚方法使用静态联编、对虚方法使用动态联编。

protected关键字
其实就是为了继承关系多定义出来的一种成员类型,在public和private之间。
如果成员被定义为protected:
对有继承关系的子类,他们相当于public属性。
对没有继承关系的类,他们相当于private属性。
用法上protected一般定义下方法,方便子类调用,而不建议定义数据

13.6抽象基类(ABC)
好,还是由需求出发。上面我们定义的类,说白了都是成串的,也就是有可能A派生B,B派生C,C派生D…这样。其实有些场景不单是这样,有些是略微并列的关系,比如椭圆和圆,圆是特殊的椭圆。完全用圆继承自椭圆来搞的话比较费劲,因为圆并不是在椭圆的基础上加东西,甚至还会去东西,长轴短轴都没有,只有个半径等等很多不同。好,思路转变,既然他俩瞅着有点并行,而且有点共性,那我为何不将共性的东西抽出来,单独定义为一个基类,然后从此基类上派生出椭圆类和圆类呢~
好,到这步看起来比较完美了,然而还是有点问题,这个基类叫什么呢?这个基类会创建什么实体对象么?貌似不好直接描述它叫什么,也不需要和不关系它创建出来的对象到底什么。。。
好这种情况下,我们创建了这么一个类:用作基类并且不创建对象。
OK,纯虚函数登场~
格式上纯虚函数很简单,在虚函数后方添加 =0 即可:virtual show() = 0;
1、将类中添加至少一个虚函数,这个类就成为了ABC
2、ABC类不能创建对象,只能当做基类。
3、纯虚函数是不需要定义的,只需要声明即可(想想也对,连对象都没有,定义个啥还,就是等着不同的子类有不同的定义来实现各自的动能就好了)。

13.7类和动态内存分配
当基类和子类都采用动态内存分配时,子类的析构函数、复制构造函数、赋值运算符都必须使用相应的基类方法来处理基类元素。三种方式各不同:
析构函数:自动完成。
构造函数:在初始化列表中调用基类复制构造函数。
赋值运算符:通过作用域解析符显示调用基类赋值运算符(这里可能会用到赋值运算符的函数表示法:BaseClass::operator=(an_SonClass_object))。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值