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

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

一、继承的概念

继承(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://www.kler.cn/a/398399.html

相关文章:

  • 蓝桥杯备赛(持续更新)
  • 7.高可用集群架构Keepalived双主热备原理
  • 37.超级简易的计算器 C语言
  • 消息中间件分类
  • 代码版本管理艺术
  • 学习threejs,使用第一视角控制器FirstPersonControls控制相机
  • 每日OJ题_牛客_NC114旋转字符串_C++_Java
  • STM32 | 空气净化器
  • 构建安全可靠的人工智能数据中心的关键因素
  • mac怎么看当前终端是zsh还是bash
  • 通过全球最前沿的技术解决视频拼接中时延带来的的应用缺陷,使得全景视频拼接能够真正得以大范围使用和推广的智慧地产开源了。
  • web前端开发--盒子属性
  • C++20中的概念(Concepts)到底是什么概念?
  • Android - Pixel 6a 手机OS 由 Android 15 降级到 Android 14 操作记录
  • 六:从五种架构风格推导出HTTP的REST架构
  • 2024.5 AAAiGLaM:通过邻域分区和生成子图编码对领域知识图谱对齐的大型语言模型进行微调
  • 深度学习神经网络创新点方向(具体)
  • Linux——环境基础开发工具使用1
  • React Native 全栈开发实战班 - 原生功能集成之地理位置服务
  • 常用的Anaconda Prompt命令行指令
  • 【第三课】Rust变量与数据类型(二)
  • java.sql.SQLException Parameter index out of range
  • Ubuntu下的Eigen库的安装及基本使用教程
  • 14.最长公共前缀
  • 基于Kafka2.1解读Consumer原理
  • memblock内存分配器