当前位置:首页 » 《关于电脑》 » 正文

探索C++三大特性--C++ 继承详解:从概念到高级用法

22 人参与  2024年11月21日 12:05  分类 : 《关于电脑》  评论

点击全文阅读


一、继承的概念

继承(Inheritance)机制是面向对象程序设计中最重要的手段之一。通过继承,我们可以在已有类的基础上进行扩展,增加新的方法和属性,从而创建新的类,这些新类被称为派生类(Derived Class)【14†source】。继承的主要目的是实现代码复用,使得相似功能代码的编写更加简洁高效,并使系统具有更好的可维护性。

1.1 没有继承时的问题

在没有继承的情况下,我们可能会设计多个功能相似的类,这些类中含有相同的成员变量和成员函数。例如,我们设计了两个类:——StudentTeacher,它们都拥有姓名、地址、电话等成员变量,以及身份认证的成员函数。这导致了代码的冗余和维护困难。

class Student {public:    // 身份认证    void identity() {        // ...    }    // 学习    void study() {        // ...    }protected:    string _name = "peter"; // 姓名    string _address;         // 地址    string _tel;             // 电话    int _age = 18;           // 年龄    int _stuid;              // 学号};class Teacher {public:    // 身份认证    void identity() {        // ...    }    // 授课    void teaching() {        // ...    }protected:    string _name = "张三";   // 姓名    int _age = 18;           // 年龄    string _address;         // 地址    string _tel;             // 电话    string _title;           // 职称};

如上代码中,StudentTeacher类中存在大量重复成员变量和函数,这种冗余不仅增加了代码量,也使得代码维护变得复杂。

1.2 使用继承的改进

通过继承,我们可以将公共的成员变量和函数提取到一个基类(Base Class)中,例如Person类,然后让StudentTeacher类继承Person类,从而减少重复代码。

class Person {public:    // 身份认证    void identity() {        cout << "void identity()" << _name << endl;    }protected:    string _name = "张三";  // 姓名    string _address;        // 地址    string _tel;            // 电话    int _age = 18;          // 年龄};class Student : public Person {public:    // 学习    void study() {        // ...    }protected:    int _stuid;             // 学号};class Teacher : public Person {public:    // 授课    void teaching() {        // ...    }protected:    string title;           // 职称};

通过上述改进,StudentTeacher类继承了Person类,公共成员被提取到基类中,这样可以减少重复代码,提高代码的可维护性。

二、继承的定义与访问控制

2.1 继承的定义方式

在C++中,继承是通过指定继承方式来实现的,常见的继承方式有三种:

public 继承:基类的publicprotected成员在派生类中保持相同的访问控制。

protected 继承:基类的public成员在派生类中变为protected,而protected成员保持不变。

private 继承:基类的所有非private成员在派生类中都变为private

class Person {public:    void Print() {        cout << _name << endl;    }protected:    string _name; // 姓名};// public 继承class Student : public Person {protected:    int _stunum; // 学号};

在实际使用中,public继承是最常见的方式,因为它可以保持基类成员的可访问性。而protectedprivate继承较少使用,因为它们限制了派生类对基类成员的访问,从而降低了代码的扩展性和维护性。

2.2 基类成员的访问控制

在继承体系中,基类的private成员在派生类中是不可见的,这意味着派生类对象无法访问基类中的私有成员。而基类的protected成员则可以在派生类中访问,这使得派生类能够继承和使用这些成员。

class Person {protected:    string _name; // 姓名private:    int _age;    // 年龄};class Student : public Person {public:    void Print() {        cout << _name << endl; // 可以访问基类的受保护成员        // cout << _age;      // 无法访问基类的私有成员    }protected:    int _stunum; // 学号};

三、继承中的作用域与隐藏规则

3.1 成员隐藏

在继承体系中,如果派生类和基类中存在同名的成员,那么基类的成员会被隐藏。此时,派生类只能访问自己的成员,而不能直接访问基类的同名成员。

class Person {protected:    int _num = 111; // 身份证号};class Student : public Person {public:    void Print() {        cout << "身份证号: " << Person::_num << endl;        cout << "学号: " << _num << endl;    }protected:    int _num = 999; // 学号};

在上面的例子中,Student类中的_num隐藏了Person类中的同名成员,因此如果想访问基类中的_num,需要使用Person::_num来显式指定。

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

在派生类中,默认的成员函数(如构造函数、析构函数、拷贝构造函数、赋值运算符等)会自动生成,并且派生类会调用基类的相应成员函数来初始化基类部分。

class Person {public:    Person(const char* name = "peter") : _name(name) {        cout << "Person()" << endl;    }    ~Person() {        cout << "~Person()" << endl;    }protected:    string _name; // 姓名};class Student : public Person {public:    Student(const char* name, int num) : Person(name), _num(num) {        cout << "Student()" << endl;    }    ~Student() {        cout << "~Student()" << endl;    }protected:    int _num; // 学号};int main() {    Student s1("jack", 18);    return 0;}

在上面的代码中,Student类的构造函数和析构函数在执行时会首先调用基类Person的构造函数和析构函数,这样能够确保基类部分正确初始化和清理。

五、多继承与菱形继承问题

5.1 多继承

C++ 支持多继承,即一个类可以继承自多个基类。然而,多继承可能会导致菱形继承问题,即多个基类中有相同的成员,导致派生类中有两份相同的成员拷贝。

class Person {public:    string _name; // 姓名};class Student : public Person {protected:    int _num; // 学号};class Teacher : public Person {protected:    int _id; // 职工编号};class Assistant : public Student, public Teacher {protected:    string _majorCourse; // 主修课程};

在上述代码中,Assistant类同时继承自StudentTeacher,由于这两个基类都继承自Person,因此Assistant类中存在两份_name成员,这就产生了数据冗余和访问二义性的问题。

5.2 虚继承解决菱形继承问题

为了避免菱形继承带来的问题,我们可以使用虚继承。通过虚继承,派生类只会保留一份基类的成员。

class Person {public:    string _name; // 姓名};class Student : virtual public Person {protected:    int _num; // 学号};class Teacher : virtual public Person {protected:    int _id; // 职工编号};class Assistant : public Student, public Teacher {protected:    string _majorCourse; // 主修课程};int main() {    Assistant a;    a._name = "peter"; // 没有二义性    return 0;}

通过虚继承,Assistant类中只会有一份Person类的成员,避免了数据冗余和访问的二义性。

六、继承与组合的选择

在面向对象设计中,继承表示一种is-a的关系,而组合表示一种has-a的关系。优先使用组合而不是继承可以降低类之间的耦合度,提高代码的灵活性和可维护性。

class Tire {protected:    string _brand = "Michelin";  // 品牌    size_t _size = 17;            // 尺寸};class Car {protected:    string _colour = "白色";       // 颜色    Tire _t1, _t2, _t3, _t4;      // 四个轮胎};class BMW : public Car {public:    void Drive() { cout << "驾驶宝马车" << endl; }};

在上述代码中,Car类通过组合方式包含了四个轮胎对象Tire,这种设计更符合逻辑上的has-a关系。在实际设计中,优先使用组合可以减少代码的耦合性,增强代码的复用性和灵活性。

总结

继承是C++中实现代码复用和扩展的重要手段,它可以简化代码结构,减少冗余代码,增强代码的可维护性。然而,在使用继承时,我们需要遵循一些设计原则,避免滥用继承导致代码复杂化。同时,理解继承与组合之间的差异,合理选择使用继承或组合,对于编写高质量的面向对象代码非常重要。

希望这篇博客能够帮助大家深入理解C++继承的概念和使用方法。欢迎大家在实际项目中应用这些知识,编写出更加优雅和高效的代码。


点击全文阅读


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

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

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

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

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