浅谈C++|类的继承篇

2023-09-16 22:46:12

 

引子: 

继承是面向对象三大特性之一、有些类与类之间存在特殊的关系,例如下图中:



我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码。

一.继承基本语法 

语法:class 子类:继承方式 父类 

优点:减少重复代码

子类也叫派生类,父类也叫基类

代码: 

#include <iostream>
using namespace std;
class person {   //定义person基类
public:
	int age;
	int height;
	int weight;
	string name;

	void show() {
		cout << "age=" << age << endl;
		cout << "height=" << height << endl;
		cout << "weight=" << weight << endl;
		cout << "name=" << name << endl;
	}
};

class women :public person {
public:
	string xingbie;
	void shou_x() {
		cout << "xingbie=" << xingbie<<endl;
	}
};

void fun() {
	women p;
	p.age = 18;
	p.height = 180;
	p.name = "tom";
	p.weight = 160;
	p.xingbie = "女";
	p.show();
	p.shou_x();
}
int main() {
	fun();
	return 0;
}

 派生类中的成员,包含两大部分:
1.类是从基类继承过来的,-类是自己增加的成员。
2.从基类继承过过来的表现其共性,而新增的成员体现了其个性。

 二.继承方式

 继承方式:

public:     公有继承

protected:保护继承

private:    私有继承

代码:

#include <iostream>
using namespace std;
class father {
public:
	int A;
protected:
	int B;
private:
	int C;
};

class son1 :public father {    //公有继承
public:
	void fun() {
		cout << "A=" << A << endl;  //公有变为公有
		cout << "B=" << B << endl;  //保护变为保护
		//cout << "C=" << C << endl;//私有不可访问
	}
};

class son2 :protected father {    //保护继承
public:
	void fun() {
		cout << "A=" << A << endl;  //公有变为保护
		cout << "B=" << B << endl;  //保护变为保护
		//cout << "C=" << C << endl;//私有不可访问
	}
};


class son3 :private father {    //私有继承
public:
	void fun() {
		cout << "A=" << A << endl;  //公有变为私有
		cout << "B=" << B << endl;  //保护变为私有
		//cout << "C=" << C << endl;//私有不可访问
	}
};


void fun() {
	son1 p1;
	son2 p2;
	son3 p3;
	p1.A = 10;
	p1.fun();
}
int main() {
	fun();
	return 0;
}

 私有成员全不见,公不变,保全保,私全私。

 三.继承中的对象模型

继承中,基类私有变量虽然访问不到,但是已经被继承,只是被隐藏了。

 代码:

#include <iostream>
using namespace std;
class father {
public:
	int A;
protected:
	int B;
private:
	int C;
};
class son :private father {
public:
	int age;
};
int main() {
	cout << sizeof(son) << endl;
	return 0;
}

四.继承构造和析构顺序

子类继承父类时,会先创建一个父类。析构的顺序和构造顺序正好相反。

 代码:

#include <iostream>
using namespace std;
class father {
public:
	father() {
		cout << "father的构造哈数" << endl;
	}
	~father() {
		cout << "father的析构函数" << endl;
	}
};
class son :public father {
public:
	son() {
		cout << "son的构造函数" << endl;
	}
	~son() {
		cout << "son的析构函数" << endl;
	}
};
void fun() {
	son a;
}
int main() {
	fun();
	return 0;
}

五.继承同名成员处理方式

当基类中的成员变量以及成员函数,和派生类的成员变量和函数重名时,基类默认调用派生类的成员函数和成员变量。要想调用基类的成员函数和成员变量,需要加上基类的作用域;

代码:

#include <iostream>
using namespace std;
class father {
public:
	int A;
	father() {
		A = 999;
	}
	void fun() {
		cout << "father的fun调用" << endl;
	}
	void fun(int a) {
		cout << "father的fun调用" << ' ' << a << endl;
	}
};
class son :public father{
public:
	int A;
	son() {
		A = 99;
	}
	void fun() {
		cout << "son的fun调用" << endl;
	}
	void fun(int a) {
		cout << "father的fun调用" << ' ' << a << endl;
	}
};
void fun() {
	son p;
	cout << "son" << ' ' << p.A << endl;
	cout << "father" << ' ' << p.father::A << endl;
	p.fun();
	p.fun(11);
	p.father::fun();
	p.father::fun(90);
}
int main() {
	fun();
	return 0;
}

 如果派生类中出现和基类重名的成员函数,那么派生类会隐藏基类全部重名的成员函数。需要注意的是,本来可以按照函数重载区分,却被派生类隐藏的情况:

代码: 

#include <iostream>
using namespace std;
class father {
public:
	int A;
	father() {
		A = 999;
	}
	
	void fun() {
		cout << "father的fun调用" << endl;
	}
	void fun(int a,int b) {
		cout << "father的fun调用" << ' ' << a << endl;
	}
};
class son :public father{
public:
	int A;
	son() {
		A = 99;
	}
	void fun() {
		cout << "son的fun调用" << endl;
	}
};
void fun() {
	son p;
	cout << "son" << ' ' << p.A << endl;
	cout << "father" << ' ' << p.father::A << endl;
	p.fun();
	p.fun();
	p.father::fun();
	//p.fun(90,89);//报错
	p.father::fun(89, 123);
}
int main() {
	fun();
	return 0;
}

六.继承同名static成员处理方式

 当重名的成员变量和函数是static时,需要注意通过类名来调用的方式

代码:

#include <iostream>
using namespace std;
class father {
public:
	static int A;
	static void fun() {
		cout << "father的fun调用" << endl;
	}
	void fun(int a) {
		cout << "father的fun调用" << a<<' ' << endl;
	}
};
int father::A=10;   //static成员变量,类内声明,类外定义
class son :public father {
public:
	static int A;
	static void fun() {
		cout << "son的fun调用" << endl;
	}
};
int son::A = 20;
void fun() {
	son p;
	//1.利用对象调用成员变量和函数
	cout << p.A << endl;
	cout << p.father::A << endl;
	p.fun();
	p.father::fun();

	cout << "**************************" << endl;
	//2.利用类名调用成员变量和函数
	cout << son::A << endl;
	cout << father::A << endl;
	cout << son::father::A << endl;   

	son::fun();
	father::fun();
	son::father::fun();    
	//father::fun(10);  //报错,此方法只能调用static类型


}
int main() {
	fun();
	return 0;
}

七.多继承语法

C++中允许一个类继承多个类


语法: class子类∶继承方式父类1,继承方式父类2...


多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承

 代码:

#include <iostream>
using namespace std;
class base1 {
public:
	int A;
	base1() {
		A = 10;
		cout << "base1构造函数调用" << endl;
	}
};


class base2 {
public:
	int A;
	base2() {
		A = 20;
		cout << "base2构造函数调用" << endl;
	}
};

class son :public base1, public base2 {
public:
	int B;
	int C;
	son() {
		B = 100;
		C = 200;
		cout << "son构造函数调用" << endl;
	}
};

void fun() {
	son b;
	cout << b.base1::A << endl;
	cout << b.base2::A << endl;
	cout << b.B << endl;
	cout << b.C << endl;
}
int main() {
	fun();
	return 0;
}

 

 注意构造函数的调用顺序,父亲1先调用,父亲2再调用,最后son调用

总结:多继承中如果父类中出现了同名情况,子类使用时候要加作用域。

八.菱形继承

菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承

1.羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。
2.羊驼将动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要—份就可以。

 代码:

#include <iostream>
using namespace std;
class dongwu {
public:
	int A;
};
class yang :public dongwu {};
class tuo:public dongwu {};
class son :public tuo, public yang {};
void fun() {
	son p;
	p.yang::A = 100;
	p.tuo::A = 200;
	//当菱形继承,两个父亲拥有相同名的成员时,要加作用域加以区分;
	cout << p.yang::A << endl;
	cout << p.tuo::A << endl;
}
int main() {
	fun();
	return 0;
}

此时的继承情况是,这样的。 

 利用虚继承,可以解决菱形继承的问题(两份相同的数据,浪费内存)

代码:

#include <iostream>
using namespace std;
class dongwu {
public:
	int A;
};
//virtual虚继承,dongwu称为虚基类
class yang :virtual public dongwu {};
class tuo:virtual public dongwu {};
class son :public tuo, public yang {};
void fun() {
	son p;
	yang p1;
	tuo p2;
	p.yang::A = 100;
	p.tuo::A = 200;
	p1.A = 90;
	p2.A = 190;
	//当菱形继承,两个父亲拥有相同名的成员时,要加作用域加以区分;
	cout << p.yang::A << endl;
	cout << p.tuo::A << endl;
	cout << p1.A << endl;
	cout << p2.A << endl;

}
int main() {
	fun();
	return 0;
}

 继承时只继承一份,此时son继承的是两个虚基类表的指针,虚基类表中存储的是到成员的偏移量。

总结:
·菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义·利用虚继承可以解决菱形继承问题

更多推荐

基于Java+SpringBoot+Vue前后端分离网络海鲜市场系统设计和实现

博主介绍:✌全网粉丝30W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌🍅文末获取源码联系🍅👇🏻精彩专栏推荐订阅👇🏻不然下次找不到哟2022-2024年最全的计算机软件毕业设计选题

强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示

【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现专栏详细介绍:【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现对于深度强化学习这块规划为:基础单智能算法教学(g

Qt Quick Layouts Overview

Qt快速布局概述#【中秋征文】程序人生,中秋共享#Qt快速布局是用于在用户界面中排列项目的项目。由于Qt快速布局还可以调整其项目的大小,因此它们非常适合可调整大小的用户界面。开始可以使用文件中的以下导入语句将QML类型导入到应用程序中。.qmlimportQtQuick.Layouts1.11主要特点一些主要功能是:可

LeetCode 362 期周赛

8029.与车相交的点题目:给你一个下标从0开始的二维整数数组nums表示汽车停放在数轴上的坐标。对于任意下标i,nums[i]=[starti,endi],其中starti是第i辆车的起点,endi是第i辆车的终点。返回数轴上被车任意部分覆盖的整数点的数目。思路:模拟代码classSolution{public:in

大模型如何可信?字节跳动研究的最新《可信赖的大型语言模型》综述,提出评估 LLMs 可信度时需要考虑的七大维度

文章目录一、前言二、主要内容三、总结🍉CSDN叶庭云:https://yetingyun.blog.csdn.net/一、前言论文地址:TrustworthyLLMs:aSurveyandGuidelineforEvaluatingLargeLanguageModels’Alignment在将大型语言模型(LLMs)

中外人工智能专家共话大语言模型与 AI 创新

文章目录一、前言二、主要内容三、总结🍉CSDN叶庭云:https://yetingyun.blog.csdn.net/一、前言智源社区活动,中外人工智能专家共话大语言模型与AI创新。对谈书目:《大模型时代》,龙志勇、黄雯著,中译出版社2023年5月出版。《为什么伟大不能被计划》,[美]肯尼斯·斯坦利、[美]乔尔·雷曼

【CS324】LLM(大模型的能力、数据、架构、分布式训练、微调等)

note本文是斯坦福大学CS324课程的学习笔记,同时参考了一些LLM相关资料文章目录note一、引言二、大模型的能力1.从语言模型到任务模型2.任务评估三、大模型的有害性四、大模型的数据五、law问题六、模型架构篇1.tokenization2.模型架构(1)onlyencoder(2)onlydecoder(3)e

猫头虎博主第5️⃣期赠书活动:《Java官方编程手册(第12版·Java 17)套装上下册》

🌷🍁博主猫头虎(🐅🐾)带您GotoNewWorld✨🍁🦄博客首页——🐅🐾猫头虎的博客🎐🐳《面试题大全专栏》🦕文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》🐾学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》🐅学会Gol

【Java 基础篇】自如应对文本数据:Java缓冲字符流详解

Java提供了许多用于读写文本文件的类,其中缓冲字符流(BufferedCharacterStream)是一种非常常用且高效的方式。本篇博客将详细介绍Java缓冲字符流的使用,包括什么是缓冲字符流、为什么需要它们、如何创建和使用缓冲字符流、以及一些常见的使用场景和注意事项。什么是缓冲字符流?在了解缓冲字符流之前,我们需

Java下部笔记

目录一.双列集合1.Map2.Map的遍历方式3.可变参数4.Collection中的默认方法5.不可变集合(map不会)二.Stream流1.获取stream流2.中间方法3.stream流的收集操作4.方法引用1.引用静态方法2.引用成员方法3.引用构造方法4.使用类名引用成员方法5.引用数组的构造方法三.异常四.

【Java 基础篇】优雅处理文本数据:Java字符流详解

当涉及字符流时,Java提供了一组类来处理字符数据的输入和输出。字符流比字节流更适合处理文本文件,因为它们可以正确处理字符编码,而不仅仅是字节。在本篇博客中,我们将详细介绍Java字符流的各个方面,包括基本的字符输入输出,字符编码,字符流的使用注意事项以及一些高级话题。1.什么是字符流?字符流是用于处理字符数据的Jav

热文推荐