当前位置:首页 » 《我的小黑屋》 » 正文

【C++】深入理解 C++ 中的继承进阶:多继承、菱形继承及其解决方案

16 人参与  2024年11月25日 18:01  分类 : 《我的小黑屋》  评论

点击全文阅读


个人主页: 起名字真南的CSDN博客

个人专栏:

【数据结构初阶】 ? 基础数据结构【C语言】 ? C语言编程技巧【C++】 ? 进阶C++【OJ题解】 ? 题解精讲

目录

C++继承机制详解与代码示例?1. 继承的基本概念? 2. 继承示例:`Student`和`Teacher`继承`Person`? 3. 继承类模板示例? 4. 基类和派生类间的转换? 5. 菱形继承与虚继承? 6. 虚继承的实现? 7. 继承与组合的区别? 总结

C++继承机制详解与代码示例

继承是面向对象编程(OOP)中的重要概念之一,通过继承,C++允许我们复用现有类的代码,并在此基础上进行扩展,以构建层次化的类结构。本文将结合详细的代码示例,讲解C++中的继承机制,包括单继承、多继承、菱形继承、虚继承、模板类继承等。学习这些概念将有助于我们在实际开发中设计更高效、可复用的代码结构。


?1. 继承的基本概念

继承(inheritance)是指一个类可以获得另一个类的属性和方法,从而实现代码的复用。通过继承,派生类(也称子类)不仅能继承基类(也称父类)的成员和行为,还可以扩展或修改这些行为。C++中继承形成了“从一般到特殊”的层次结构,并且有以下几种访问控制方式:

public继承:基类的publicprotected成员在派生类中分别保持为publicprotectedprotected继承:基类的public成员变为protectedprotected成员保持不变。private继承:基类的publicprotected成员都变为private

示例代码展示了如何通过继承减少重复代码,提升代码复用率。


? 2. 继承示例:StudentTeacher继承Person

在下面的示例中,我们定义了一个Person类,包含了一些基本的个人信息。然后我们通过继承创建Student类和Teacher类,分别表示学生和老师。这些类除了继承Person类的基本信息外,还包含各自特有的属性和方法。

class Person {public:    void identity() {        cout << "身份认证:" << _name << endl;    }protected:    string _name = "默认名字";    string _address;    string _tel;    int _age = 18;};class Student : public Person {public:    void study() {        cout << _name << "在学习" << endl;    }protected:    int _stuid;  // 学号};class Teacher : public Person {public:    void teaching() {        cout << _name << "在授课" << endl;    }protected:    string _title;  // 职称};

在该示例中:

Person类包含了姓名、地址、电话、年龄等个人信息。Student类继承了Person类,同时增加了学号(_stuid)和study()方法,用于学生的学习行为。Teacher类同样继承自Person类,增加了职称(_title)和teaching()方法,用于表示老师的授课行为。

这种设计避免了在StudentTeacher中重复定义姓名、年龄等成员变量,使代码更加简洁。


? 3. 继承类模板示例

C++支持模板类继承,通过继承标准库的容器类,我们可以轻松地扩展这些容器类的功能。以下示例展示了一个栈(Stack)类模板,分别继承自std::vectorstd::liststd::deque,从而实现栈的基本操作。

template<class T>class Stack : public std::vector<T> {public:    void push(const T& x) {        std::vector<T>::push_back(x);    }    void pop() {        std::vector<T>::pop_back();    }    bool empty() {        return std::vector<T>::empty();    }};

在这个模板类示例中,Stack类继承了std::vector模板类,并添加了push()pop()empty()方法:

push:在栈顶添加一个元素;pop:移除栈顶的元素;empty:判断栈是否为空。

模板类在继承时需要特别注意类域的使用。在上例中,vector<T>::push_back(x)明确指出调用vector模板类的成员函数push_back,以确保模板实例化时找到正确的成员函数。这种方式提供了简便的接口,使得Stack类直接具备了向量的功能而无需重写。


? 4. 基类和派生类间的转换

在C++中,基类指针或引用可以指向派生类对象,从而通过基类指针或引用调用派生类对象的基类成员,这种机制称为“切片”或“切割”。以下代码示例展示了这种转换的用法:

Student sobj;Person* pp = &sobj;       // 基类指针指向派生类对象Person& rp = sobj;        // 基类引用指向派生类对象

这种转换称为“向上转换”(upcasting),因为派生类对象可以转换为基类类型。这在多态性设计中非常有用,可以使代码更加通用。相反,基类对象不能直接转换为派生类对象,但可以通过强制类型转换来实现。这种转换称为“向下转换”(downcasting),需要开发者确保安全性,可以使用dynamic_cast来进行运行时类型检查。

注意:向下转换必须确认基类指针实际指向派生类对象,否则会引发运行时错误。


? 5. 菱形继承与虚继承

C++支持多继承,即一个类可以继承自多个基类。然而,如果多个基类继承自相同的祖先类,就会导致菱形继承问题。如下所示,Assistant类继承了TeacherStudent,而这两个类都继承自Person

class Person {public:    string _name = "莱昂纳多";    int _num = 111;};class Student : virtual public Person {};class Teacher : virtual public Person {};class Assistant : public Teacher, public Student {protected:    string _major;};

此时,Assistant类将拥有两份Person的成员变量,导致数据冗余。此外,访问_name等成员变量时会引起二义性问题。通过虚继承可以避免这一问题。


? 6. 虚继承的实现

虚继承(virtual inheritance)是解决菱形继承问题的一种方法。通过虚继承,派生类可以确保祖先类的成员只有一份,从而消除了菱形继承中的数据冗余和访问二义性问题。以下代码展示了虚继承的用法:

class Person {public:    string _name = "莱昂纳多";    int _num = 111;};class Student : virtual public Person {};class Teacher : virtual public Person {};class Assistant : public Teacher, public Student {protected:    string _major;};int main() {    Assistant a;    a._name = "小李";  // 仅有一个 _name,避免二义性    cout << "Name: " << a._name << endl;    return 0;}

通过在StudentTeacher类中使用virtual public继承Person,虚继承确保了Assistant类仅保留一份Person的成员变量,这样既解决了数据冗余问题,又避免了访问时的二义性。


? 7. 继承与组合的区别

在面向对象设计中,继承组合是两个不同的设计思路:

继承(is-a关系):用于表示派生类是基类的一种特殊类型。继承常用于表示类之间的层次结构,例如Car类和BMW类。组合(has-a关系):用于表示类之间的包含关系。组合常用于表示类之间的拥有关系,例如Car类包含多个Tire对象(轮胎)。

组合优于继承,通常能降低类与类之间的耦合性,使代码更加灵活。仅当派生类确实是基类的一种“特殊类型”时,才考虑使用继承。

例如,在以下代码中,Tire类表示轮胎,Car类组合了多个Tire对象,因为车和轮胎是拥有关系,而不是层次关系。

class Tire {protected:    string _brand = "Michelin";  // 轮胎品牌};class Car {protected:    string _color = "白色";  // 车颜色    Tire _tire1, _tire2, _tire3, _tire4;  // 4个轮胎};

在该示例中,CarTire是组合关系,Car对象拥有四个`Tire

对象,说明两者间的关系是has-a`,更适合组合关系,而非继承关系。


? 总结

C++的继承机制提供了代码复用和层次结构的基础,但其灵活性也带来了复杂性。本文介绍了C++继承的多种使用方式和注意事项,如模板类的继承、基类与派生类的转换、菱形继承和虚继承的使用等。菱形继承可能导致数据冗余和访问冲突,虚继承可以解决这一问题,但会增加实现的复杂性。因此,在设计中要谨慎使用继承,尽量优先选择组合关系,以降低耦合性,提高代码的可维护性和复用性。



点击全文阅读


本文链接:http://zhangshiyu.com/post/191933.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1