C++——菱形继承、虚继承

继承

一.含义

继承来自父类的成员(数据成员和成员函数)

二.访问规则:

父类成员变量在子类中的访问权限:在父类中的原有权限和继承方式中取最小即可。

min(父类中的原有权限,继承方式)

三.切片操作

子类为父类赋值(对象、引用、指针)

父类不能为子类赋值,除非强制类型转换,但是这样操作的结果就是会出现,访问到非法内存的情况。

四.菱形继承

1.菱形继承的结构如下:

 代码表示如下:

//菱形继承
class A {
public:
	int a;
};

class B1 :public A {
public:
	int b1;
};

class B2 :public A{
public:
	int b2;
};

class C :public B1, public B2 {
public:
	int c;
};

但是,这样会出现数据冗余,与二义性的问题。

数据冗余:C的两个直接父类都存储了根本父类A的数据元素

二义性:通过继承关系,A的数据成员被继承了两份,通过C来访问其继承的A的数据元素时,会出现二义性,不清楚是B1继承的还是B2继承的。

看下面的代码:

void test2() {
	C c;
	c.B1::a = 40;
	c.B2::a = 50;
	c.b1 = 2;
	c.b2 = 3;
	c.c = 9;
}

当我们想修改C中继承的A的成员的值时,会出现二义性,修改一次后我们希望得到的结果应该是C继承体系中的a变量的值均被修改并且相同,但是呈现的结果却是这样的:

为了解决菱形继承的数据冗余和二义性的问题,我们引入虚拟继承来解决。

//菱形继承
class A {
public:
	int a;
};

class B1 :virtual public A {
public:
	int b1;
};

class B2 :virtual public A{
public:
	int b2;
};

class C :public B1, public B2 {
public:
	int c;
};

这样处理的结果是:

两种实现的原理:

 这样看似空间比原来占用还大,但事实上,当根本父类A的成员变量多的时候,虚拟继承就体现出了它的空间优势。

五.派生类的默认成员函数:

class Person {
private:
	int age;
public:
	Person() {
		cout << "Person()" << endl;
	}

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

class Student :public Person {
private:
	int sno;
public:
	Student() {
		cout << "Student()" << endl;
	}

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

class Teacher :public Person {
private:
	int tno;
public:
	Teacher() {
		cout << "Teacher()" << endl;
	}
	~Teacher() {
		cout << "~Teacher()" << endl;
	}
};

void test1() {
    	Person p;
	Student s;
	Teacher t;
}

函数调用情况如下:

构造与析构:派生类默认先调基类的构造函数,析构函数,先调用派生类的析构函数,再调用基类的析构函数。

拷贝构造:派生类有显示的拷贝构造,优先调用派生类的,不再调用基类的;否则,就自动调用基类的拷贝构造。

赋值运算符重载:调用规则与构造函数一致。

六.继承中的静态成员

当基类定义了一个静态成员时,该静态成员属于整个继承体系——它的任何一个派生类。

### C++ 中的菱形继承问题及其解决方案 #### 菱形继承概述 在 C++ 中,当一个派生类通过多重继承从两个不同的基类派生而来,而这两个基类又共同继承自同一个祖先类时,就会形成所谓的“菱形继承”。这种结构可能导致二义性问题 (ambiguity),因为编译器无法判断应该使用哪个路径上的成员函数或变量。 例如,在以下情况下会遇到菱形继承: ```cpp class Base { public: void func() {} }; class Derived1 : public Base {}; class Derived2 : public Base {}; class FinalDerived : public Derived1, public Derived2 {}; // 形成菱形继承 ``` 如果 `FinalDerived` 尝试调用 `func()` 函数,则会出现二义性错误,因为编译器不知道是从 `Derived1` 还是 `Derived2` 继承来的版本[^1]。 --- #### 解决方案:虚继承 (Virtual Inheritance) 为了避免上述二义性问题,可以采用 **虚继承** 来解决。虚继承确保所有子类共享同一份基类实例,而不是各自独立复制一份副本。这样即使存在多个继承路径,最终也只会有一条通向公共基类的有效路径。 以下是修正后的代码示例: ```cpp class Base { public: void func() {} }; class Derived1 : virtual public Base {}; // 使用虚拟继承 class Derived2 : virtual public Base {}; // 使用虚拟继承 class FinalDerived : public Derived1, public Derived2 {}; // 不再有二义性 int main() { FinalDerived obj; obj.func(); // 正确解析到唯一的 Base::func() } ``` 在此设计下,无论通过哪一条路径访问 `Base` 类中的成员,都指向相同的单一实例[^3]。 --- #### 实际应用注意事项 尽管虚继承解决了二义性问题,但它引入了一些额外复杂度和开销。具体表现为: - 需要显式指定初始化顺序; - 对象大小可能增加,因存储必要的指针来管理虚表 (vtable) 和动态绑定逻辑。 此外需要注意的是,并非所有的编程语言都支持类似的机制。例如 Java 明确禁止类之间的多重继承,转而依赖接口(interface)实现类似功能[^2]。 对于更复杂的场景,开发者还需考虑如何合理分配职责以及避免不必要的耦合关系[^4]。 --- #### 总结 综上所述,C++ 提供了强大的灵活性允许定义包含菱形继承结构的程序模型;然而伴随此自由度同时也带来了潜在风险——即可能出现名称冲突或者行为未定义等情况。利用好虚继承这一特性能够有效缓解这些问题带来的困扰的同时保持良好的软件工程实践标准[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值