【C++进阶:继承上】

简介: 在 C++ 初阶专栏 ➡ 类和对象一文中,我们提出了面向对象的三大特性 —— 封装、继承、多态。但在 C++ 初阶专栏中涉及到的只是封装,而这里我们直接以封装和继承作为 C++ 进阶专栏中的敲门砖。

【写在前面】

在 C++ 初阶专栏 ➡ 类和对象一文中,我们提出了面向对象的三大特性 —— 封装、继承、多态。但在 C++ 初阶专栏中涉及到的只是封装,而这里我们直接以封装和继承作为 C++ 进阶专栏中的敲门砖。

我们说过 C++ 是大佬从 C 发展出来的,最开始的 C++ 叫做 C With Class,就是在 C 的基础上增加了类。经过 C++ 初阶的学习,我们知道了 C++ 中类,就是为了对标并解决 C 的缺陷,比如构造、析构等。

在 C With Class 时,在类的设计层面,C++ 还面临一个问题,假设:

在这里插入图片描述

  我们发现每个角色都有公共的信息,如果在每个类中都写一份,构造和析构时会对每个冗余的信息都处理一次,这是设计层面上的困境。

面对这种困境,不谈继承,如何解决 ❓

在这里插入图片描述

  我们把公共的信息提取出来封装成一个类,每个角色创建时就调用。这样就完成类层面的复用,这里 C 也是支持的。

这种复用有什么缺陷 ???

  没错,它确实可以解决代码冗余的困境,但是它肯定有缺陷,不然 C++ 就不会有继承了。这里的缺陷在于 Student 里复用 Person 时,并不好访问 Person 的成员,因为一般都会设置为私有。所以 C++ 中就衍生了继承。

一、继承的概念及定义

💦 继承的概念

继承 (inheritance) 机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,而继承是类设计层次的复用。

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    void print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "DanceBit";
    int _age = 18;
};

class Student : public Person
{
protected:
    int _stuid;
};

class Teacher : public Person
{
protected:
    int _jobid;
};

int main()
{
    Student s;
    s.print();

    Teacher t;
    t.print();

    return 0;
}

在这里插入图片描述

  其中 Person 就叫做父类或基类;Student、Teacher 叫做子类或派生类。Student、Teacher 继承了 Person,Student、Teacher 中就拥有了 Person 的成员变量和成员函数,所以可以 s.print()、t.print(),但是对于一个类对象,它只存储成员变量,成员函数存储于公共代码区。所以继承的本质是类级别的复用。

  这里一继承就把父类的所有东西都继承了,这样也不好。好比皇帝不想干了,让年纪较小的太子继位,但并不是所有的事都交给太子来决策,而是重要的事要给皇帝请示。所以对于继承来说,我们需要能灵活控制,针对不同场景,我们可以部分继承、半继承、全继承、暂时不继承。

💦 继承定义

1、定义格式

在这里插入图片描述

  Student 是子类或派生类;Person 是父类或基类;public 是继承方式;C++ 把这块区分后,它的继承方式有 3 种。

2、继承关系和访问限定符

在这里插入图片描述

  Student 继承 Person,Person 里的成员变量在 Student 中到底是什么样的访问方式,它是由这里的继承方式和原来的访问限定符所决定的。

3、继承基类成员访问方式的变化
类成员/继承方式 public继承 ptotected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员
基类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员
基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见
  • 实际从上面的表格总结会发现,基类的私有成员在子类都是不可见的。其余我们认为派生类中的访问方式是参照类成员原来的访问方式和继承方式,我们认为它们的权限关系是 public > protected > private,那么派生类最终的访问方式是取 Min(访问方式,继承方式)。
  • 在类和对象中我们说过,想被访问的成员设置为 public,不想被访问的成员设置为 private 或 protected。我们当时说 private 和 protected 的区别到了继承才能体现,体现如下。
#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    void print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "DanceBit";
private:
    int _age = 18;
};

class Student : public Person
{
    void f()
    {
        _name = "dancebit";
        print();
        //_age = 20;
    }
protected:
    int _stuid;
};

class Teacher : protected Person
{
    void f()
    {
        _name = "dancebit";
        print();
        //_age = 20;
    }
protected:
    int _jobid;
};

class Other : private Person
{
    void f()
    {
        print();
        _name = "dancebit";
        //_age = 20;
    }
protected:
    int _jobid;
};

int main()
{
    Student s;
    s.print();
    //s._name;
    //s._age;

    Teacher t;
    //t.print();
    //t._name;
    //t._age;

    Other o;
    //o.print();
    //o._name();
    //o._age();
    
    return 0;
}

📝 说明:

  • Student 中 f 函数里能访问 _name 的原因是因为访问限定符限制的是类外的人。
  • 任何继承方式继承的私有成员都是不可见,它指的是基类的私有成员,虽然还是被继承到了派生类对象中,但语法限制了不管在类里类外都不能访问,也就是说对象的物理空间上存在,但是类里类外都不能使用。注意区分非继承的私有成员,它是类里可以使用,类外不能使用。
  • 在继承之前,我们以前说想被访问的设置为公有,不想被访问的设置为私有。在继承之后,对于 public 继承:想让子类或类外访问的把成员设置为 public;想让子类访问,但不想让类外访问的把成员设置为 protected;不想让子类访问,也不想让类外访问的把成员设置为 private;所以在继承中,一个类,尽量不要使用 private,因为 private 在子类中不可见,尽量用 protected。这里就可以看出保护成员限定符是因继承而出现的。
  • private 和 protected 成员对于父类是一样的,都是在类里可以访问,类外不可以访问;区别在于,public 继承后,对于子类,private 成员不可见,protected 成员可以在类里访问,类外不能访问。
  • 使用关键字 class 时默认的继承方式是 private;使用关键字 struct 时默认的继承方式是 public。不过最好显示的写出继承方式。其实这块设计时应该强制写出继承方式比较好,不写就报错,就如构造函数的内置类型不处理、访问限定符不一定需要写等。这些都是由于早期设计时,在这方面考虑的不是很周到,也没有经验借鉴,同时 C++ 是向前兼容的,所以就有了我们现在所看到的 C++ 很多细细小小的坑。我们也不能站在现在的时代去指责历史,就像你不能跟你爸说:爸,你当初要是好好学习,奋斗一番事业,我现在就不会在这码字了。

    在这里插入图片描述

  • 当时 C++ 设计时,考虑的很健全,但在健全的同时,也增加了学习的成本,且这种成本价值不大,因为实际中常用的只有基类的 public 成员和 public 继承方式、基类的 protected 成员和 public 继承方式,几乎很少使用 protected/private 继承和 private 访问限定符,当然也不提倡使用,因为实际中扩展维护性很差。所以后来的好多语言把这块内容简化了,这也就是 C++ 相对其它语言难学的原因,本质就是有些地方考虑的比较复杂,但是这也没办法,因为 C++ 是早期吃螃蟹的人。这里想说的是虽然 C++ 设计的比较复杂,但是我们要往简单去理解。所以对于 C++ 的一些小缺陷小细节的地方,我们应该谦虚的、包容的去学习,不是说它恶心就厌恶它。
相关文章
|
6天前
|
安全 前端开发 Java
【C++】从零开始认识继承二)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
14 1
|
6天前
|
安全 程序员 编译器
【C++】从零开始认识继承(一)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
24 3
【C++】从零开始认识继承(一)
|
6天前
|
存储 编译器 C++
C++中的继承
C++中的继承
12 0
|
6天前
|
设计模式 算法 编译器
【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]
【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]
13 0
|
6天前
|
安全 程序员 编译器
【C++】继承(定义、菱形继承、虚拟继承)
【C++】继承(定义、菱形继承、虚拟继承)
15 1
|
6天前
|
安全 编译器 程序员
[C++基础]-继承
[C++基础]-继承
|
6天前
|
C++ 芯片
【期末不挂科-C++考前速过系列P4】大二C++实验作业-继承和派生(3道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P4】大二C++实验作业-继承和派生(3道代码题)【解析,注释】
|
6天前
|
编译器 C++
【C++进阶】引用 & 函数提高
【C++进阶】引用 & 函数提高
|
6天前
|
安全 Java 程序员
【C++笔记】从零开始认识继承
在编程中,继承是C++的核心特性,它允许类复用和扩展已有功能。继承自一个基类的派生类可以拥有基类的属性和方法,同时添加自己的特性。继承的起源是为了解决代码重复,提高模块化和可维护性。继承关系中的类形成层次结构,基类定义共性,派生类则根据需求添加特有功能。在继承时,需要注意成员函数的隐藏、作用域以及默认成员函数(的处理。此外,继承不支持友元关系的继承,静态成员在整个继承体系中是唯一的。虽然多继承和菱形继承可以提供复杂的设计,但它们可能导致二义性、数据冗余和性能问题,因此在实际编程中应谨慎使用。
18 1
【C++笔记】从零开始认识继承
|
6天前
|
设计模式 编译器 数据安全/隐私保护
C++ 多级继承与多重继承:代码组织与灵活性的平衡
C++的多级和多重继承允许类从多个基类继承,促进代码重用和组织。优点包括代码效率和灵活性,但复杂性、菱形继承问题(导致命名冲突和歧义)以及对基类修改的脆弱性是潜在缺点。建议使用接口继承或组合来避免菱形继承。访问控制规则遵循公有、私有和受保护继承的原则。在使用这些继承形式时,需谨慎权衡优缺点。
25 1