一、继承的概念
继承(Inheritance)机制是面向对象程序设计中最重要的手段之一。通过继承,我们可以在已有类的基础上进行扩展,增加新的方法和属性,从而创建新的类,这些新类被称为派生类(Derived Class)【14†source】。继承的主要目的是实现代码复用,使得相似功能代码的编写更加简洁高效,并使系统具有更好的可维护性。
1.1 没有继承时的问题
在没有继承的情况下,我们可能会设计多个功能相似的类,这些类中含有相同的成员变量和成员函数。例如,我们设计了两个类:——Student
和Teacher
,它们都拥有姓名、地址、电话等成员变量,以及身份认证的成员函数。这导致了代码的冗余和维护困难。
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; // 职称};
如上代码中,Student
和Teacher
类中存在大量重复成员变量和函数,这种冗余不仅增加了代码量,也使得代码维护变得复杂。
1.2 使用继承的改进
通过继承,我们可以将公共的成员变量和函数提取到一个基类(Base Class)中,例如Person
类,然后让Student
和Teacher
类继承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; // 职称};
通过上述改进,Student
和Teacher
类继承了Person
类,公共成员被提取到基类中,这样可以减少重复代码,提高代码的可维护性。
二、继承的定义与访问控制
2.1 继承的定义方式
在C++中,继承是通过指定继承方式来实现的,常见的继承方式有三种:
public
继承:基类的public
和protected
成员在派生类中保持相同的访问控制。
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
继承是最常见的方式,因为它可以保持基类成员的可访问性。而protected
和private
继承较少使用,因为它们限制了派生类对基类成员的访问,从而降低了代码的扩展性和维护性。
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
类同时继承自Student
和Teacher
,由于这两个基类都继承自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++继承的概念和使用方法。欢迎大家在实际项目中应用这些知识,编写出更加优雅和高效的代码。