当前位置: 首页 > news >正文

【C++继承】关于继承的细节分析

  1. 继承的本质

继承的本质是一种复用,即保持原有类的特之外,还可以进行扩展。而继承后由于扩展就会产生其他复杂的问题,以下会会列举出继承产生的主要问题,并对问题进行分析

  1. 继承产生的二义性及数据冗余的问题

在多继承时,如果父类都继承了同一个类,这时就不能直接访问父类的成员,而是必须指定是哪个类(如f.Derived1::show();)。

#include <iostream>

class Base {
public:
	Base(int val) : baseValue(val) {}
	void show() const { std::cout << "Base value: " << baseValue << std::endl; }

protected:
	int baseValue;
};

class Derived1 : public Base {
public:
	Derived1(int val) : Base(val) {}
};

class Derived2 : public Base {
public:
	Derived2(int val) : Base(val) {}
};

class Final : public Derived1, public Derived2 {
public:
	Final(int val) : Derived1(val), Derived2(val) {}
};


int main() {
	Final f(10);
	f.show(); // 错误:二义性,不知道是Derived1还是Derived2中的show()
	f.Derived1::show();
	return 0;
}

同时,多个父类继承于同一类时,会产生数据冗余。代码如下更改,derived1和derived2分别打印baseValue的地址,会发现两边的地址不同,即derived1和derived2继承的于Base的baseValue是分开的,可以得出结论——直接继承,在那条继承通道上会产生各自的继承副本,数据与其他继承通道是不共享的。

#include <iostream>

class Base {
public:
	Base(int val) : baseValue(val) {}
	void show() const { std::cout << "Base value: " << baseValue << std::endl; }
protected:
	int baseValue;
};

class Derived1 : public Base {
public:
	Derived1(int val) : Base(val) {}
	void fun1()
	{
		printf("Derived1 baseValu: %p\n", &baseValue);
	}
};

class Derived2 : public Base {
public:
	Derived2(int val) : Base(val) {}
	void fun1()
	{
		printf("Derived2 baseValu : %p\n", &baseValue);
	}
};

class Final : public Derived1, public Derived2
{
public:
	Final(int val) : Derived1(val), Derived2(val) {}
};


int main() {
	Final f(10);
	f.Derived1::fun1();
	f.Derived2::fun1();

	return 0;
}

这就是虚继承提出的必要性,虚继承解决了上述问题,确保无论有多少条路径继承自基类,派生类中只会有一个基类的实例。这样可以避免数据冗余和二义性问题。

#include <iostream>

class Base {
public:
	Base(int val) : baseValue(val) {}
	void show() const { std::cout << "Base value: " << baseValue << std::endl; }
protected:
	int baseValue;
};

class Derived1 :virtual public Base 
//class Derived1 : public Base
{
public:
	Derived1(int val) : Base(val) {}
	void fun1()
	{
		printf("Derived1 baseValu: %p\n", &baseValue);
	}
};

class Derived2 :virtual public Base
//class Derived2 :public Base
{
public:
	Derived2(int val) : Base(val) {}
	void fun1()
	{
		printf("Derived2 baseValu : %p\n", &baseValue);
	}
};

class Final : public Derived1, public Derived2
{
public:
	Final(int val) : Derived1(val), Derived2(val),Base(val) {}
};


int main() {
	Final f(10);
	f.Derived1::fun1();
	f.Derived2::fun1();

	return 0;
}

普通继承的特点:
  1. 多份基类副本:如果一个类通过多个路径继承同一个基类,则每个路径都有一个独立的基类副本。
  2. 可能引起二义性:当试图访问基类的成员时,编译器无法确定使用哪个副本,从而导致二义性错误。
  3. 构造顺序简单:派生类构造函数直接调用其直接基类的构造函数。
虚继承的特点:
  1. 单一基类副本:无论通过多少条路径继承同一个基类,派生类中只会有一个基类的实例。
  2. 解决二义性问题:消除了由于多重继承引起的二义性问题。
  3. 复杂的构造顺序:需要显式调用虚基类的构造函数。通常,虚基类的构造函数应在最远派生类的初始化列表中被调用,而不是在中间基类中调用。

当然,直接继承与虚拟继承从实质上讲并不是说谁优谁劣,个人认为不排除需要有当你不需要共享基类实例,可以使用普通继承的。

3、父类和子类有同名成员

父类和子类有同名成员时,这种情况子类会隐藏父类的成员。

class Base
{
public:
	Base(int val)
		:_val(val)
	{
	}
	void getVal()
	{
		cout << "Base _val: " << _val << endl;
	}
protected:
	int _val;
};

class Derived1:public Base
{
public:
	Derived1(int n)
		:Base(n)
		,_dval(n)
	{

	}
	void getVal()
	{
		cout << "Derived _dval: " << _dval << endl;
	}
protected:
	int _dval;
};

int main()
{
	Derived1 d1(1);
	d1.getVal();
	return 0;
}

其实隐藏是很好理解的,即就近原则,在Derived1中有getVal()函数,则Derived1中的getVal()函数是更加靠近d1的,所以会优先调用Derived1中的getVal()。当然也可以指定调用Base中的getVal()。

以上展示的是同名成员函数,同理同名成员变量也是同样的原理。

4、虚函数

谈到隐藏的时候就不得不提到虚函数。如果父类的函数是虚函数,子类中的同名函数会覆盖(而不是隐藏)父类的函数。父类指针或引用调用 show(),也会执行子类中的版本,这就是多态。这里不过多介绍多态。

class Base
{
public:
	Base(int val)
		:_val(val)
	{
	}
	virtual void getVal()
	{
		cout << "Base _val: " << _val << endl;
	}
protected:
	int _val;
};

class Derived1:public Base
{
public:
	Derived1(int n)
		:Base(n)
		,_dval(n+1)
	{

	}
	virtual void getVal() override
	{
		cout << "Derived _dval: " << _dval << endl;
	}
protected:
	int _dval;
};

int main()
{

	/*Derived1 d1(1);
	d1.getVal();
	d1.Base::getVal();*/

	Base* b;
	b = new Derived1(1);
	b->getVal();
	return 0;
}

http://www.dtcms.com/a/106687.html

相关文章:

  • yolo11参数信息
  • 学习总结 网格划分+瞬态求解设置
  • vector模拟实现2
  • Windows系统服务器安装Office Online Server
  • C语言复习笔记--指针(2)
  • 详解 MySQL InnoDB 引擎中的聚簇索引和非聚簇索的区别
  • OpenCV边界填充方法详解
  • Python入门(6):面向对象编程(OOP)
  • Smith Chart阻抗匹配
  • elasticsearch 7.17 索引模板
  • 一段式端到端自动驾驶:UniAD:Planning-oriented Autonomous Driving
  • python函数装饰器
  • (linux操作系统)环境变量
  • 【NS3-如何在NS3中模拟每个节点的本地时钟?
  • 自动获取屏幕尺寸信息的html文件
  • [图文]directed不是direct-《分析模式》漫谈50
  • 算法刷题记录——LeetCode篇(3.10) [第291~300题](持续更新)
  • 【Java中级】10章、内部类、局部内部类、匿名内部类、成员内部类、静态内部类的基本语法和细节讲解配套例题巩固理解【5】
  • 《AI大模型应知应会100篇》第7篇:Prompt Engineering基础:如何与大模型有效沟通
  • SAP BDC应用1-批量处理财务三大报表
  • Java 代理模式 详解
  • 4.2学习总结
  • Vue与React区别分析
  • 机器学习开发全流程详解:从数据到部署的完整指南
  • el-dialog实现遮罩层后面的内容也能点击
  • SqlServer整库迁移至Oracle
  • 鹧鸪云光伏仿真软件场外设计功能:构建系统级工程闭环
  • time.sleep(10)和 async 区别
  • 通信算法之251: 时频图谱spectrogram(如短时傅里叶变换STFT)
  • 数据结构——队列的实现