广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++继承与菱形继承详细介绍
  • 790
分享到

C++继承与菱形继承详细介绍

2024-04-02 19:04:59 790人浏览 薄情痞子
摘要

目录继承的概念和定义基类和派生类之间的赋值继承中的作用域派生类的默认成员函数菱形继承继承和组合的区分与联系其余注意事项继承的概念和定义 继承机制是面向对象程序设计的一种实现代码复用的

继承的概念和定义

继承机制是面向对象程序设计的一种实现代码复用的重要手段,它允许程序员在保持原有类特性的基础上进行拓展,增加其他的功能,在此基础上也就产生了一个新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,是类设计层次的复用。

//以下代码就是采用了继承机制的一个场景
class person
{
protected:
	char _name[28];
	int _age;
	char _id[30];
};
//继承是代码复用的一种重要手段
class student :public person
{
protected:
	char _academy[50]; //学院
};

继承的格式

在前面的例子中,person是基类,student是派生类,继承方式是public. 这是很容易记忆的,person是基础的类,student是在person这个类的基础之上派生出来的。这就非常地像父子关系,所以基类又可以称为父类,派生类又可为子类。子类的后面紧跟着:,是:后面这个类派生出来的。

继承关系和访问限定符

继承的几种方式和访问限定符是相似的。

三种继承方式:public继承、protected继承、private继承。

三种访问限定符:public访问、protected访问、private访问。

基类类成员的访问权限和派生类继承基类的继承方式, 关系到了基类被继承下来的类成员在派生类中的情况。ps:这句话起始很好理解地,就是这句话写起来就变得绕口和复杂了,哈哈哈?.

基类成员/继承方式public继承protected继承private继承
public成员在派生类中为public成员在派生类中为protected成员在派生类中为private成员
protected成员在派生类中为protected成员在派生类中为protected成员在派生类中为private成员
private成员在派生类中不可见在派生类中不可见在派生类中不可见

这里的不可见指的是:基类中的private成员也是被继承下来了的,只是在语法上,在派生类的类里和类外都不能够访问。

记住这个特殊的点,那么其他的就可理解为“权限问题”,这里“权限只能缩小,不能放大”。例如,基类的public成员以private继承方式继承下来,为“权限小的那个”,也就是继承下来后在派生类中是private成员。

class person
{
protected:
	char _name[28];
	char _id[30];
private:
	int _age;
};
class teacher :public person
{
public:
	teacher()
		:_age(0) //基类的private成员在派生类里不能访问
	{
	}
protected:
	char _jodid[20]; //工号
};
int main(void)
{
	teacher t1;
	t1._age; //基类的private成员在类外不能访问
	return 0;
}

基类和派生类之间的赋值

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

就像上面这样,取基类需要被赋值的值过去即可。

派生类赋值给基类的对象、基类的指针、基类的引用。在派生类中取基类需要的,就像把派生类给切割了一样、所以这里有一个形象的称呼:切割/切片

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _id; // 学号
};
int main(void)
{
	//可以将派生类赋值给基类的对象、指针、引用
	Person p;
	Student s;
	p = s;
	Person* Pptr = &s;
	Person& Refp = s;
	//注意不能将将基类对象给派生类对象
	//s = p;
	//允许将基类指针赋值给派生类指针,但是需要强制转换
	Student* sPtr = (Student*)Pptr;
	return 0;
}

【注意】

1、不允许基类对象赋值给派生类对象

2、允许基类指针赋值给派生类指针, 但是需要强制转化。这种转化虽然可以,但是会存在越界访问的问题。

继承中的作用域

基类和派生类都有独立的作用域。继承下来的基类成员在一个作用域,派生类的成员在另一作用域。

//以下代码的运行结果是什么?
class Person
{
protected:
	string _name = "杨XX"; // 姓名
	int _num = 12138; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout <<_num << endl;
	}
protected:
	int _num = 52622; // 学号
};
void Test()
{
	Student s1;
	s1.Print();
};

基类中有一个_num 给了缺省值“12138”, 派生类中也有一个_name,给了缺省值“52622”,那么在派生类里直接使用_name,使用的具体是哪一个类里的?

使用的是派生类Student里的。

总结:基类和派生类中如果有同名成员,派生类将屏蔽基类对同名成员的直接访问,这种情况称为隐藏 , 或者称为重定义。

如果想要访问,则使用基类::基类成员显示的访问。

class Person
{
protected:
	string _name = "杨XX"; // 姓名
	int _num = 12138; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << "身份证号:" << Person::_num << endl;
		cout << "学号:" << _num << endl;
	}
protected:
	int _num = 52622; // 学号
};
void Test()
{
	Student s1;
	s1.Print();
};
int main(void)
{
	Test();
	return 0;
}

运行结果

我们已经了解了什么是隐藏。那么来看一下下面这些代码。

//以下的两个函数构成隐藏还是重载?
class A
{
public:
	void func()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void func(int num)
	{
		cout << "func(int num)" << endl;
	}
};
void Test()
{
	B b;
	b.func(10);
}

函数重载要求在同一作用域,而被继承下来的基类成员和派生类成员在不同的作用域,所以构成的是隐藏。

```cpp
//以下代码的运行结果是什么?
class A
{
public:
	void func()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void func(int num)
	{
		cout << "func(int num)" << endl;
	}
};
void Test()
{
	B b;
	b.func();
}

因为func()函数隐藏了,在派生类的作用域内没有func()函数,所以会出现编译报错。

派生类的默认成员函数

类有8个默认成员函数,这里只说重点的四个默认成员函数:构造函数、析构函数、拷贝构造函数、赋值重载函数

如果我们不写派生类的构造函数和析构函数,编译器会做如下的事情:

1、基类被继承下来的部分会调用基类的默认构造函数和析构函数

2、派生类自己也会生成默认构造和析构函数,派生类自己的和普通类的处理一样

如果我们不写派生类的赋值构造函数和拷贝构造函数,编译器会做如下的事情

3、基类被继承下来的部分会调用基类的默认拷贝构造函数和赋值构造函数。

4、派生类自己也会生成默认赋值拷贝构造函数和赋值函数,和普通类的处理一样。

什么情况下需要自己写?

1、父类没有合适的默认构造函数,需要自己显示地写

2、如果子类有资源需要释放,就需要自己显示地写析构函数

3、如果子类存在浅拷贝的问题,就需要自己实现拷贝构造和赋值函数解决浅拷贝的问题。

如果需要自己写派生类的这几个重点成员函数,那么该如何写?

//如果需要自己实现派生类的几个四个重点默认成员函数,需要如何实现?该注意什么?
class Person
{
public:
	Person(const char* name)
		:_name(name)
	{
		cout << "Person(const char* name)" << endl; //方便查看它什么被调用了
	}
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		//首先排除自己给自己赋值
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl; 
	}
protected:
	string _name; //姓名
};
class Student : public Person
{
protected:
	int _id; //学号
	int* _ptr = new int[10]; //给一个需要自己实现默认成员函数场景用以举例
};

1、实现派生类的构造函数:需要调用基类的构造函数初始化被继承下来的基类部分的成员。如果基类没有合适的默认构造函数,就需要在实现派生类构造函数的初始化列表阶段显示调用。

2、实现派生类的析构函数:派生类的析构函数会在被调用完成后自动调用基类的析构函数清理被继承下来的基类成员。这样可以保证派生类自己的成员的清理先于被继承下来的基类成员。ps:析构函数名字会被统一处理成destructor(),所以被继承下来的基类的析构函数和派生类的析构函数构成隐藏。

3、实现派生类的拷贝构造函数:需要调用基类的拷贝构造函数完成被继承下来的基类成员的拷贝初始化。

4、实现派生类的operator=:需要调用基类的operator=完成被继承下来的基类成员的赋值。

5、派生类对象初始化先调用基类构造再调用派生类构造。

class Student : public Person
{
public:
	Student(const char* name, int id)
		: Person(name)
		, _id(id)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		: Person(s)
		, _id(s._id)
	{
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);
			_id = s._id;
		}
		return *this;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _id; //学号
};

菱形继承

继承可分为单继承和多继承。

单继承:一个派生类只有一个直接基类

多继承:一个派生类有两个或两个以上的直接基类。

而多继承中又存在着一种特殊的继承关系,菱形继承

它们之间的继承关系逻辑上就类似一个菱形,所以称为菱形继承。菱形继承相对于其他继承关系是复杂的。

B中有一份A的成员,C中也有一份A的成员,D将B和C都继承了,那么D中被继承下来的A的成员不就有两份了吗?不难看出,菱形继承有数据冗余和二义性的问题。

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
public:
	int _num; //学号
};
class Teacher : public Person
{
public:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
public:
	string _majorCourse; // 主修课程
};
int main()
{
	// 二义性、数据冗余
	Assistant a;
	a._id = 1;
	a._num = 2;
	// 这样会有二义性无法明确知道访问的是哪一个
	a._name = "peter";
	return 0;
}

上面的继承关系如下:

此时Assitant中有两份_name.存在数据冗余和二义性的问题。

二义性的问题是比较好解决的,使用::指定就可以了,但是并不能解决数据冗余的问题。

int main()
{
	// 二义性、数据冗余
	Assistant a;
	a._id = 1;
	a._num = 2;
	a.Student::_name = "小张";
	a.Teacher::_name = "张老师";
	return 0;
}

虚拟继承可以解决继承的数据冗余和二义性的问题。如上面所画的逻辑继承关系。在开始可能产生数据冗余和二义性的地方使用虚拟继承,即可解决,但是在其他地方不要去使用虚拟继承。

虚拟继承格式

虚拟继承解决数据冗余和二义性的原理

为了更好地研究,在这里给出一个比较简单的菱形继承体系

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;
}

B和C中都有一份A的数据可以看出数据的冗余。

现在增加虚拟继承机制,解决数据冗余和二义性。

class A {
public:
	int _a;
};
class B : virtual public A {
public:
	int _b;
};
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;
}

再次调式调用内存窗口,会发现

和没有采用虚拟继承的内存窗口有较大的变化。

B中的地址0x00677bdc里有什么?C中的地址0x00677be4里有什么?

从内存窗口可看出,菱形虚拟继承,内存中只在对象组成的最高处地址保存了一份A,A是B、C公共的。而B和C里分别保存了一个指针,该指针指向一张表。这张表称为虚基表,而指向虚基表的指针称虚基指针。虚基表中保存的值,是到A地址的偏移量,通过这个偏移量就能够找到A了。

继承和组合的区分与联系

在没有学习继承之前,我们其实频繁地使用组合。

class head
{
private:
	int _eye;
	int _ear;
	int _mouth;
};
class hand
{
private:
	int _arm;
	int _fingers;
};
class Person
{
	//组合
	//一个人由手、头等组合
	hand _a;
	head _b;
};
  • 继承是一种is-a的关系, 每一个派生类是基类,例如,Student是一个Person, Teacher 是一个Person
  • 组合是一种has-a的关系,Person组合了head, hand, 每一个Person对象中都有一个head、hand对象。
  • 如果某种情况既可以使用继承又可以使用组合,那么优先使用对象组合,而不是类继承。

其余注意事项

  • 友元关系不能被继承,好比父亲的朋友不一定是你的朋友。
  • 如果基类中定义了静态成员,当这个基类被实例化后出现了一份,那么整个继承体系中都只有这一份实例。

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

--结束END--

本文标题: C++继承与菱形继承详细介绍

本文链接: https://www.lsjlt.com/news/166130.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

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

下载Word文档
猜你喜欢
  • C++继承与菱形继承详细介绍
    目录继承的概念和定义基类和派生类之间的赋值继承中的作用域派生类的默认成员函数菱形继承继承和组合的区分与联系其余注意事项继承的概念和定义 继承机制是面向对象程序设计的一种实现代码复用的...
    99+
    2022-11-13
  • C++继承详细介绍
    在我们进行开发的时候,我们经常会遇到抽象出来的类之间具有继承关系。 举个简单的例子,比如我们在设计某游戏,当中需要定义Human也就是人这个类。每个人有名字,以及一定的血量,能够工作...
    99+
    2022-11-12
  • C++继承的赋值转换与菱形虚拟继承深入详解
    目录一、继承的概念及定义1.1、继承的概念1.2、继承的定义二、基类和派生类对象赋值转换三、继承中的作用域3.1、继承同名成员处理方式3.2、继承同名静态成员处理方式3.3、继承与友...
    99+
    2022-11-13
    C++继承的赋值转换 C++菱形虚拟继承
  • 【C++历险记】面向对象|菱形继承及菱形虚拟继承
    个人主页:兜里有颗棉花糖💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【C++之路】💌 本专栏旨...
    99+
    2023-09-08
    c++ 面向对象
  • C++数据结构继承的概念与菱形继承及虚拟继承和组合
    目录继承的概念继承的定义基类和派生类对象之间的赋值转换继承中的作用域派生类的默认成员函数继承中的两个小细节继承和友元继承和静态成员单继承和多继承(菱形继承)虚拟继承概念虚拟继承的原理...
    99+
    2022-11-13
  • C++ 多继承详情介绍
    C++支持多继承,即允许一个类同时继承多个类。 关于多继承,一直以来争议不断,有一部分人认为多继承会带来大量的问题,为了解决这些问题会使得语言本身变得非常复杂,因此应当避免。另外一派...
    99+
    2022-11-13
  • C++详细讲解继承与虚继承实现
    目录继承的概念及定义概念:定义:继承关系和访问限定符总结基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承与静态成员复杂的菱形继承及菱形虚拟继承虚继承原理继承的...
    99+
    2022-11-13
  • C++中菱形继承的解释与处理详解
    封装,继承,多态。这是C++语言的三大特性,而每次在谈到继承时我们不可避免的要谈到一个很重要的问题——菱形继承。 派生类继承父类,同时也会继承父类中的所有成员...
    99+
    2022-11-13
  • C++数据结构继承的概念与菱形继承及虚拟继承和组合分析
    这篇“C++数据结构继承的概念与菱形继承及虚拟继承和组合分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C++数据结构继承...
    99+
    2023-06-29
  • C++中菱形继承怎么处理
    本篇内容介绍了“C++中菱形继承怎么处理”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!封装,继承,多态。这是C++语言的三大特性,而每次在谈...
    99+
    2023-06-29
  • Python类的继承与多态详细介绍
    目录概念类的创建类的继承多态的使用概念 类(Class): 用来描述具有相同的属性和方法的对象的集合。 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类...
    99+
    2022-11-12
  • Javascript继承机制的详细介绍
    这篇文章主要介绍“Javascript继承机制的详细介绍”,在日常操作中,相信很多人在Javascript继承机制的详细介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Ja...
    99+
    2022-10-19
  • 一文读懂C++中的继承之菱形继承(案例分析)
    目录前言一、什么是多继承?1.单继承2.多继承二、菱形继承1.虚基类的引入2.虚基表的引入总结前言 我们上一篇说了世间万物都有一个继承体制,或多或少子类继承了父类的某些特征,但大多都...
    99+
    2022-11-12
  • C++中的继承问题(继承基本概念、菱形虚拟继承的对象模型)
    目录一、继承的概念与定义格式概念及定义格式二、赋值兼容规则三、继承中的作用域四、子类的默认成员函数构造函数拷贝构造函数赋值运算符重载析构函数构造和析构函数调用顺序五、继承与友元、静态...
    99+
    2023-02-05
    C++的继承 继承基本概念 菱形虚拟继承
  • 详解C++中菱形继承的原理与解决方法
    目录菱形继承形成原因应对方案虚继承 vitrual解决二义性变量内存布局–虚基表感悟菱形继承形成原因 多继承,呈菱形状 菱形继承代码: class A { publi...
    99+
    2023-02-01
    C++菱形继承原理 C++菱形继承解决方法 C++菱形继承
  • Kotlin类的继承实现详细介绍
    1.在kotlin中,默认类都是封闭的closed的。如果要让某个类开放继承,必须用open关键字修饰 类中的方法默认也是关闭的。如果需要子类复写父类的方法,也必须用open修饰。 ...
    99+
    2022-11-13
  • 详解C++中单继承与多继承的使用
    目录前言1.继承的概念和定义(1)继承的概念(2)继承的定义方法(2)继承后子类的成员类型2.基类与派生类的赋值转换(1)派生类赋值给基类(2)基类给派生类3.继承中的作用域(1)隐...
    99+
    2022-11-13
  • 一篇文章超详细的介绍Java继承
    目录前言继承继承的优点重写和隐藏父类方法重写父类中的方法隐藏父类中的方法方法重写和隐藏后的修饰符子类访问父类私有成员使用super关键字使用super调用父类的无参数构造方法/有参数...
    99+
    2022-11-13
  • C++中继承(inheritance)详解及其作用介绍
    概述 面向对象程序设计中最重要的一个概念是继承 (inheritance). 继承允许我们依据另一个类来定义一个类, 这使得创建和维护一个应用程序变得更统一. 这样做也达到了重用代码...
    99+
    2022-11-12
  • Maven继承与聚合详解及作用介绍
    目录一、继承引言1. 继承关系的实现2. 依赖配置二、聚合引言实现聚合三、继承与聚合的合并一、继承 引言 继承关系可以对不同模块的依赖版本做统一管理,因为子模块中的依赖基本都继承于父...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作