【C++】继承|切片|菱形继承|虚继承

news2024/11/16 13:47:08

目录

​编辑

一.什么是继承

 三大特性

继承的语法

访问控制 

继承的总结

二.继承的作用域 

三.切片 

四. 派生类的默认成员函数

构造函数

 析构函数

拷贝构造

赋值运算符重载

五.单继承和多继承

单继承

多继承

菱形继承

解决方式

六.虚继承 


 

一.什么是继承

C++中的继承是面向对象编程的一个重要特性,它允许一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和方法。是最重要的代码复用的手段

是一种在原有基础上进行拓展,增加功能,产生新类的方式;

体现了由简单到复杂的认知过程。

 三大特性

面向对象语言有三大特性:封装,继承,多态

封装:

1:数据和方法放到一起,把想给访问定义成公有,不想给你访问定义成私有和保护。

2:一个类型放到另一个类型里面,通过typedef 成员函数调整,封装另一个全新的类型。

比如STL库中的vector,list等,都是将一系列函数封装到一起。

继承:

允许一个类(称为子类或派生类)获取另一个类(称为父类或基类)的属性和行为。通过继承,子类可以重用父类的代码,同时还可以扩展和修改这些代码以满足自己的需求。

多态:

指的是同一个方法调用在不同的对象上可以表现出不同的行为。(下篇讲)

继承的语法

// 基类定义
class Base {
    // 成员变量和方法
};

// 派生类定义
//class 新类的名字:继承方式 继承类的名字{};
class Derived : public Base {
    // 派生类可以添加新的成员或覆盖基类方法
};

继承体现的是一种代码的复用,将大家都会有的数据,整合到一起形成一个新的类,大家都可以共享,避免了数据的重复。

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;

	}
protected:
	string _name = "MM";  //姓名
	int _age = 18;  //年龄
};

class Student :public Person
{
protected:
	int _stid;    //学号
};

int main()
{
	Student s;
	cout << "Student:";
	s.Print();
	return 0;
}

访问控制 

使用public, protected, private关键字来控制成员的访问级别

public,和protected继承,子类都可以直接访问使用

protected只能在类中public,没限制,private,不能直接访问

public>protected>private

继承的总结

1.基类private成员无论以什么方式继承到派生类中都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2.基类private成员在派生类中不能被访问如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。

总结:

如果父类内是public成员和protected成员的话,继承后会根据继承方式的改变而将权限变小

二.继承的作用域 

在继承体系中基类和派生类都有独立的作用域
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
注意在实际中在继承体系里面最好不要定义同名的成员。

 例1:成员变量的隐藏

class Person	//基类
{
protected:
	string _name = "MM";
	int _num = 123456;//身份证号
};
class Student :public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;
		cout << "身份证号:" << _num << endl;			//构成隐藏,显示的是派生类成员
		cout << "身份证号:" << Person::_num << endl;			//显示用指定域名方式找到父类的成员变量
	}
protected:
	int _num = 202469;//学号	//成员和基类构成隐藏 (同名)
};
int main()
{
	Student s;
	s.Print();
	return 0;
}

 例2:成员函数的隐藏

class Base	//基类
{
public:
	void func()
	{
		cout << "Base::func()" << endl;
	}
};
class Derive :public Base
{
public:
	void func()		//满足同名,构成隐藏
	{
		cout << "Derive::func()" << endl;
	}
};

int main()
{
	Derive d;
	d.func();
	return 0;
}

下面这个,可以看到明明继承了父类,但是还是会报错,因为两个构成了隐藏(同名)

class Base	//基类
{
public:
	void func()
	{
		cout << "Base::func()" << endl;
	}
};
class Derive :public Base
{
public:

	void func(int i)		//  注意这里!!!满足同名,构成隐藏 
	{
		cout << "Derive::func()" << endl;
	}
};

int main()
{
	Derive d;
	d.func();
	return 0;
}

总的来说,是局部优先原则, 在这个程序中就是,先在子类中查找,子类中没有才会去父类中查找,但是在子类中找到了函数名。在这里不构成重载,因为重载要在同一作用域 

三.切片 

切片派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。

基类对象不能赋值给派生类对象。

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

class Base
{
protected:
	string _name;
	string _sex;
	int _age;
};

class Derive:public Base
{
public:
	int _id;
};

int main()
{
	Derive d;
	Base* b = &d;	//指针
	Base b1 = d;	//对象
	Base& b2 = d;	//引用
	return 0;
}

四. 派生类的默认成员函数

构造函数

编译器会默认先调用父类的构造函数,再调用子类的构造函数。

因为会先调用基类,所以先要确保父类的默认构有效(有效传参,全省,满足初始化)

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}

};
class Derive:public Base
{
public:
	Derive()	//默认构造函数,先构造基类 再构造派生类
	{
		cout << "Derive()" << endl;
	}
};

int main()
{
	Derive d;
	return 0;
}

 

 析构函数

析构函数和构造函数相反。

编译器默认先调用子类的析构函数,再调用父类的析构函数。

派生类析构会自动调用基类的析构

        切勿在派生类主动调用基类析构,        

        如果是指针类型,那么同一块区域被析构两次就会造成野指针的问题。

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	~Base()	
	{
		cout << "~Base()" << endl;
	}

};
class Derive:public Base
{
public:
	Derive()	//默认构造函数,先构造基类 再构造派生类
	{
		cout << "Derive()" << endl;
	}
	~Derive()		//会自动调用基类析构
	{
		cout << "~Derive()" << endl;	
		//Base::~Base();
	}
};
int main()
{
	Derive d;
	return 0;
}

 

拷贝构造

子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分

class Person
{
public:
	Person(string name = "MM")	//缺省  保证基类有效默认构造
		:_name(name)	//初始化列表
	{
		cout << "name:" << _name << endl;
	}
protected:
	string _name;
};
class Studen :public Person
{
public:
	Studen(int age = 1)
		:_age(age)
	{
		cout << "age:" << _age << endl;
	}
	Studen(Studen& s)
		:Person(s)			//派生类对象s 通过切片拿到基类中的值,传给基类
		, _age(s._age)			//取派生类特有的值
	{
		cout << s._age <<" " << s._name << endl;
	}

protected:
	int _age;
};
int main()
{
	Studen s;
	Studen s1(s);
	return 0;
}

赋值运算符重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。

因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,所以这里的赋值要直接修饰限定父类.

class Person
{
public:
	Person(string name = "MM")	//缺省  保证基类有效默认构造
		:_name(name)	//初始化列表
	{
		cout << "name:" << _name << endl;
	}
	Person& operator=(Person& p)
	{
		if (this != &p)
		{
			cout << "调用基类=" << endl;
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};
class Studen :public Person
{
public:
	Studen(string name, int age)
		:_age(age)
	{

	}
	Studen(Studen& s)	//拷贝
		:Person(s)
		,_age(s._age)
	{

	}
	Studen& operator=(Studen& s)
	{
		if (this != &s)	//判断是否自己给自己赋值
		{
			cout << "调用子类" << endl;
			Person::operator=(s); //先给基类赋值
			_age = s._age;
			_name = s._name;
		}
		return *this;
	}
protected:
	int _age;
};
int main()
{
	Studen s("MM",18);
	Studen s1(s);
	Studen s2("mm", 100);
	s = s2;	
	return 0;
}

五.单继承和多继承

单继承

一个子类只有一个直接父类的继承关系。

多继承

一个子类有两个或以上直接父类的继承关系。

菱形继承

有了多继承,自然就会出现菱形继承菱形继承是多继承的一种特殊情况

菱形继承内数据冗余和二义性问题

class A {
public:
	string name;
};
class B :public A {	//类B 里面有了份类A的成员变量
public:
	int age;
};
class C :public A {  //类C 里面有了份类A的成员变量
public:
	string sex;
};
class D :public B, public C {  //类D继承了B,C 不仅拥有了这两个基类特有的成员变量,
										//也有了基类自己继承的成员变量 这里有了两份类A的成员变量
public:
	int id;
};
int main()
{
	D d;
	d.name = "MM";
	d.age = 18;
	d.sex = "男";
	d.id = 666;
	return 0;
}

 

类B,C都继承了类A的name,所以D不知道继承B的name还是C中的name

这也就是引出了代码冗余和二义性的问题。

解决方式

加修饰限定,指定类域;

	d.C::name = "MM";

使用虚函数,在继承方式前加上virtual。(在腰间使用)

class B :virtual  public A {
public:
	int age;
};
class C :virtual public A {
public:
	string sex;
};

六.虚继承 

C++中的虚继承是一种特殊的继承方式,用于解决多重继承中的菱形继承问题

在菱形继承中,一个基类被两个或多个派生类继承,然后这些派生类又共同派生出一个最终类。如果不使用虚继承,最终类将包含多个基类的副本,这会导致数据冗余和不一致性问题。

虚继承通过在派生类中共享基类的单一实例来解决这个问题。这意味着所有通过虚继承得到的基类子对象在内存中只有一份拷贝,所有派生类共享这个拷贝。

不使用虚继承情况下(指定域)

 

使用虚继承

 

使用虚继承时,要注意以下几点:

虚继承通过关键字 virtual 来实现。

虚继承的基类成员在派生类中是共享的,而不是复制的。

虚继承可以避免多重继承中的二义性问题。

虚继承可能会稍微增加程序的复杂性,因为需要处理虚指针和虚基类表。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1812684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Excel最基本的常用函数

最基本最常用的函数&#xff0c;掌握了可以解决大部分问题。 (笔记模板由python脚本于2024年06月11日 19:05:56创建&#xff0c;本篇笔记适合熟悉excel的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣…

linux用户态操作GPIO首先需要export导出

在使用系统调用来实现 GPIO&#xff08;通用输入输出端口&#xff09;的输入输出操作时&#xff0c;同样需要先通过 export 属性文件来导出 GPIO&#xff0c;这是因为 Linux 内核对 GPIO 的管理和访问机制决定了这一点。 以下是具体原因&#xff1a; 内核设备模型&#xff1a…

加密解密工具免费分享12款,最新文件加密软件排行榜已出炉!

2024年&#xff0c;信息技术的快速发展让我们步入了一个数字化的时代&#xff0c;数据的交换和存储变得异常频繁和庞大。与此同时&#xff0c;数据泄露和盗窃的风险也日益增加&#xff0c;让人们对数据/文件/文件夹传输过程中的安全产生了更深刻的理解和关注。因此&#xff0c;…

vue-cli 快速入门

vue-cli &#xff08;目前向Vite发展&#xff09; 介绍&#xff1a;Vue-cli 是Vue官方提供一个脚手架&#xff0c;用于快速生成一个Vue的项目模板。 Vue-cli提供了如下功能&#xff1a; 统一的目录结构 本地调试 热部署 单元测试 集成打包上线 依赖环境&#xff1a;NodeJ…

成功解决IndexError: index 0 is out of bounds for axis 1 with size 0

成功解决IndexError: index 0 is out of bounds for axis 1 with size 0 &#x1f6e0;️ 成功解决IndexError: index 0 is out of bounds for axis 1 with size 0摘要引言正文内容&#xff08;详细介绍&#xff09;&#x1f914; 错误分析&#xff1a;为什么会发生IndexError&…

flask基础知识1

目录 1.介绍 2.体验一下 3.配置参数&#xff1a; 4.路由和URL 1.路由 2.动态路由&#xff1a; 自定义转换器&#xff1a; 3.使用自定义转换器 5.url_for函数 6.request参数 7.处理响应&#xff1a; 1.重定向&#xff1a; 2.返回json数据&#xff1a; 3.返回模板&…

接口测试 Mock 工具使用 - 弱网测试

在当今移动互联网的时代&#xff0c;网络的形态非常多变&#xff0c;不光有 2G, 3G&#xff0c;4G&#xff0c;不同的制式、不同的速率&#xff0c;让我们移动应用运行的场景更加丰富。而且移动产品使用场景非常多变&#xff0c;如近地铁&#xff0c;上公交&#xff0c;进电梯&…

C#中的Web抓取:避免被阻挡

C# 是一种广泛应用于企业级项目和应用程序的多功能编程语言。它源自 C 系列语言&#xff0c;具有高效和强大的特点&#xff0c;使其成为任何开发人员工具包中不可或缺的一部分。 由于其广泛的应用&#xff0c;C# 提供了大量的工具&#xff0c;使开发人员能够解决复杂的解决方案…

DeepSpeed Mixture-of-Quantization (MoQ)

属于QAT (Quantization-Aware Training)的一种&#xff0c;训练阶段用量化。 特点是&#xff1a; 1. 从16-bit INT开始训练&#xff0c;逐渐减1bit&#xff0c;训练一些steps就减1bit&#xff0c;直至减至8bit INT&#xff1b; 2. &#xff08;可选&#xff0c;不一定非用&a…

如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?

前言 昨天分享了在 .NET Core Console 项目中应用 NLog 写日志的详细例子&#xff0c;有几位小伙伴私信说 ASP.NET Core Web Api 项目中无法使用&#xff0c;其实在 ASP.NET Core Web Api 项目中应用 NLog 写日志&#xff0c;跟 .NET Core Console 项目是有些不一样的&#xf…

css font-family

知乎的font-family的设置理解 -apple-system, BlinkMacSystemFont 这两个值是为了确保在macOS和iOS系统上能够使用系统默认字体进行文本渲染。-apple-system特别为Safari浏览器设计&#xff0c;而BlinkMacSystemFont则主要针对基于Chromium的浏览器&#xff08;如Chrome&#…

OS进程取样器OS Process Sampler执行CMD/Shell命令

Apache JMeter - Users Manual: Component Reference 1.背景 项目上最近需要测试一种很少用到的DICOM协议,但是网上资料很少,基本上可以总结为三种方案: 直接发送TCP 16进制数据包,但是参数化数据准备难度大通过开发封装jar包发送,需要开发组提供通过发送cmd命令给前置机…

【精选研报】#2形态识别,均线的收敛与发散

下载地址https://pan.baidu.com/s/1L1wPR7kXCb-ZbrgwFKcIvg?pwd8888

Qt线程间的同步(QMutex、QReadWriteLock、QSemaphone、QWaitCondition、信号槽)

同步方法&#xff1a; 1、互斥锁QMutex、函数互斥锁QMutexLocker。 2、读写锁QReadWriteLock、读锁QReadLockerr、写锁QWriteLocker。 3、信号量QSemaphore&#xff08;QSystemSemaphore支持进程间的同步&#xff09;。 4、条件变量QWaitConditon。 5、信号槽&#xff08;第五个…

Jmeter07:函数

1 Jmeter组件&#xff1a;函数 1.1 是什么&#xff1f; 是程序中的封装单元&#xff08;最小的&#xff09;&#xff0c;封装一些功能实现 1.2 为什么&#xff1f; 优点1&#xff1a;易读 易维护 优点2&#xff1a;实现功能复用 1.3 怎么用&#xff1f; 流程&#xff1a; 1&…

[ADS信号完整性分析]深入理解IBIS AMI模型设计:从基础到实践

在高速数字设计领域&#xff0c;信号完整性&#xff08;SI&#xff09;分析对于确保系统性能至关重要。IBIS AMI&#xff08;Algorithmic Model Interface&#xff09;模型作为一种强大的工具&#xff0c;能够帮助设计师在系统层面上评估和优化SERDES&#xff08;串行器/解串器…

【STM32】基于I2C协议的OLED显示(利用U82G库)

【STM32】基于I2C协议的OLED显示(利用U82G库) 文章目录 【STM32】基于I2C协议的OLED显示(利用U82G库)一、实验背景二、U8g2介绍&#xff08;一&#xff09;获取&#xff08;二&#xff09;简介 三、实践&#xff08;一&#xff09;CubexMX配置&#xff08;二&#xff09;U8g2配…

【wiki知识库】06.文档管理接口的实现--SpringBoot后端部分

目录 一、&#x1f525;今日目标 二、&#x1f388;SpringBoot部分类的添加 1.调用MybatisGenerator 2.添加DocSaveParam 3.添加DocQueryVo 三、&#x1f686;后端新增接口 3.1添加DocController 3.1.1 /all/{ebokId} 3.1.2 /doc/save 3.1.3 /doc/delete/{idStr} …

Pixi.js学习 (五)动画效果与变量逻辑控制

目录 前言 一、动画效果 1.1 帧频 1.2 帧频函数 二、变量逻辑控制 2.1 定义变量的语法 2.2 使用变量控制逻辑 2.3 使用变量控制追加效果 三、实战 例题一&#xff1a;完成天天酷跑 例题一代码&#xff1a; 总结 前言 为了提高作者的代码编辑水品&#xff0c;作者在使用博客的时…

day35|1005.K次取反后最大化的数组和 134. 加油站135. 分发糖果

文章目录 python语法记录 sort格式 1005.K次取反后最大化的数组和思路方法一方法二 按照绝对值排序 教程&#x1f388;✨ 背住 按照绝对值进行降序排序的语法是&#xff1a; 134. 加油站思路方法一 教程解法方法二 暴力求解 135. 分发糖果思路方法一 总结 python语法记录 sort …