C++继承

2023-11-14

继承的概念

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

example:以下代码就是一个继承

//基类(父类)
class person
{
public:
    string _name;
    int _age;
};

//子类
class student :public person//继承语法
{
public:
    int _stuid;
};

class teacher :public person
{
public:
    int _jobid;
};

继承后,父类person的成员,包括成员函数和成员变量,都会变成子类的一部分,等于说子类student和teacher复用了父类person的成员。

继承的定义

继承的定义格式

 在继承当中,父类也称为基类,子类是由基类派生而来的,所以子类又称为派生类。 

继承方式和访问限定符

访问限定符有以下三种

  1. public访问
  2. protected访问
  3. private访问

继承的方式也一样

  1. public继承
  2. protected继承
  3. private继承

继承基类成员访问方式的变化

 三种访问限定符的权限大小为:public > protected > private

基类成员访问方式的变化规则如下

在基类当中的访问方式为public或protected的成员,在派生类当中的访问方式变为成员在基类的访问方式和继承方式权限小的。

在基类当中的访问方式为private的成员,在派生类当中都是不可见的

什么是派生类中不可见?

无法在派生类当中访问基类的private成员

class person
{
private:
    string _name;
};


class student :public person
{
public:
    void Print()
    {
        cout << _name << endl;//error 无法在派生类当中访问基类的private成员
    }
protected:
    int _stuid;
};

这里的不可见是指基类的私有成员虽然被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

如果基类成员不想在类外直接被访问,但需要在派生类中访问,就需要protected限定符

注意: 在实际运用中一般使用的都是public继承,几乎很少使用protected和private继承,也不提倡使用protected和private继承,因为使用protected和private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

默认继承方式

在使用继承的时候也可以不指定继承方式,使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public

class person
{
public:
    string _name;
    int _age;
};

//子类
class student : person//不写默认是私有继承
{
protected:
    int _stuid;
};

struct student : person//不写默认是公有继承
{
protected:
    int _stuid;
};

最好显示写出继承方式

基类和派生类对象赋值转换

派生类对象可以赋值给基类的对象、基类的指针以及基类的引用

//基类
class Person
{
protected:
    string _name; 
    string _sex;  
    int _age;    
};
//派生类
class Student : public Person
{
protected:
    int _stuid; 
};

int main()
{
    Student s;
    Person p = s;//派生类对象赋值给基类对象
    Person &ref = s;//派生类对象赋值给基类引用
    Person* ptr = &s;//派生类对象赋值给基类指针
}

这里有个形象的说法叫做切片/切割,寓意把派生类中基类那部分切来赋值过去
派生类对象赋值给基类对象(另外两种类似)

注意:
基类对象不能赋值给派生类对象,基类的指针可以通过强制类型转换赋值给派生类的指针,有越界访问风险,但是此时基类的指针必须是指向派生类的对象才是安全的

继承中的作用域

继承体系中的基类和派生类都有独立的作用域

若子类和父类中有同名成员,子类成员将屏蔽对父类的同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员显示访问)

如果是对成员函数的隐藏,只需要函数名相同即可构成隐藏

最好不要定义重名的成员。

同名成员变量_age的访问

class Person
{
protected:
    int _age = 20;//对内置类型给初始值
};
//子类
class Student : public Person
{
public:
    void print()
    {
        cout << _age << endl;
    }
protected:
    int _age = 18;
};

int main()
{
    Student s;
    s.print();//打印18,隐藏了父类的同名成员
}

void print()

{
     cout << Person::_age << endl;//对父类中的_age访问

}

涵数名相同构成隐藏

class Person
{
public:
    void print(int x)
    {
        cout << x << endl;
    }
};
//子类
class Student : public Person
{
public:
    void print(double x)
    {
        cout << x << endl;
    }
};

int main()
{
    Student s;
    s.print(1.21);//打印1.21
    s.Person::print(1);//打印1
}

注意:

父类中的print和子类中的print不构成函数重载,因为函数重载要求两个函数域在同一作用,而此时这两个print函数是不同的作用域。实际在继承体系当中最好不要定义同名的成员。

派生类的默认成员函数

默认成员函数,我们不写编译器会自动生成的函数,类当中的默认成员函数有以下六个

派生类当中的默认成员函数与普通默认成员函数的不同之处

//Person的四个默认成员函数

class Person
{
public:
    //构造函数
    Person(const string& name = "pride")
        :_name(name)
    {
        cout << "Person()" << endl;
    }
    //拷贝构造函数
    Person(const Person& p)
        :_name(p._name)
    {
        cout << "Person(p)" << endl;
    }
    //赋值运算符重载函数
    Person& operator=(const Person& p)
    {
        cout << "operator=(&p)" << endl;
        if (this != &p)
        {
            _name = p._name;
        }
        return *this;
    }
    //析构函数
    ~Person()
    {
        cout << "~Person()" << endl;
    }
private:
    string _name; //姓名
};

//Student的四个默认成员函数

class Student :public Person
{
public:
    Student(const string& name, int id)
        :Person(name)//调用基类的构造函数初始化继承下来的基类成员变量name
        ,_stuid(id)
    {
        cout << "Student()" << endl;
    }
    Student(const Student& s)
        : Person(s)//调用基类的拷贝构造函数完成基类成员的拷贝构造
        ,
_stuid(s._stuid)
    {
        cout << "Student(s)" << endl;
    }
    Student operator=(const Student& s)
    {
        cout << "operator=(&s)" << endl;
        if (this != &s)
        {
            Person:: operator=(s);//调用基类的operator=完成基类成员的赋值
            _stuid = s._stuid;
        }
        return *this;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
        //派生类的析构函数会在被调用完成后自动调用基类的析构函数
    }
private:
    int _stuid;
};

派生类与普通类的默认成员函数的不同之处概括为以下几点

1.派生类的构造函数被调用时,会自动调用基类的构造函数初始化基类的那一部分成员,如果基类当中没有默认的构造函数,则必须在派生类构造函数的初始化列表当中显示调用基类的构造函数

2.派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类成员的拷贝构造。

3.派生类的赋值运算符重载函数必须调用基类的赋值运算符重载函数完成基类成员的赋值。

4.派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。

5.派生类对象初始化时,会先调用基类的构造函数再调用派生类的构造函数。

6.派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数。
 

编写派生类的默认成员函数时,需要注意以下几点

派生类和基类的赋值运算符重载函数因为函数名相同构成隐藏,因此在派生类当中调用基类的赋值运算符重载函数时,需要使用作用域限定符进行指定调用

任何类的析构函数名都会被统一处理为destructor();。因此,派生类和基类的析构函数也会因为函数名相同构成隐藏,若是我们需要在某处调用基类的析构函数,就要使用作用域限定符进行指定调用

在派生类的拷贝构造函数和operator=当中调用基类的拷贝构造函数和operator=的传参方式是一个切片行为,都是将派生类对象直接赋值给基类的引用

除了析构函数是当子类的析构函数被调用后由编译器自动调用,不能我们手动调用(多次析构问题以及调用析构顺序变换),其他几个都可自行调用。

继承与友元

友元关系不能继承,也就是说基类的友元可以访问基类的私有和保护成员,但是不能访问派生类的私有和保护成员

继承与静态成员

若基类当中定义了一个static静态成员变量,则在整个继承体系里面只有一个该静态成员。无论派生出多少个子类,都只有一个static成员实例

静态成员在类外初始化

继承的方式

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

多继承:一个子类有两个或两个以上直接父类

菱形继承:菱形继承是多继承的一种特殊情况

菱形继承的继承方式存在数据冗余和二义性的问题

二义性:访问子类(菱角)继承父类(菱顶)对象时,若是菱形继承,会存在二义性,可以显示调用解决,但是数据冗余并没有解决。

菱形虚拟继承

为了解决菱形继承的二义性和数据冗余问题,出现了虚拟继承

虚继承的位置是在父类(菱顶)相连的子类(这里是B和C),菱形继承不一定是对称菱形。

class A
{
protected:
    int _a;
};
class B :virtual public A
{
protected:
    int _b;
};

class C :virtual public A
{
protected:
    int _c;
};

class D :public A, public B//多继承用 ‘,’隔开
{
protected:
    int _d;
};

菱形虚拟继承解决了二义性和冗余性问题,不在D中对象存储B和C对象继承A对象的内容,而是通过虚基表指针找到虚基表。

虚基表中包含两个数据,第一个数据是为多态的虚表预留的存偏移量的位置(多态时了解),第二个数据就是当前类对象位置距离公共虚基类的偏移量。

继承和组合

继承是一种is-a的关系,老师是人,学生是人;而组合是一种has-a的关系,若是B组合了A,那么每个B对象中都有一个A对象,脸有五官。

组合语法

class face
{
protected:
    string _color; 
};
class organ
{
protected:
    string _nose; 
    string _ear; 
    face _c; //组合
};

若是两个类之间既可以看作is-a的关系,又可以看作has-a的关系,则优先使用组合。

原因如下:

继承允许你根据基类的实现来定义派生类的实现,这种通过生成派生类的复用通常被称为白箱复用(White-boxreuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对于派生类可见,继承一定程度破坏了基类的封装,基类的改变对派生类有很大的影响,派生类和基类间的依赖性关系很强,耦合度高。


组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称之为黑箱复用(Black-box reuse),因为对象的内部细节是不可见的,对象只以“黑箱”的形式出现,组合类之间没有很强的依赖关系,耦合度低,优先使用对象组合有助于你保持每个类被封装。


实际中尽量多使用组合,组合的耦合度低,代码维护性好。不过继承也是有用武之地的,有些关系就适合用继承,另外要实现多态也必须要继承。若是类之间的关系既可以用继承,又可以用组合,则优先使用组合。
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++继承 的相关文章

随机推荐

  • 注解@TableName、@TableField

    目录 TableName value 当数据库名与实体类名不一致或不符合驼峰命名时 需要在此注解指定表名 不加这个注解默认将实体类的小写形式在db中寻找 TableField 字段注解 该注解用于标识非主键的字段 将数据库列与 JavaBe
  • 幂函数与指数函数的区别

    a表示底数 n表示指数 a n叫做幂 幂就是一个数和它自己相乘的积 二个乘是二次幂 三个乘是三次幂 四个乘是四次幂 象三 五这样的幂是奇次幂 二 四是偶次幂负数乘负数是正数 负数乘正数是负 幂函数与指数函数的区别 指数函数 自变量 x 在指
  • 关于欧拉角的问题

    一 简单介绍 自己主要做一个知识记录 想着学了还是要写点东西的 首先我们可以把欧拉角看成是描述方位的一种方法 我们可以用欧拉角来表示旋转 也可以用四元数 以及用矩阵来表示旋转 欧拉角是一种常用的描述方位的方法 在这里简单的介绍下方向和方位的
  • 阿里巴巴“三板斧”管理到底是什么?

    阿里巴巴从最初的以马老师为首的18罗汉创始员工 发展至今拥有4万员工 从杭州的湖畔花园起家 到去美国纽约证券交易所上市敲钟 阿里巴巴如何走到现在 它背后的管理机制是怎样的 我们到底向它学什么 阿里巴巴管理总纲 阿里巴巴九板斧 中层能力三板斧
  • 哈希表与树的介绍

    前言 该篇文章 主要带我们认识什么哈希表和树 为我们在研究各个数据结构的实现及扩展算法 有个基本的认识 哈希表 特点 数组 寻址容易 数据连续存储空间 链表 插入与删除容易 放在堆内存中对象 存储并不连续 哈希表 寻址容易 插入删除也容易的
  • 多模态机器翻译

    摘录自 机器翻译 基础与模型 东北大学 文章目录 1 背景 2 机器翻译需要更多的上下文 3 图像翻译 4 基于图像增强的文本翻译 4 1 基于特征融合的方法 4 2 基于联合模型的方法 5 参考文献 1 背景 基于上下文的翻译是机器翻译的
  • js实例学习笔记

  • Elasticsearch ILM (index lifecycle management): logstash vs fluentd

    ILM Elasticsearch在升级到7 x之后 推出了一项新功能ILM 用于管理被大家诟病已久的index lifecycle management问题 只需要在kibana内简单配置 就可以管理以前我们不得不设置cronjob去删除
  • C#比较运算符及解析

    文章目录 博主写作不容易 孩子需要您鼓励 万水千山总是情 先点个赞行不行 比较运算符得出的结果是逻辑型 bool 即 True 或 False 比较运算符又称关系运算符 我们可以把它理解为一种判断 判断的结果是真或者是假 关系表达式的返回值
  • Android 权限大全-转载

    Android 权限大全 转自博客园 博客园链接 Key android permission ACCESS CHECKIN PROPERTIES Title 访问检入属性 Memo 允许对检入服务上传的属性进行读 写访问 普通应用程序不能
  • 初入HTML

    1 HTML语言用来做什么 html语言专门用来描述网页 它属于一种标记语言 它是由一组标签构成 2 HTML元素 一个HTML元素是包含了开始标签和结束标签 当然 还有一些是单标签 例如 p 段落标签 p 双标签 br 换行标签 单标签
  • openlayers地图坐标coordinate转换为屏幕像素坐标pixel

    openlayers地图坐标coordinate转换为屏幕像素坐标pixel 网上查资料试了很多人的方法 需要各种转换但没成功 后来发现openlayers的map对象自带该方法 记录下来 希望帮助到大家 方法说明 获取坐标的像素坐标 这将
  • 随机森林补充缺失值

    导入必要的库 import numpy as np import pandas as pd from sklearn ensemble import RandomForestRegressor 读取数据 data data all1 找出所
  • 3D数学基础——向量与矩阵变换

    向量相乘 1 点乘 两个向量的点乘等于他们的数乘结果乘以两个向量之间家教的余弦值 v k v k cos cos v k v k 通过点乘的结果计算两个非单位向量的夹角 2 叉乘 叉乘只在3d空间中有定义 他需要两个不平行向量作为输入 生成
  • nvm install node没反应_LINUX使用nvm安装node,nrm的使用

    为什么要使用nvm来安装node 我们在开发过程中 特别是协作开发时 通常会对具体的node的版本有限制 我们使用nvm可以轻松解决这个问题 nvm安装node的好处就是可以切换node版本 用起来方便 所以介绍下如何使用nvm安装node
  • 【Vs Code 学习笔记】

    Vs Code 远程连接服务器 详细教程 默认你已经安装好了Vs Code 1 如果没有请参考官网链接 https code visualstudio com 直接安装就可以了 2 打开VsCode 你可以看到如下界面 然后在按照如下操作下
  • Java的Properties属性集、获取项目路径的3种方式(干货满满)

    属性集介绍 集合家族中有个成员java util Properties 它继承于Hashtable Properties是使用键值结构存储数据的 但它最大的特点是具有持久化功能 持久化 内存 gt 硬盘 持久化的过程必须依赖于IO流 对IO
  • MyBatis执行器与新增返回主键问题

    前提 在写需求时碰到一个问题 在新增加一条数据时需要返回主键并进行后续操作 发现当前项目并不能返回主键 正常返回主键代码 1
  • PTA C 7-3 计算职工工资

    给定N个职员的信息 包括姓名 基本工资 浮动工资和支出 要求编写程序顺序输出每位职员的姓名和实发工资 实发工资 基本工资 浮动工资 支出 输入格式 输入在一行中给出正整数N 随后N行 每行给出一位职员的信息 格式为 姓名 基本工资 浮动工资
  • C++继承

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