文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

C++继承的赋值转换与菱形虚拟继承深入详解

2022-11-13 14:07

关注

一、继承的概念及定义

继承是面向对象三大特性之一。

1.1、继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用。继承是类设计层次的复用。

1.2、继承的定义

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

继承方式:

基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

二、基类和派生类对象赋值转换

class person{
protected:
	string _name;
	int _age;
};
class student :public person
{
public:
	int _No;
};
void test01()
{
	student sobj;
	//1.子类对象可以赋值给父类对象/指针/引用
	person pobj = sobj;
	person* pp = &sobj;
	person& rp = sobj;
	//2.基类对象不可以赋值给派生类对象
	//sobj = pobj;
	//3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &sobj;
	student* ps1 = (student*)pp;//这种情况是可以的
	ps1->_No = 10;
	pp = &pobj;
	student* ps2 = (student*)pp;//这种情况转换时虽然可以,但存在越界访问的问题
	ps2->_No = 10;
}

三、继承中的作用域

3.1、继承同名成员处理方式

⚠️问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

class Base {
public:
	Base(){
		m_A = 100;
	}
	void func(){
		cout << "Base - func()调用" << endl;
	}
	void func(int a){
		cout << "Base - func(int a)调用" << endl;
	}
public:
	int m_A;
};
class Son : public Base {
public:
	Son(){
		m_A = 200;
	}
	//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
	//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
	void func()
	{
		cout << "Son - func()调用" << endl;
	}
public:
	int m_A;
};
void test01()
{
	Son s;
	cout << "Son下的m_A = " << s.m_A << endl;
	cout << "Base下的m_A = " << s.Base::m_A << endl;
	s.func();
	s.Base::func();
	s.Base::func(10);
}

⭐️⭐️⭐️总结:

注:子类和父类中有同名成员时构成隐藏关系,也叫重定义。需要注意的是,如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

3.2、继承同名静态成员处理方式

⚠️:问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致:

class Base {
public:
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
		cout << "Base - static void func(int a)" << endl;
	}
	static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};
int Son::m_A = 200;
//同名成员属性
void test01()
{
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;
	//通过类名访问
	cout << "通过类名访问: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名成员函数
void test02()
{
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	s.func();
	s.Base::func();
	cout << "通过类名访问: " << endl;
	Son::func();
	Son::Base::func();
	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
	Son::Base::func(100);
}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)

3.3、继承与友元

友元关系不可以继承,也就是说基类的友元不要可以访问子类的私有成员和保护成员。

(就好比说爸爸的朋友不一定是我的朋友)

3.4、继承与静态成员

基类定义了static静态成员,则整个继承体系只有这一个成员(我们知道静态成员是整个类共享的),无论派生出多少个子类,都只有这么一个static成员。

class person
{
public:
	person()
	{
		_count++;
	}
protected:
	string _name;
public:
	static int _count;//统计人数
};
int person::_count = 0;
class student:public person
{
protected:
	int _stuNum;
};
class graduate :public student
{
protected:
	string course;
};
void test()
{
	student s1;
	student s2;
	student s3;
	graduate s4;
	cout << "人数" << person::_count << endl;
	student::_count = 0;
	cout << "人数" << person::_count << endl;
}

人数4
人数0
请按任意键继续. .

代码解释:因为子类对象构造是会调用基类的构造函数,所以每实例化一个子类对象都会调用一次基类构造,从而_count++,并且静态成员是整个类共享的,所以无论哪个子类都可修改!!!

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

6个默认成员函数,“默认"的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。
class person
{
public:
	person(const char* name = "pxl")
		:_name(name)
	{}
	person(const person& p)
		:_name(p._name)
	{}
	person& operator=(const person& p)
	{
		if (this != *p){
			_name = p._name;
		}
		return *this;
	}
	~person()
	{}
protected:
	string _name;
};
class student :public person
{
public:
	student(const char* name, int num)
		:person(name)//显示调用基类的构造函数初始化基类成员
		, _num(num)
	{}
	student(const student& s)
		:person(s)//注意这里有个隐式的切片操作 person& p = s;
		, _num(s._num)
	{}
	student& operator=(const student& s)
	{
		if (this != &s){
			person::operator=(s);//调用基类的operator=完成基类的赋值
			_num = s._num;
		}
		return *this;
	}
	~student()
	{
		cout << "~student()" << endl;
		//注意这里会自动调用父类析构
	}
protected:
	int _num;
};
void test()
{
	student s1("ppp", 20);
	student s2(s1);
	student s3("xxx", 30);
	s1 = s3;
}

⚠️留意代码中注释部分!

五、复杂菱形继承及菱形虚拟继承

5.1、继承分类

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

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

菱形继承:两个派生类继承同一个基类,又有某个类同时继承者两个派生类。菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。

利用虚继承可以解决菱形继承问题

⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

对于菱形继承的二义性问题,我们可以在访问的时候加上类域,这样是可以解决的,但是数据冗余无法解决。所以下面引入虚拟继承!

5.2、虚拟继承解决菱形继承问题原理

为了研究虚拟继承原理,我们给出一个简单的菱形继承体系,再借助内存窗口观察对象成员模型。

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;
	system("pause");
	return 0;
}

如图是菱形继承的内存对象成员模型,可以看出来数据冗余!!!

下面是菱形虚拟继承的内存对象成员模型:

这里可以分析出D对象将A放在了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?

这里通过B和C的两个指针,指向一张表。这两个指针叫虚基表指针,这两个表叫虚基表,虚基表中存的是偏移量。通过偏移量可以找到下面的A。

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

阅读原文内容投诉

免责声明:

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

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

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯