【C++温故知新】详解C++中的类的继承

这是C++类重新复习学习笔记的第 七 篇,同专题的其他文章可以移步:https://www.jianshu.com/nb/39156122

类继承的语法

类的继承允许通过继承的方式生成新类,继承自的类为基类,继承自基类的类成为派生类,类的继承写法如下:

class derivedClass : public/protect/private baseClass
{
// statements
}

其中基类前有一个访问限定符,不写的时候默认为private,但是我们主要使用public,又称作公有继承,三个不同的访问限定词的区别在于:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问
  • 保护继承(protecte): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员
  • 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员

例如我们定义一个基类再定义一个派生类:

class baseClass
{
private:
    baseInt;
    baseDouble;
public:
    baseClass(const int pInt = 1, cosnt double pDouble = 2.5);
    int function(double d);
}
 
baseClass :: baseClass(const int pInt = 1, cosnt double pDouble = 2.5)
{
    baseInt = pInt;
    baseDouble = pDouble;
}
 
baseClass :: function(double d)
{
    cout << d;
    return 1;
}
 
 
class derivedClass : public baseClass
{
private:
    derivedChar;
public:
    derivedClass(const int pInt, const double pDouble, const char pChar);
    void anotherFunction();
}
 
derivedClass :: derivedClass(const int pInt, const double pDouble, const char pChar) : baseClass(pInt,pDouble)
{
    derivedChar = pChar;
}
 
void derivedClass :: anotherFunction()
{
    cout << "I am the derived class.";
}

从这个例子中,我们可以看到,基类有两个私有变量,baseIntbaseDouble,两个方法,一个构造函数用于给两个私有变量赋值,一个用于充数。派生类继承自基类,于是派生类derivedClass就拥有了基类的两个公有方法,但是它不能访问基类的两个私有变量。派生类又定义了自己的一个新私有变量,同时有自己的构造函数和一个用于充数的函数。可见继承的作用在于方便地进行代码的重用以及组织管理项目设计。

基类和派生类的关系

类的继承是“is-a”的关系,或者说是“is-a-kind-of”,即派生类对象也是一个基类对象。

  • 派生类是可以调用基类的protectedpublic修饰的成员变量和方法的,而派生类也可以定义自己的变量和方法。
  • 同时,派生类还可以重载基类的方法,即声明一个和基类中相同名称的成员变量,但是在派生类中对其进行重新的定义。如果基类和派生类同时拥有同名同变量参数同返回值但是定义不同的函数,在使用基类对象调用该函数时,调用的是基类的函数,在使用派生类的对象调用该函数时,调用的是派生类的定义。
  • 基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象。C++要求的引用和指针类型与赋给的类型匹配的规则对继承来说例外。然而,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针。基类指针或引用只能用于调用基类方法。

多态公有继承

多态即一个派生类继承自一个基类后,希望可以定义一个和基类中相同名称、参数列表、返回值的函数,但这个函数的定义却与基类中的不同,即一种派生类对基类方法的重载。

首先,派生类可以重载基类的方法,如果派生类使用基类相同的函数和函数定义,那么就不需要再在派生类中声明该函数,即共有的函数需要放在基类中。如果派生类想对基类的函数进行新的定义,则需要在派生类中对其再次进行声明并定义,定义时也需要表明定义的是那个类的函数。如 baseClass::function()derivedClass::function() 这样。

虚方法(virtual method),需要使用关键词 virtual 修饰基类中的函数,如下面这样:

virtual void function(int i);

它的作用如下:当基类和派生类都有定义过某个相同方法后,我们需要确定调用的是哪个类下的方法,特别是当方法是通过引用或指针而不是对象调用的。

  • 如果没有使用关键字 virtual ,程序将根据引用类型或指针类型选择方法
  • 如果使用了关键字 virtual ,程序将根据引用或指针指向的对象的类型来选择方法
  • 如果有派生类重载了基类的方法,一般需要将基类的析构函数设置成virtual的以保证释放派生类对象时能够按照正确的顺序调用析构函数
// 不使用virtual
BaseClass baseClass();
DerivedClass derivedClass();
BaseClass & reference1 = baseClass; // 指向baseClass的类型是BaseClass的引用变量
BaseClass & reference2 = derivedClass; // 指向derivedClass的但是类型是BaseClass的引用变量
reference1.function(); // 会根据引用的类型即BaseClass调用BaseClass下的function方法
reference2.function(); // 会根据引用的类型即BaseClass调用BaseClass下的function方法
 
// 使用virtual
BaseClass baseClass();
DerivedClass derivedClass();
BaseClass & reference1 = baseClass; // 指向baseClass的类型是BaseClass的引用变量
BaseClass & reference2 = derivedClass; // 指向derivedClass的但是类型是BaseClass的引用变量
reference1.function(); // 会根据引用指向的类型即BaseClass调用BaseClass下的function方法
reference2.function(); // 会根据引用指向的类型即DerivedClass调用BaseClass下的function方法

抽象基类

抽象基类(abstract base class,ABC)是一种特殊的基类,从概念上讲,将所有派生类的公用的方法进行抽象汇总声明(定义)到的一个类中,这种设计下的类可以视作一个抽象基类。但是真正的抽象基类应该是至少包含了一个纯虚函数(pure virtual function)的类,这种类不能声明对应的对象,只能作为基类。

纯虚函数是一种只在抽象基类中给出原型,但是部给出定义的函数,更像是一个接口,由所有的派生类对纯虚函数根据自己类的需求来实现其定义。纯虚函数的写法是在虚函数后面以 =0 结尾

virtual double pureVirtualFunction(int i) const = 0;

应用这种方式,可以将所有派生类共有但是却又各自有着不同实现的方法抽象到一个基类中,提供其原型但是不对其进行定义(也只有纯虚函数C++允许不给出定义),然后使得各个派生类自己给出其定义。

私有继承

私有继承即继承的基类使用private修饰符修饰的继承,如果没有访问限定符的修饰,默认也是私有继承,私有继承是一种“has-a”的关系。

class DerivedClass : private BaseClass{ }
class DerivedClass : BaseClass{ }

私有继承使得基类的公有成员、保护成员都被成为派生类的私有成员,这就使得基类的那些方法都不能再被派生类的实例化对象使用,而只能被派生类的成员函数在类内部使用。即派生类部继承基类的接口。

这里比较了三种继承之间的区别:

特征 公有继承 保护继承 私有继承
公有成员变成 派生类的公有成员 派生类的保护成员 派生类的私有成员
保护成员变成 派生类的保护成员 派生类的保护成员 派生类的私有成员
私有成员变成 只能通过基类接口访问 只能通过基类接口访问 只能通过基类接口访问
能否隐式向上转换 只能在派生类中 不能

多重继承

多重继承(Multiple Inheritance)也是“is-a”的关系,它允许一个类继承自多个类,只需要将继承的类使用逗号隔开即可,像下面这样:

class DerivedClass : public BaseClass1, public BaseClass2 {……}
class DerivedClass : public BaseClass1, BaseClass2 {……} // BaseClass2 is a private base

多重继承中每一个被继承的基类都需要设置访问限定符,根据需要可以使用不同的访问限定符,不写默认为private

例如设置一个基类Worker表示工人,然后工人可以是歌手也可以是服务员,我们使用两个类继承自这个基类,Singer和Waiter,最后,我们可以定义一个既是歌手有时服务员的类,所以它同时继承自Singer和Waiter,他们的关系就像下边这样:

class Worker {……}
class Singer : public Worker {……}
class Waiter : public Worker {……}
class SingingWaiter : public Singer, public Waiter {……}
一个多重继承

虚基类

首先多重继承导致了一个问题就是,当一个SingingWaiter的实例继承自Singer和Waiter时,也就间接地两次继承了Worker,也就是说一个SingingWaiter的实例结构应该是如下这样的:

在不使用虚基类的时候

这样引发的问题就是,当我们把派生类对象的地址赋给一个基类的指针时就无法区分是赋给哪个基类,导致二义性:

SingingWaiter sw;
Worker * psw = &sw;

因为sw中包含两个Worker对象,从而有两给地址可以选择,于是正确的写法应该是:

Worker * psw1 = (Waiter *) &sw;
Worker * psw2 = (Singer *) &sw;

所以虚基类(Virtual Base Classes)将解决这个问题。虚基类使得从多个类派生出的对象只继承一个基类对象,需要在类继承的声明中使用virtual关键词,virtual和public的次序无所谓:

class Singer : virtual public Worker {……}
class Waiter : public virtual Worker {……}
class SingingWaiter : public Singer, public Waiter {……}

现在,SingingWaiter对象就只包含Worker对象的一个副本,从本质上说是,继承的Singer和Waiter共享一个Worker对象,而不是各自引入一个Worker对象的副本,从而可以使用多态。

在使用虚基类的时候

转载请注明出处,本文永久更新链接:https://blogs.littlegenius.xin/2019/08/28/【C-温故知新】七类的继承/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 3. 类设计者工具 3.1 拷贝控制 五种函数拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符析构函数拷贝和移...
    王侦阅读 1,781评论 0 1
  • C++类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心...
    863cda997e42阅读 633评论 0 4
  • C++文件 例:从文件income. in中读入收入直到文件结束,并将收入和税金输出到文件tax. out。 检查...
    SeanC52111阅读 2,751评论 0 3
  • 类的继承与派生 类的继承就是新类由已经存在的类获得已有特性,类的派生是由已经存在的类产生新类的过程。已有类叫做基类...
    Mr希灵阅读 609评论 0 1
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,500评论 1 51