C++类的继承与派生

转自大神博客凡程子

一、基本概念

  1. 类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生,原有的类称为基类或父类,产生的新类称为派生类或子类。
  2. 派生类的声明:
    class 派生类名 : 继承方式 基类名1,继承方式 基类名2,...,继承方式 基类名n
    {
    派生类成员声明;
    }
  3. 一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承,直接派生,间接派生。
  4. 继承方式规定了如何访问基类继承的成员。继承方式有public,private,protected。如果不显示给出继承方式,默认为private继承。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。
  5. 派生类继承基类中除构造和析构函数以外的所有成员。
  6. 派生类生成:
    • 吸收基类成员(除构造析构函数以外的所有成员)
    • 改造基类成员(根据继承方式调整基类成员的访问,函数在子类中的覆盖,以及虚函数在子类中的覆盖)
    • 添加新的成员
  7. public继承(基类的private不是我的private)
    当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无论派生类的成员还是派生类的对象都无法访问基类的私有成员。
  8. private继承
    当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。
  9. 保护继承
    保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。

二、派生类的构造函数和析构函数

1.派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,然后派生类中新增的成员在派生类的构造函数中初始化

  1. 派生类构造函数的语法
    派生类名::派生类名(参数总表):基类名1(参数表1),基类名(参数名2)....基类名n(参数名n),内嵌子对象1(参数表1),内嵌子对象2(参数表2)....内嵌子对象n(参数表n)
    {
    派生类新增成员的初始化语句;
    }
    注意:构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化
  2. 如果基类中没有不带参数的构造函数(且参数没有默认初值),也可以说,只有带参数的构造函数,那么在派生类的构造函数初始化列表里中必须调用基类构造函数,以初始化基类成员
  3. 派生类构造函数执行的次序:
    • 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右)
    • 调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序
    • 派生类的构造函数体中的内容
      例子:
#include <iostream>
#include <time.h>
using namespace std;

class B1
{
public:
    B1(int i)
    {
        cout<<"constructing B1 "<<i<<endl;
    }
};

class B2
{
public:
    B2(int j)
    {
        cout<<"constructing B2 "<<j<<endl;
    }
};

class B3
{
public:
    B3()
    {
        cout<<"constructing B3"<<endl;
    }
};

class C: public B2, public B1, public B3
{
public:
    C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b)
    {

    }
private:
    B1 memberB1;
    B2 memberB2;
    B3 memberB3;
};

int main() 
{ 
    C obj(1,2,3,4);

    return 0; 
}

输出结果为:
constructing B2 2
constructing B1 1
constructing B3
constructing B1 3
constructing B2 4
constructing B3

  1. 析构函数
    派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。
#include <iostream>
#include <time.h>
using namespace std;

class B1
{
public:
    B1(int i)
    {
        cout<<"constructing B1 "<<i<<endl;
    }
    ~B1()
    {
        cout<<"destructing B1"<<endl;
    }
};

class B2
{
public:
    B2(int j)
    {
        cout<<"constructing B2 "<<j<<endl;
    }
    ~B2()
    {
        cout<<"destructing B2"<<endl;
    }
};

class B3
{
public:
    B3()
    {
        cout<<"constructing B3"<<endl;
    }
    ~B3()
    {
        cout<<"destructing B3"<<endl;
    }
};

class C: public B2, public B1, public B3
{
public:
    C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b)
    {

    }
private:
    B1 memberB1;
    B2 memberB2;
    B3 memberB3;
};

int main() 
{ 
    C obj(1,2,3,4);

    return 0; 
}

输出结果为:
constructing B2 2
constructing B1 1
constructing B3
constructing B1 3
constructing B2 4
constructing B3
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2

三、派生类成员的标识和访问

  1. 派生类成员属性划分为四种:不可访问的成员,私有成员,保护成员,公有成员
  2. 作用域分辨
    形式为:基类名::成员名;基类名::成员名(参数表)
    如果某派生类的多个基类拥有同名的成员,同时派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员,这就需要上面的调用方式才能调用基类的同名成员。
    例子:多继承同名
#include <iostream>
#include <time.h>
using namespace std;

class B1
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B1 "<<nV<<endl;
    }
};

class B2
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B2 "<<nV<<endl;
    }
};

class D1: public B1, public B2
{
public:
    int nV;
    void fun()
    {
        cout<<"member of D1 "<<nV<<endl;
    }
};

int main() 
{ 
    D1 d1;
    d1.nV = 1;
    d1.fun();
    d1.B1::nV = 2;
    d1.B1::fun();
    d1.B2::nV = 3;
    d1.B2::fun();

    return 0; 
}

输出结果为:
member of D1 1
member of B1 2
member of B2 3
以上通过作用域分辨符,解决了访问基类中被屏蔽的同名成员。

  1. 如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名现象,对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须用直接基类进行限定。
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B0 "<<nV<<endl;
    }
};

class B1:public B0
{
public:
    int nV1;
};

class B2:public B0
{
public:
    int nV2;
};

class D1:public B1, public B2    //B1 B2是直接基类
{
public:
    int nVd;
    void fund()
    {
        cout<<"member of D1"<<endl;
    }
};

int main() 
{ 
    D1 d1;
    d1.B1::nV = 2;
    d1.B1::fun();
    d1.B2::nV = 3;
    d1.B2::fun();

    return 0; 
}

输出结果为:
member of B0 2
member of B0 3
在这种情况下,派生类对象在内存中就同时拥有成员nV及fun的两份拷贝。但是很多情况下,我们只需要这样一个这样的数据拷贝,同一成员的多份拷贝增加了内存的开销。可以通过虚函数来解决这个问题。

  1. 虚基类(针对多重继承
    为了解决前面提到的多重拷贝的问题,可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数也只有一个映射。
    虚基类的声明是在派生类的声明过程,其语法形式为:
    class 派生类名::virtual 继承方式 基类名;
    例子:
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B0 "<<nV<<endl;
    }
};

class B1:virtual public B0
{
public:
    int nV1;
};

class B2:virtual public B0
{
public:
    int nV2;
};

class D1:public B1, public B2
{
public:
    int nVd;
    void fund()
    {
        cout<<"member of D1"<<endl;
    }
};

int main() 
{ 
    D1 d1;
    d1.nV = 2;
    d1.fun();

    return 0; 
}

输出结果为:
member of B0 2

  1. 虚函数及其派生类的构造函数
    一般而言,派生类只对其直接基类的构造函数传递参数,但是在虚基类中,不管是直接或间接虚基类的所有派生类,都必须在构造函数的成员初始化列表中列出对虚基类的初始化。
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    B0(int n)
    {
        nV = n;
    }
    int nV;
    void fun()
    {
        cout<<"member of B0 "<<nV<<endl;
    }
};

class B1:virtual public B0
{
public:
    B1(int a):B0(a)   
    {
    }
    int nV1;
};

class B2:virtual public B0
{
public:
    B2(int a):B0(a)
    {
    }
    int nV2;
};

class D1:public B1, public B2
{
public:
    D1(int a):B0(a), B1(a), B2(a)
    {
    }
    int nVd;
    void fund()
    {
        cout<<"member of D1"<<endl;
    }
};

int main() 
{ 
    D1 d1(1);
    d1.nV = 2;
    d1.fun();

    return 0; 
}

以上例子看上去B0的构造函数好像被调用了三次,但是实际上只有D1类中的D1(int a):B0(a), B1(a), B2(a)
才是真正的调用了B0构造函数。

四、赋值兼容规则

  1. 赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
  2. 赋值兼容规则所指的替代包括:
    • 派生类的对象可以赋值给基类对象
    • 派生类的对象可以初始化基类的引用
    • 派生类对象的地址可以赋给指向基类的指针
    • 在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

例子:

#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    void display()
    {
        cout<<"B0::display()"<<endl;
    }
};

class B1:public B0
{
public:
    void display()
    {
        cout<<"B1::display()"<<endl;
    }
};

class B2:public B0
{
public:
    void display()
    {
        cout<<"B2::display()"<<endl;
    }
};

void fun(B0 *ptr)
{
    ptr->display();
}

int main() 
{ 
    B0 b0;
    B1 b1;
    B2 b2;
    fun(&b0);
    b0 = b1;
    fun(&b0);
    b0 = b2;
    fun(&b0);

    return 0; 
}

输出结果为:
B0::display()
B0::display()
B0::display()

通过这种赋值兼容后,每次调用的同名函数都是基类的同名函数,如果想调用派生类的,则需要使用虚函数。

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

推荐阅读更多精彩内容