继承与派生详解

一、继承的概念与派生类的定义

1)继承:它是一个动词,指的是一种连接类与类之间的层次模型。

2)派生:指从一个主要的事物中分化出来。

定义看起来不太好理解,但是我们可以用一个具体的例子来说明这两者的内涵:

“狗”和“黑狗”。
当人们谈论“狗”的时候,知道它是哺乳动物、有4条腿、1条尾巴,喜欢吃骨头......
若谈论“黑狗”,人们会怎么说呢?当然可以说:“黑狗也是哺乳动物,也有4条腿、1条尾巴,喜欢啃骨头......,并且它的毛是黑色的”。
但人们一般不这么说,而是说:“黑狗就是毛是黑色的狗”。

在这个例子中,“黑狗”就继承了“狗”的全部特征,且有自己的特殊性:黑。

这种通过特殊化已有的类来建立新类的过程,叫做“类的派生”。

原有的类叫做“基类”,新建立的类则叫做“派生类”。(或称父类和子类)

在这里,“狗”就是基类,“黑狗”就是派生类。


二、继承的种类

C++有两种继承:单一继承多重继承

对于单一继承,派生类只能有一个基类。对于多重继承,派生类可以有多个基类。

我们用PPT中的一张图来说明这个概念:

 三、基本用法

1.定义框架

class A
{
    private:
    protected:
    public:
};
class B:private/protected/public A
{
    //继承 A's members
    //定义 B's members
};

在这里我们发现了B对于A的继承有三种方式private/protected/public,我们也用PPT中的一张图来说明这三种方式继承的区别和联系:

当B以public方式继承A时,A的public类型在B中仍为public,A的protected类型仍为protected。

当B以protected方式继承A时,A的public和protected数据都变为protected。

当B以private方式继承A时,A的public和protected数据都变为private。

其实也就是两个重要点:

1)安全性只会变强。

2)基类的private数据类型虽然会被继承,但在派生类中不可见。 

2.基本结构

1)关于继承的方式

class A
{
    private:
        int pri;
        void set_pri(int i){pri = i;}
    protected:
        int pro;
        void set_pro(int i){pro = i;}
    public:
        int pub;
        void set_pub(int i){pub = i;}
};
class priA:private A{....};     //无法直接使用pri
class proA:protected A{....};       //可以直接使用pro,但不能直接在类外使用
class pubA:public A{....};      //可以直接使用pub,可以在类外直接使用

2)构造函数

首先,构造函数是不被继承的,所以一个派生类只能调用它的直接基类的构造函数。 

当定义派生类的一个对象时,首先调用基类的构造函数,对基类的成员进行初始化,然后执行派生类的构造函数,如果一个基类仍然是一个派生类,则这个过程递归进行。

基本用法如下:

class Person    //定义一个基类的Person
{
    private:
        string pName;
        int pAge;
        bool pIsMale;

    public:
        Person(string strName,int nAge,bool bIsMale):pName(strName),pAge(nAge),pIsMale(bIsMale){}
        //构造函数(初始化数据)
        string GetName(){ return pName;}
        int GetAge(){ return pAge;}
        bool IsMale(){ return pIsMale;}     //不是直接类外访问private数据,而是通过类内的函数间接访问
};
class Employee      //定义一个基类的Employee
{
    private:
        string strEmployer;
        double dWage;
    public:
        Employee(string strEmployer,double dWage):strEmployer(strEmployer),dWage(dWage){}
        string GetEmployer(){ return strEmployer;}
        double GetWage(){ return dWage;}
};

class Teacher:public Person,public Employee     //定义一个派生类的Teacher(这里属于多重继承)
{
    private:
        int m_nTeachesGrade;        //新增的数据成员
    public:
        Teacher(string strName,int nAge,bool bIsMale,string strEmployer,double dWage,int nTeachesGrade):
        //这里为了美观换了个行,实际上是一行的,Person紧跟着冒号
        Person(strName,nAge,bIsMale),Employee(strEmployer,dWage),m_nTeachesGrade(nTeachesGrade){}
        //这里通过初始化列表调用基类的构造函数
        int GetTeachesGrade(){ return m_nTeachesGrade;}     //新增的数据成员的访问函数
};

int main(){
    Teacher t("Wang",30,true,"Jia",10000,5);    //创建一个Teacher类的对象t
    cout<<"NAME:"<<t.GetName()<<endl;
    cout<<"AGE:"<<t.GetAge()<<endl;
    cout<<"IS_MALE?:"<<t.IsMale()<<endl;    //这三行是通过类内的函数间接访问基类Person的private数据
    cout<<"EMPLOYER'S NAME:"<<t.GetEmployer()<<endl;
    cout<<"WAGE:"<<t.GetWage()<<endl;       //这两行是访问基类Employee的private数据
    cout<<"TEACHESGRADE:"<<t.GetTeachesGrade()<<endl;       //这一行是访问派生类Teacher的private数据
    return 0;
}

 输出结果如图

我们为了输出老师的一些信息,定义了一个Person基类和一个Employee基类,即老师属于人类,也属于雇员(这里为了说明多重继承的关系所以把人类和雇员规定为同一层级的基类了)。当我们输出老师的信息时,可以输出这位老师作为人类的一些信息、作为雇员的一些信息以及作为老师的特殊信息。 

3)析构函数

照例来说析构函数属于构造函数,但为了更清晰地说明知识点,我们将它作为新的一块进行说明。

当该定义的派生类对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反

具体例子如下:

class Animal{
    private:
        int id;
    public:
        Animal():id(0){cout<<"#111 Animal no-argument constructor called"<<endl;}
        Animal(int aid):id(aid){cout<<"#222 Animal parameterized constructor called"<<endl;}
        ~Animal(){cout<<"#333 Animal destructor called"<<endl;}
        void info(){cout<<"ID:"<<id<<endl;}
};
class Cat:public Animal{
    public:
        Cat(int aid):Animal(aid){cout<<"#444 Cat parameterized constructor called"<<endl;}
        Cat(){cout<<"#555 Cat no-argument constructor called"<<endl;}
        ~Cat(){cout<<"#666 Cat destructor called"<<endl;}
};
class Tiger:public Cat{
    public:
        Tiger():Cat(999){cout<<"#777 Tiger no-argument constructor called"<<endl;}
        //Tiger():Cat(){{cout<<"#777 Tiger no-argument constructor called"<<endl;}}
        ~Tiger(){cout<<"#888 Tiger destructor called"<<endl;}
};
int main(){
    Tiger tiger;
    tiger.info();
    return 0;
}

这里用了数字来说明它运行的顺序,结果如下:

 

假如说我们把代码中的#777句改成被隐藏的这句:

Tiger():Cat(){{cout<<"#777 Tiger no-argument constructor called"<<endl;}

则变成输出:

这是由于调用了基类的无参(默认)构造函数,具体大家之后可以仔细看看代码。 

4)同名成员访问

当引用派生类的成员时, 首先在派生类自身的作用域中寻找这个成员, 如果没有找到,则到它的基类中寻找。如果一个派生类是从多个基类派生出来的, 而这些基类又有一个共同的基类,则在这个派生类中访问这个共同的基类中的成员时, 可能会产生二义性。

说白了就是父子拥有同一个东西

且这个东西作用还不一样,那我们在提到这个东西的时候指的是谁的呢?我们有没有什么办法解决这个问题呢?看看如下代码:

class Student
{
    public:
        int id;
};
class Teacher
{
    public:
        int id;
};
class Assistant:public Student,public Teacher
{
    public:
        void print()
        {
            //cout<<"ID:"<<id<<endl;     /error! 无法确定是Student的id还是Teacher的id(二义性)
            cout<<"Student ID:"<<Student::id<<endl;
            cout<<"Teacher ID:"<<Teacher::id<<endl;
        }
};
int main()
{
    Assistant a;
    a.Student::id = 1;
    a.Teacher::id = 2;
    //a.id = 3;     /error! 无法确定是Student的id还是Teacher的id(二义性)
    a.print();
    return 0;
}

最终输出结果是:

Student ID:1
Teacher ID:2

 这就是通过类名限定符”::“来解决的。

还有一个重要的办法叫做虚基类(virtual base class)

这个在之后学的多态中有详细讲解,这里就不再赘述了。(其实是偷懒了)

下回见!拜拜!! 

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
析构函数不可以继承派生类函数继承不构成重载。这是因为析构函数在派生类中的作用是销毁派生类对象时调用基类和派生类的析构函数,确保正确的析构函数序列被调用。\[1\]虽然派生类可以继承基类的析构函数,但是派生类的析构函数并不构成重载关系。派生类的析构函数只是在基类的析构函数之后被调用,用于销毁派生类自身的资源。\[3\]因此,派生类的析构函数不会覆盖基类的析构函数,也不会构成重载关系。如果派生类需要执行特定的析构操作,可以在派生类中定义自己的析构函数,并在其中调用基类的析构函数。\[2\]这样可以确保基类和派生类的析构函数都被正确调用,从而避免资源泄漏和其他问题的发生。 #### 引用[.reference_title] - *1* *2* [C++中的虚析构函数、纯虚析构函数详解](https://blog.csdn.net/oscarjulia/article/details/74457066)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [C++继承详解(二):派生类的构造与析构,理解函数的重载、隐藏与覆盖](https://blog.csdn.net/ZYZMZM_/article/details/89423332)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值