当前位置: 首页 > news >正文

【C++进阶九】继承和虚继承

【C++进阶九】继承和虚继承

  • 1.什么是继承
  • 2.继承关系
    • 2.1protected和private的区别
    • 2.2通过父类的函数去访问父类的private成员
    • 2.3默认继承
  • 3.基类和派生类对象的赋值转换
  • 4.继承中的作用域
  • 5.子类中的默认成员函数
  • 6.继承与静态成员
  • 7. 菱形继承
  • 8.虚继承
  • 9.继承和组合

1.什么是继承

继承是代码复用的一种手段,子类继承父类就能使用父类中的变量
比如我们同时定义student类和teacher类
在这里插入图片描述
二者有大量的相同信息,因此可以把共同信息提取出来单独形成一个类

struct Person
{
	string name;
	string sex;
	int age;
	int height;
}

只用在实现student类和teacher类时继承person类即可

class Student : public Person//继承
{
protected:
	int _stuid;//学号
};
class Teacher : public Person//继承
{
protected:
	int _jobid;//工号
};

在这里插入图片描述
子类通过继承可以获得父类中的成员

Student st;
st._stuid = 123456;
st.name = "张三";
st.age = 20;

2.继承关系

类中的三种访问限定符和继承的三种方式组成了下表:
在这里插入图片描述
基类中的public成员通过protected继承方式到派生类中,变成派生类的protected成员,以此类推
基类中的private成员,通过任何继承方式都是不可见的,继承下来了但是不能用,子类没办法调用父类中的private成员变量,不想要被子类继承,就可以设置为private

2.1protected和private的区别

protected和private访问限定符被认为都是一样的,都是在类里面可以访问,类外不能访问
只有在继承的时候二者才有区别,基类设置为private后,子类继承不可见在这里插入图片描述

而基类设置为protected后,子类继承可以使用在这里插入图片描述

2.2通过父类的函数去访问父类的private成员

虽然基类的成员变量是由private修饰的,只是派生类中不可以用,但是子类student 可以调用父类的函数去访问
在这里插入图片描述

2.3默认继承

继承方式可以不写
使用关键字class时,默认的继承方式是private
使用关键字struct时,默认的继承方式是public

class Student : Person//继承
{
protected:
	int _stuid;//学号
};

3.基类和派生类对象的赋值转换

在共有继承的前提下,将子类对象赋值给父类对象天然支持,不存在类型转换发生,认为子类对象就是特殊的父类对象

由于d是double类型,而i是int类型,将d赋值给i会发生隐式类型转换,产生一个int类型的临时变量,再将临时变量传给i,但是由于临时变量具有常性,所以i需要使用const修饰

double d = 1.1;
const int& i = d;

而将子类对象赋值给父类对象不会产生临时变量,不需要const修饰

student st;
person p = st;

在这里插入图片描述
在子类中,把和父类相同的那部分找到,调用父类的拷贝构造依次将其拷贝到父类
这个过程被叫做切割或者切片

例:

student st;
person& p = st;

在这里插入图片描述

student st;
person* p_ptr = &st;

在这里插入图片描述

4.继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域
  2. 子类和父类有同名成员,子类成员将屏蔽父类并对同名成员的直接访问,这种情况也叫隐藏,或者重定义
    (在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
  4. 注意在实际中的继承体系里面最好不要定义同名的成员
class Person
{
protected :
	int _num = 0010;   //身份证号
};

class Student : public Person
{
protected:
	int _num = 1119; //学号
};

在main函数中定义student对象后再打印_num默认为子类中的_num,若想打印父类中的_num,需要指定类域

Student st;
cout << st._num;
cout << st.Person::_num;

5.子类中的默认成员函数

在这里插入图片描述

  1. 子类中的父类的成员必须调用父类的构造去初始化(拷贝构造也是)
    如果不显示调用,会在初始化列表去调用父类的默认构造函数:自己实现的全缺省的构造函数
class Person
{
public:
    Person(const char* name = "peter")//全缺省构造函数
        : _name(name)
    {
        cout << "Person()" << endl;
    }
protected:
    string _name; // 姓名
};

class Student : public Person
{
public:
	student(const char* name,int num)//构造函数
		:person(name)//显示调用父类的构造
		,_num(num)
	{
	}
protected :
    int _num;
};
  1. 子类的拷贝构造需要调用父类的拷贝构造
    父类的拷贝构造的const person& p = s,p作为子类Student中父类那部分的别名,被称之为切片或者切割
class Person
{
public:
    Person(const person& p)//拷贝构造
        : _name(p.name)
    {
        cout << "Person()" << endl;
    }
protected:
    string _name; // 姓名
};

class Student : public Person
{
public:
	student(const student& s)//拷贝构造
		:person(s)//显示调用父类的拷贝构造
		,_num(num)
	{
	}
protected :
    int _num;
};
  1. 子类的operator=中必须调用父类的operator=完成父类成员赋值
    在子类中operator=前面需要指定作用域从而调用父类的operator=(因为子类的operator= 与父类的operator= 形成隐藏,会优先使用子类的operator=)
class Person
{
public:
	person& operator=(const person& p)
	{
		if(this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
protected:
    string _name; // 姓名
};

class Student : public Person
{
public:
	student& operator=(const student& s)
	{
		if(this != &s)
		{
			person::operator=(s);
			_num = s._num;
		}
		return *this;
    }
protected :
    int _num;
};
  1. 子类的析构函数不用显示调用父类的析构,编译器会自动去调用
    (构造时,先构造父类后构造子类,所以析构时,要先析构子类再析构父类,为了减少错误,编译器在子类的析构函数完成时,会自动调用父类的析构函数,保证先析构子,再析构父)
  2. 子类初始化对象时,先初始化父类的成员变量,再初始化子类的成员变量
  3. 友元关系不能被继承
    一个函数是父类的友元,但不是子类的友元

6.继承与静态成员

继承并不会把static修饰的静态成员变量继承下来,但是可以访问它
静态成员变量属于整个类,即属于父类,也属于子类

在这里插入图片描述
在父类和子类中,都可以调用_count,并且地址相同,说明是同一个_count

7. 菱形继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述
但多继承可能会导致菱形继承
在这里插入图片描述
会导致在对象中存在两份person的信息,就存在了数据冗余和二义性(二义性:直接访问不知道访问谁,因为有两个父类带来的两个名字)
在这里插入图片描述

指定访问可以解决二义性:
在这里插入图片描述
但是实际上依旧是不合理的,大量相同的信息造成数据冗余,本质为空间浪费

8.虚继承

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

非虚继承版本:

#include<iostream>
using namespace std;

class A
{
public:
    int _a;
};

class B : public A
{
public:
    int _b;
};

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

class D : public B, public C
{
public:
    int _d;
};

int main()
{
    D d;
    
    d.B::_a = 1;//指定访问解决二义性
    d.C::_a = 2;//指定访问解决二义性
    
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

调试并调用内存窗口
在这里插入图片描述
由于A是B和C的父类,所以A类的_a在B类和C类中都存在
B类本身有一个_b的成员变量
C类本身有一个_c的成员变量
在这里插入图片描述

虚继承版本:

#include<iostream>
using namespace std;
class A
{
public:
    int _a;
};
//class B : public A
class B : virtual public A
{
public:
    int _b;
};
//class C : public A
class C : virtual public A
{
public:
    int _c;
};
class D : public B, public C
{
public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

可以看到在调试模式下存在三份_a的数据,但内存中只有一份地址储存_a
在这里插入图片描述

相比于不是虚继承的版本,03 和04上面多个一份地址存在
在这里插入图片描述
换成8列观察:
在这里插入图片描述
使用地址00007FF73F4B9C20加上偏移量40正好为00 00 00 02即A的地址
使用地址00007FF73F4B9C20加上偏移量24正好为00 00 00 02即A的地址
在这里插入图片描述

因为B类和C类中都存在A,存在数据冗余和二义性,所以把A的数据放入公共区域,既不放入B中,也不放入C中
通过偏移量来寻找这个公共的位置
这两个偏移量没有存到各自的第一个位置上,因为第一个位置是为以后的多态做准备的、

解决数据冗余和二义性,会增加了两个指针,只节省了一个A,即4个字节,相当于多消耗了4个字节
但如果A变大,指向的空间忽略不计,会创建很多对象,每个对象都指向该空间,由大家共同分担,所以消耗可以忽略不计,节约大量空间

9.继承和组合

public继承是一种is-a的关系,每个派生类对象都是一个基类对象,如:学生和人,学生是一个人
组合是一种has-a的关系,假设B组合了A,每个B对象中都有一个A对象,如:车和轮胎的关系 ,车有轮胎
在这里插入图片描述
耦合度指的是关联关系,继承关系更紧密一些 ,说明继承的耦合度高
B可以直接用A的3个成员
D可以直接用C的一个成员,间接用另外的两个成员

若把A类中的_a1改了,会影响B类,A改动保护可能影响B
若把C类中的_c1改了,不会影响D类,C改动保护和私有成员基本不影响D

低耦合,高内聚
所以单个模块之间关联越低越好,这样一个模块出现问题,最大值程度上减少对另一个模块的影响
若两个类完成的功能是一样的,就把他们两个放在一起,若这两个类毫不相关,就不要合在一起
尽量使用组合去降低耦合度,但要用多态时就需要使用继承,多态是建立在继承之上的

http://www.dtcms.com/a/110053.html

相关文章:

  • 【新书上市】《STM32嵌入式系统开发—基于STM32CubeMX和HAL库》,高校教学推荐,STM32入门必读书籍!
  • TVM流程及调优
  • skynet.start 的作用详细解析
  • Jira 停售一周年:国产研发管理平台能否扛起替代大旗?
  • 【补题】Xi‘an Invitational 2023 E. Merge the Rectangles
  • 【PHP】- 字符串,数组,字典常用操作
  • Cribl 脱敏mask
  • (三)机器学习---线性回归及其Python实现
  • 聚焦建筑业碳中和,楼宇自控系统应如何发挥建筑节能价值
  • 大语言模型中的嵌入模型
  • (C语言)单链表(2.0)数据结构(指针,单链表教程)
  • 栈和队列的概念
  • dfs递归回溯的两种体型
  • 水下声呐探测仪,应急救援中的高效水下定位技术|深圳鼎跃
  • Nuxt3项目的SEO优化(robots.txt,页面tdk,伪静态.html,sitemap.xml动态生成等)
  • 开源虚拟化管理平台Proxmox VE部署超融合
  • RHCSA LINUX系统文件管理
  • 市场交易策略优化与波动管理
  • 6.模型训练4-毕设篇
  • 【Prometheus】kube-state-metrics 的详细说明
  • 【学习笔记】计算机网络(七)—— 网络安全
  • Metasploit 反弹Shell
  • eplan许可证常见问题及解决方法
  • 数据结构(JAVA)单向,双向链表
  • 解析CSRF攻击
  • Transformer架构详解:从Encoder到Decoder的完整旅程
  • VSCode历史版本的下载安装
  • 破解AI编程瓶颈:上下文管理助力高效开发,以Cline为例
  • kornia,一个实用的 Python 库!
  • 环形链表相关题目