从C++开始的编程生活(16)——继承

前言

本系列文章承接C语言的学习,需要有++C语言的基础++ 才能学会哦~

第16篇主要讲的是有关于C++的++继承++ 。
C++已经进入进阶,加油!!

目录

前言

继承

普通类继承

语法

子类访问父类成员的访问限定符限制

继承类模板

基类与派生类之间的转换

继承的隐藏作用域

派生类的默认成员函数

构造函数

拷贝构造

析构函数

继承与友元

继承和静态成员

单继承、多继承和菱形继承

虚继承virtual

多继承的指针偏移

继承和组合


继承

是实现面向对象编程的复用代码的一个重要手段。

当两个类有重复的功能或函数时,就把这些重复功能创建为父类,让这两个类去继承父类。这样一来,这两个类只用写独自独特的成员函数,重复部分都复用父类的代码即可。

普通类继承

语法

cpp 复制代码
//父类(基类)
class person
{
public:
    str _name;
    int _number;
}

//子类(派生类)
class Student: public person
{
public:
    string _class;
}

此处student类public方式继承于person类,student就也有父类的成员了。

子类访问父类成员的访问限定符限制

|----------------|----------------|----------------|--------------|
| 类成员/继承方式 | public继承 | protected继承 | private继承 |
| 基类的public成员 | 派生类public成员 | 派生类protected成员 | 派生类private成员 |
| 基类的protected成员 | 派生类protected成员 | 派生类protected成员 | 派生类private成员 |
| 基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |

①可父类成员在子类中的访问方式取决于谁的限定符限制更大。比如基类protected和子类private继承,private限制大,所以就能访问方式为private,此时可以访问基类的protected和public成员。

②父类的private成员,无论如何都无法被子类直接使用。只能靠调用父类的为被限制的函数进行访问。

③使用class则默认为private继承,使用struct则默认为public继承。

继承类模板

当基类是类模板时,需要指定类域。

因为模板是按需实例化,还未被使用的父类成员函数由于未实例化,无法被找到,编译会报错,所以要指明类域来避免这种错误。

同样的,如果父类有内部类,也要注意是否被实例化的问题。

基类与派生类之间的转换

①public继承的派生类对象可以赋值给基类的指针/引用。(也叫赋值兼容转换)

cpp 复制代码
Student sobj;

Person *pp = &sobj;
Person &rp = sobj;

把sobj赋值给了Person类指针/引用,这时我们使用这些指针/引用时就不会访问到sobj的独有成员。

这种操作也叫做切片、切割。

②但是基类对象不可以赋值给派生类对象。

继承的隐藏作用域

若子类和父类有同名的成员,访问时默认优先访问最近类域中的成员,我们也可以自行指明类域来访问。

那么不访问的同名成员,就会被隐藏,这些同名的成员构成隐藏关系。

被隐藏的成员无法被使用。

cpp 复制代码
class A
{
public:
    void fun()
    {
        cout << "Hi" << endl;
    }
};

class B : public A
{
public:
    void fun(int i)
    {
        cout <<"int i" << endl;
    }
};

此处A类的fun被隐藏,若要使用A的fun函数,就要指定类域来使用。

派生类的默认成员函数

构造函数

派生类的默认构造,最好要自己写。对继承的父类成员,我们要用父类的构造函数进行构造。

cpp 复制代码
class Person
{
public:
    Person(const char* name)
        :_name(name)
    {}
protected:
    string _name;
};

class Student : public Person
{
public:
    Student(int num, const char* address, const char* name)
        :_num(num)
        ,_address(address)
        ,Person(name)//父类成员使用父类的构造。
    {}
protected:
    int _num;
    string _address;
};

父类的部分,我们把它们看作一个整体,统一走父类的构造函数来构造。

注意格式哦!

ps:若某类的默认构造为私有的,那么这个类将无法被继承。

或者被final修饰之后就不可以被继承了(关键词final在C++11支持)。

拷贝构造

一般使用默认生成的拷贝即可。同样的,父类的成员要调用父类的拷贝构造。默认生成的拷贝是浅拷贝,如果不能满足需要就要自己写一个深拷贝的拷贝构造。

要注意的是,子类的赋值运算符重载要用到父类的赋值运算符重载,父类的赋值运算符重载会被隐藏,需要指定类域来使用。

析构函数

一般用默认生成的析构即可。顺序是先析构子类,再析构父类。

当我们自己写析构函数的时候,要注意不用显示调用父类的析构时。

在默认情况下,父类的析构函数与子类的构成隐藏关系,这是规定,详细原因再有关多态的部分可以讲。

继承与友元

一句话概括,友元关系不可以继承。

继承和静态成员

一句话概括,父类和子类共用父类的静态成员

单继承、多继承和菱形继承

语法:

cpp 复制代码
class Assistant:public Student,public Teacher

单继承:一个子类只有一个直接父类。

多继承:一个子类(C)有两个或以上的直接父类(A、B)。

菱形继承:某子类(D)间接多次继承同一个基类(A)。

此时因为D间接继承了两次父类A,所有会有两份父类A的成员,导致数据冗余。

多继承和菱形继承会造成数据冗余和二义性(若有同名变量可能会不发辨别是哪个类的变量,或者这个变量是哪个父类或者哪个子类的),即使二义性可以靠指定类域解决,但是数据冗余的问题无法解决。

虚继承virtual

用于解决菱形继承的二义性和数据冗余的问题。

用关键字virtual修饰菱形腰部的类(上图的B、C),编译时就只会保留一份数据。

了解即可,实际上最好不要使用多继承,即使使用多继承,也最好不要出现菱形继承的情况。

多继承的指针偏移

cpp 复制代码
//D多继承于B1和B2
class B1 { public: int _b1;};
class B2 { public: int _b2;};
class D : public B1, public B2 { public: int _d;};

int main()
{
    D d;
    B1* p1 = &d;
    B2* p2 = &d;
    D* p3 = &d;

    return 0;
}

三个指针都用&d来赋值,但是p1 == p3 != p2。

可以用赋值兼容转换的切割来解释。

如图

p1和p2只能指向d继承父类的部分,p1指向B1部分,p2指向B2部分。而且,虽然p1 == p3但是两个指针的含义也是不同的,p1是对继承父类B1的切片,p3指向的是整个D。

继承和组合

继承是is-a的关系(如Student类继承于Person类,即Student is a Person)。

组合是has-a的关系(如Car类有Seat类的成员,即Car has a Seat)。

两者都对代码进行了复用,能够提高开发效率。

不同的是:

|---------|------------------|--------------------|
| 特性/复用方式 | 继承(白箱复用) | 组合(黑箱复用) |
| 可见性 | 父类的成员对子类可见 | 内部细节不可见 |
| 封装性 | 破坏基类封装,派生类依赖基类实现 | 保持类的封装,组合类依赖接口而非实现 |
| 耦合性 | 高耦合 | 低耦合 |

白箱和黑箱是相对于可见性而言的,白箱可见细节,黑箱不可见细节。

耦合性高的代码,每个部分环环相扣,如果某处出错或者进行修改,就会牵一发而动全身,这对代码的维护工作来说是不利的。

因此,实际应用中尽量使用组合,主要还是看类与类之间的关系。如果是更符合is-a关系,就用继承;如果更符合has-a关系,就用组合;如果两者都符合(比如链表和栈),那就优先使用组合。

❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤

相关推荐
纵有疾風起8 小时前
【C++11深度解析(2)】从新增类功能到智能指针的现代 C++ 核心新特性
开发语言·c++·经验分享·开源
Chase_______8 小时前
【JAVA基础指南(一)】快速掌握基础语法
java·开发语言
沧澜sincerely8 小时前
蓝桥杯103 日期问题
c++·蓝桥杯
小白学大数据8 小时前
Python 爬虫如何分析并模拟 JS 动态请求
开发语言·javascript·爬虫·python
秦少游在淮海8 小时前
网络缓冲区 · 通过读写偏移量维护数据区间的高效“零拷贝” Buffer 设计
linux·开发语言·网络·tcp协议·muduo·网络缓冲区
qs70168 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
zoujiahui_20188 小时前
python中模型加速训练accelerate包的用法
开发语言·python
码界奇点8 小时前
基于Golang的分布式综合资产管理系统设计与实现
开发语言·分布式·golang·毕业设计·go语言·源代码管理
xiaolongmeiya8 小时前
P3810 【模板】三维偏序 / 陌上花开 cdq分治+树状数组
c++·算法