我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++ 继承的范例讲解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++ 继承的范例讲解

1.继承的概念

继承,是面向对象的三大特性之一。继承可以理解成是类级别的一个复用,它允许我们在原有类的基础上进行扩展,增加新的功能。

当创建一个类时,我们可以继承一个已有类的成员和方法,并且在原有的基础上进行提升,这个被继承的类叫做基类,而这个继承后新建的类叫做派生类。

用法如下:

class [派生类名] : [继承类型] [基类名]

例如:

class Person
{
public:
	string _name;
	int _age;
};
class Student : public Person
{
protected:
	string _stuNum;
};

这里的派生类Student就复用了Person的方法和成员,并在此基础上扩展补充。

2.继承方式

继承的方式和类的访问限定符一样,分为public(公有继承),private(私有继承), protected(保护继承)三种。

不同的继承方式,在派生类中继承下来的基类成员的访问权限也不一样。

基类的其他成员在子类的访问方式 = Min(成员在基类的访问限定符,继承方式)

备注:

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

2.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

3.基类与派生类的赋值转换

派生类可以赋值给基类的对象、指针或者引用,这样的赋值也叫做对象切割。

例如上面的Person类和Student类

这种赋值只能是派生类赋给基类(但需要割掉多出来的成员例如_ stuID),而基类对象不能赋给派生类。

基类的指针可以强制类型转换赋值给派生类的指针, 如:

int main()
{
	Person p1;
	Student s1;
	Person* hPtr1 = &s1;//指向派生类对象
	Person* hPtr2 = &p1;//指向基类对象
	Student* pPtr = (Student*)hPtr1;//没问题
	Student* pPtr = (Student*)hPtr2;//有时候没有问题,但是会存在越界风险
	return 0;
}

小结:

1.派生类可以赋值给基类的对象、指针或者引用

2.基类对象不能赋值给派生类对象

3.基类的指针可以通过强制类型转换赋值给派生类的指针。**?但是必须是基类的指针是指向派生类对象时才是安全的,否则会存在越界的风险。**这里基类如果是多态类型,可以使用RTT的dynamic_cast来进行识别后进行安全转换。

4.作用域与隐藏

?隐藏:隐藏,也叫做重定义,当基类和派生类中出现重名的成员时,派生类就会将基类的同名成员给隐藏起来,然后使用自己的。(但是隐藏并不意味着就无法访问,可以通过指明基类作用域来显式访问隐藏成员。)

class Person
{
public:
	void f(int age)
	{
		cout << "姓名" << _name << endl;
		cout << "年龄" << _age << endl;
	}
protected:
	string _name;
	int _age;
};
class Student : public Person
{
public:
	void f()
	{
		Person::f(32);//需显式调用f函数
		cout << "学号" << _stuNum << endl;
	}
private:
	string _stuNum;
};

例如这里的f( )就构成了隐藏

同时,这里还有个需要注意的问题,在基类与派生类中,同名的方法并不能构成重载,因为处于不同的作用域中。而只要满足方法名相同,就会构成隐藏。

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

在每一个类中,都会有6个默认的成员函数,这些函数即使我们自己不去实现,编译器也会帮我们实现。

?这里有两点需要注意(笔试题常考):

1.构造函数,拷贝构造,operator=三种情况,都要调用父类对应的构造函数/拷贝构造/operator=进行对父类的成员变量的初始化,并且倘若父类没有默认的构造函数的时候(比如父类写了带参的构造函数),我们就要显式调用(Person(参数…),Person::operator=(参数…))

?构造函数显示调用

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函 数,则必须在派生类构造函数的初始化列表阶段显示调用。

Student()
    	:People()
	{
		cout << "Student()" << endl;
	}

?拷贝构造显示调用

建议拷贝构造都用显示调用,不然免不了出现子类拷贝构造当中调用了父类的构造函数的情况(因为拷贝构造也是构造,在初始化列表处,对于子类而言,父类相当于一个自定义类型对象,子类会调用父类的构造函数对父类的资源进行初始化。)

Student(const Student& s)
		:People(s)
	{
		cout << "Student(const Student& s)" << endl;
	}

?派生类的operator=必须要调用基类的operator=完成基类的复制。

Student& operator=(const Student& s)
	{
		cout << "Student& operator = (const Student& s)" << endl;
		if (this != &s)
		{
			Person:: operator=(s);
		}
	}

?析构函数

由于编译器会将析构函数的名字处理成destructor,因此派生类和基类的析构函数会构成隐藏关系,故若要派生类要调用基类的析构函数,那么需要显式调用,但是编译器会默认在派生类的析构函数调用结束后调用基类的析构函数,这样就析构两次了。

	~Person()
	{
		cout << "~Person()" << endl;
	}
	~Student()
	{
		Person:: ~Person();
		cout << "~Student()" << endl;
	}

在派生类中,基类的析构函数会被隐藏,虽然它们这里的名字不同,但是为了实现多态, 它们都会被编译器重命名为destructor。在调用子类的构造函数时,我们是先调用父类的构造函数,后对子类的成员进行构造。由先构造后析构的顺序,所以我们是在析构函数当中析构子类的资源,析构函数调用完后编译器自动帮我们调用父类的析构函数。

6.友元与静态成员

?1.友元

友元关系是不会继承的,如果子类要使用父类的友元,则子类自己也要将其定义为友元。

?2.静态成员

基类定义了static静态成员,无论继承了多少次,派生了多少子类,静态成员在这整个继承体系中有且只有一个。静态成员不再单独属于某一个类亦或者是某一个对象,而是属于这一整个继承体系。

7.菱形继承与虚继承

首先简单介绍下单继承、多继承的概念

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

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

菱形继承:菱形继承是多继承的一种特殊情况,下面简单举例介绍下菱形继承及其带来的二义性问题:

class Human
{
public:
	int _age;
};
class Student : public Human
{
public:
	int _stuNum;
};
class Teacher : public Human
{
public:
	int _teaNum;
};
class Assistant : public Teacher, public Student
{
};

哦豁!!!菱形继承这个关系感受到了吧!!!

?直接讲下这个菱形继承带来的二义性问题:

按照道理来说,各个类的大小应该是这样的。human类4个字节,teacher和student都是8个字节,而assistant是12个字节,但是实际上assistant却是16字节。

?为什么assistant会有16字节?

这就是菱形继承的数据冗余和二义性问题的体现。

这里的teacher和student都从human中继承了相同的成员 _age。但是assistant再从teacher和student继承时,就分别把这两个 _age都给继承了过来,导致这里有了两个一样的成员。

在这样的情况下,后续想给 _age赋值,也会被编译器提示指示不明确,报错。

?菱形继承的二义性是很致命的问题,如何解决呢?

虚继承,在腰部的类继承时添加virtual关键字。

class Student : virtual public Human
{
public:
	int _stuNum;
};
class Teacher : virtual public Human
{
public:
	int _teaNum;
};
class Assistant : public Teacher, public Student
{
};

这次,二义性问题解决了,teacher和student都是12个字节,而assistant是20个字节。

想知道为什么?点这里:从内存角度看待虚继承

?简单小结一下

1.可以看到没有虚继承的情况下,Assitant中的成员连续排列出现了Teacher和Student中的_age是两个不同的值,但实际上一个人不会有两个年龄,所以这就出现了数据冗余。

2.这里多出来的8个字节,其实是两个虚基表指针。因为这里Human中的 _age是 teacher和 student共有的,所以为了能够方便处理,在内存中分布的时候,就会把这个共有成员 _age放到对象组成的最末尾的位置。然后通过了Teacher和Student的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的Human。由此可见,使用了虚拟继承后,就可以解决菱形继承导致的问题。

?为什么Teacher、Student需要去找属于自己的 _age?

基类与派生类的赋值转换时,需要进行切片。

int main()
{
	Assistant a;
	Teacher t = a; 
	Student s = a;
	return 0;
}

当把对象a赋值给t和s的时候,因为他们互相没有对方的 _stuNum和 _teaNum,所以他们需要进行对象的切割,但是又因为 _age存放在对象的最尾部,所以只有知道了自己的偏移量,才能够成功的在切割了没有的元素时,还能找到自己的 _age。

8.继承和组合

?继承是一种复用的方式,但不是唯一方式!

1.public继承是一种is-a的关系,就是基类是一个大类,而派生类则是这个大类中细分出来的一个子类,但是他们本质上其实是一种东西。

2.组合是一种has-a的关系,就是一种包含关系,比如对象a是对象b的成员,那么他们的关系就是对象b的组成中包含了对象a,对象a是对象b中的一部分,对象b包含对象a。

3.继承方式的复用常称之为白箱复用,在继承方式中,基类的内部细节对子类可见,这一定程度上破坏了基类的封装,伴随着基类的改变,对派生类的改变很大。并且两者依赖关系强,耦合度大。

4.对象组合式继承之外的复用选择,对象组合要求被组合对象提供良好的接口定义。这种复用称之为黑箱复用,对象的内部实现细节是不可见的。耦合度低。

实际工程中能用继承和组合就用组合,组合的耦合度低,代码的维护性好,但是继承在有些关系就适合用继承就用继承,并且要实现多态就一定要用继承。

到此这篇关于C++ 继承的范例讲解的文章就介绍到这了,更多相关C++ 继承内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++ 继承的范例讲解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JavaScript类的继承全面示例讲解

在ES5中,类的继承可以有多种方式,然而过多的选择有时反而会成为障碍,ES6统了类继承的写法,避免开发者在不同写法的细节之中过多纠缠,但在介绍新方法之前,还是有必要先回顾下ES5中类的继承方式
2022-11-13

python 面向对象之继承实例讲解

面向对象编程语言具有封装、继承、多态三个基本特征,本文就继承举例详谈比如说学校主要有3大角色:学校,讲师,学员学校可以注册学员,统计有多少学员老师负责讲课学生听课,提问,学习,交学费先定义4个类:classSchool(object):#学
2023-01-30

C++继承方式的示例分析

这篇文章给大家介绍C++继承方式的示例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。C++支持多种程序设计风格,其中就包括对面向对象设计的支持。我们今天在这里将会为大家详细介绍一下各种C++继承方式的具体应用方法,
2023-06-17

C++多继承(多重继承)的实现

多继承容易让代码逻辑复杂、思路混乱,本文主要介绍了C++多继承(多重继承)的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-16

举例讲解Python面向对象编程中类的继承

python创建一个类很简单只需要定义它就可以了.class Cat:pass就像这样就可以了,通过创建子类我们可以继承他的父类(超类)的方法。这里重新写一下catclass Cat:name = 'cat'class A(Cat):pas
2022-06-04

C++类的继承怎么理解

这篇文章主要讲解了“C++类的继承怎么理解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++类的继承怎么理解”吧!而基类中的公有成员在public派生时,不仅可以由派生类对象成员访问,也可
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录