C++学习笔记(十二)------is_a关系(继承关系)

 

你好,这里是争做图书馆扫地僧的小白。

个人主页争做图书馆扫地僧的小白_-CSDN博客

目标:希望通过学习技术,期待着改变世界。


 提示:以下是本篇文章正文内容,下面案例可供参考

前言  

        随着技术的革新,出现各种各样的编程语言,C++就是其中之一,作为最受欢迎的编程语言之一,C++带给开发者们最大的惊喜便是其强大的特性。一方面跟C兼容,可以直面系统底层API,SDK,另一方面提供了很多范式,足够的抽象能力,面向对象,操作符重载,模板等。

        之前的C++学习笔记(十一)------has_a和use_a关系-CSDN博客已经讲解了has_a和use_a关系,本篇将讲解最为重要的一种关系即is_a关系(继承关系)。


一、继承关系的语法形式

        (一)单继承的语法格式:

class 子类 : 继承权限 + 父类
{
    //继承即是子类继承了父类中的所有属性与方法(构造函数除外)
};

        (二)多继承的语法形式

class 子类 : 继承权限1,父类1,继承权限2,父类2,... 继承权限n,父类n
{
    //子类继承多个父类中的所有属性与方法。
}

        注意:继承关系中,子类并不继承父类中的构造函数。因为构造函数是特别的为本类域进行初始化的函数,并不会被继承。子类中要有子类的默认构造或自定义有参构造。

       多继承一般情况不使用,如果使用多用于集成多个抽象类,原因见下文的多继承部分。

         (三)单继承关系举例

#include <iostream>
using namespace std;
class Car
{
public:
    int price;
public:
    void run()
    {
        cout << "汽车在行驶" << endl;
    }
};
class BYD : public Car
{
public:
    void uniqueEmblem()
    {
        cout << "有着中国特色的车标" << endl;
    }
};
int main()
{
    BYD byd;
    byd.run();
    byd.price;
    byd.uniqueEmblem();

    return 0;
}

二、继承关系的意义

        继承关系的意义是:代码复用性与高拓展性。

        使用继承关系的步骤:首先把这类的事件抽象出来一些共有的属性与特征,把它们定义为父类。再用这个父类,对具有不同特点的子类进行派生,就可以派生出各种不同的子类。这样的子类有着公有的属性,还有着各自独特的性质。

三、继承关系的权限问题

#include <iostream>
using namespace std;

class Car
{
public:
    int price;
public:
    void run()
    {
        cout << "汽车在行驶" << endl;
    }
};

class BYD : private Car
{
public:
    void uniqueEmblem()
    {
        cout << "有着中国特色的车标" << endl;
    }
};

int main()
{
    BYD byd;
    byd.run();
    byd.price;
    byd.uniqueEmblem();

    return 0;
}

         继承关系图如下所示,如图所示当子类继承父类时,可以理解为将父类整体打包,将其作为一整个属性赋给子类。

        注意:每一种继承关系对父类各种权限的属性的访问也是有区别的。当public继承时,对父类中private权限的属性不管是在子类的类外还是类内都没有访问权限;对父类中protected权限的属性在子类的类内有访问权限且为protected访问权限,在子类类外没有访问权限;对父类中public属性的属性不管是在子类类内还是类外都是public访问权限。其余两种不在采用文字形式进行描述,详细的直观的描述见下面的表格。

四、单继承

        (一)单继承关系下的内存布局        

        子类对象被构造时,先构造父类,然后再调用子类的构造完成对子类类域中拓展的属性进行初始化,此时,子类的对象具有具体初始值。析构的时候先析构子类,然后在析构父类。

        注意的是子类析构被执行,父类析构也一定被析构。原因是内存绑定

        1.举个例子,代码如下:

#include <iostream>
using namespace std;
class Car
{
private:
    int price;
public:
    void run()
    {
        cout << "汽车在行驶" << endl;
    }
    Car()
    {
        cout << "Car父类的构造" << endl;
    }
    ~Car()
    {
        cout << "Car父类的析构" << endl;
    }
};
class BYD : private Car
{
public:
    BYD()
    {
        cout << "BYD子类的构造"  << endl;
    }
    ~BYD()
    {
        cout << "BYD子类的析构" << endl;
    }
};

int main()
{
    BYD byd;
    return 0;
}

        2.图解如下:

(二)自动隐藏机制

        当子类中有与父类同名属性或函数时,父类中的同名属性或行为将自动隐藏在自己的父类的类域之中,如果想访问父类类域之中的属性或行为,就使用域名::访问。

        举个例子,代码如下:

#include <iostream>
#include <unistd.h>
using namespace std;
class Car
{
public:
    int price = 10000;
public:
    void run()
    {
        cout << "车子正在行驶之中" << endl;
    }
    Car()
    {
        cout << "Car父类的构造" << endl;
    }
    ~Car()
    {
        cout << "Car父类的析构" << endl;
    }
};


class BYD : public Car
{
public:
    int weight = 20000;
public:
    void run()
    {
        cout << "BYD高速行驶" << endl;
    }
    BYD()
    {
        cout << "BYD子类的构造" << endl;
    }
    ~BYD()
    {
        cout << "BYD子类的析构" << endl;
    }
};
int main()
{
    BYD byd;
    cout << byd.Car::price << endl;
    byd.run();
    byd.Car::run();
    return 0;
}

(三)继承关系下的类型兼容规则

        在之前我们学到当使用指针的时候,要保证等号前后的指针类型要相同才可以,在继承中不然。继承关系中父类指针或者引用可以安全的指向或者引用子类类型的对象,但是子类指针或者引用并不能指向或者引用父类类型的对象。

        需要注意的是使用父类指针在释放资源时,注意要强转成子类指针才可以完全释放,不然会造成资源的泄露。

        还需要注意的是当父类中只有有参构造的时候,子类的构造函数处应该进行初始化列表的操作,指定父类的有参构造对其进行初始化操作,否则会报错。

        举个例子,代码如下:

#include <iostream>
#include <unistd.h>
using namespace std;
class Car
{
public:
    int price = 10000;
public:
    void run()
    {
        cout << "汽车在行驶" << endl;
    }
    Car(int price)
    {
        cout << "Car父类的构造" << endl;
    }
    ~Car()
    {
        cout << "Car父类的析构" << endl;
    }
};
class BYD : public Car
{
public:
    int price = 20000;
public:
    void run()
    {
        cout << "BYD高速行驶" << endl;
    }
    BYD():Car(1)  
    {
        cout << "BYD子类的构造" << endl;
    }
    ~BYD()
    {
        cout << "BYD子类的析构" << endl;
    }
};
int main()
{
    //栈上定义对象
    BYD byd;
    Car* c = &byd;   //父类属性的指针可以安全的指向子类属性
    c->run();        //调用父类中子类和父类公有的run方法
    cout << c->price << endl;  //10000
    Car& ref_c = byd;  //父类属性的引用可以安全的指向子类属性
    ref_c.run();        //调用父类中子类和父类公有的run方法
    cout << ref_c.price << endl;  //10000
    //用堆上的对象:
    Car* ptr_c = new BYD();
    ptr_c->run();
    delete (BYD*)ptr_c;  //释放指针时,需要进行强转,否则只采用delete ptr_c,只会析构父类,而子类指向的堆上资源并没有被回收
                        
    return 0;
}

 五、多继承

        在多继承时,虽然多继承可以大在提高代码的复用性,但是会造成代码冗余,当多个父类中有同名函数或属性时,在访问时候会出现二义性的问题。

        需要注意使用类域::访问的方式进行指定访问,但是这样给程序设计带来很多不便,所以多继承在开发时尽量避免。如果一定要使用多继承时,推荐使用多继承多个抽象类,因为抽象类一般都没有具体的属性,只有纯虚函数。

        (一)多继承举例说明:

#include <iostream>
using namespace std;
class Phone
{
public:
    int power;
public:
    void sendMessage()
    {
        cout << "发消息" << endl;
    }
};
class Pad
{
public:
    int power;
public:
    void playGame()
    {
        cout << "玩游戏" << endl;
    }
};
class Computer : public Phone, public Pad
{

};
int main()
{
    Computer computer;
    computer.sendMessage();
    computer.playGame();
    cout << computer.Phone::power << endl;  
    cout << sizeof(Computer) << endl;
    return 0;
}

(二)多继承的内存布局

        注意:当上述例子中定义Phone *p = &computer 和 Pad *p1 = &computer 这两个指针指向的地址空间是否一致?

        答案是不一致的,当使用父类指针指向子类时,在继承中父类指针永远指向的是自己类中的起始地址。

        当然多继承也符合继承关系下的类型兼容规则。


总结

        以上就是今天要讲的内容,本文将is_a关系(继承关系)进行了详细的介绍,分成了单继承和多继承,并对继承中权限问题和内存布局进行了详细的介绍,当然多继承中还存在着一种特殊的构造形式即为菱形继承,这部分将留作下篇笔记进行撰写。

  • 22
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

译泽在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值