🎯 一、什么是继承?

继承是面向对象编程中实现代码复用的重要手段。它允许我们在保持原有类特性的基础上进行扩展,增加新的成员变量和成员函数,从而产生新的类(称为派生类或子类)。

Teacher类

继承自Person

_title: string

teach函数

Student类

继承自Person

_stuid: int

study函数

Person类

_name: string

_age: int

identity函数

Person类

Student类

Teacher类

继承的优势:

  • ✅ 减少代码冗余
  • ✅ 提高代码复用性
  • ✅ 体现类之间的层次关系
// 没有继承时的冗余代码
class Student {
    string _name;
    int _age;
    void identity() { /* ... */ }
};

class Teacher {
    string _name;    // 重复
    int _age;        // 重复
    void identity() { /* ... */ }  // 重复
};

// 使用继承
class Person {
protected:
    string _name;
    int _age;
    void identity() { /* ... */ }
};

class Student : public Person {
    // 继承Person的所有成员
    // 只需要添加学生特有的成员
};

class Teacher : public Person {
    // 只需要添加老师特有的成员
};

🎯 二、继承的基本语法和访问权限

// 继承语法:class 派生类 : 继承方式 基类
class Student : public Person {
    // 派生类特有的成员
    int _stuid;  // 学号
    void study() { /* 学习 */ }
};

三种继承方式:

基类

public继承

protected继承

private继承

保持权限不变

public变protected

public/protected变private

访问权限变化表:

基类成员/继承方式 public继承 protected继承 private继承
public成员 public protected private
protected成员 protected protected private
private成员 不可见 不可见 不可见

关键点:

  • 基类private成员在派生类中不可见,但确实被继承了
  • protected成员专门为继承设计,派生类可以访问
  • 实际开发中,99%的情况使用public继承

🎯 三、派生类的构造函数和析构函数

构造和析构顺序:

Student Person Main Student Person Main 创建Student对象 调用Person构造函数 Person构造完成 调用Student构造函数 Student对象创建完成 销毁Student对象 调用Student析构函数 调用Person析构函数 对象销毁完成
class Person {
public:
    Person(string name) : _name(name) {
        cout << "Person构造" << endl;
    }
    ~Person() {
        cout << "Person析构" << endl;
    }
protected:
    string _name;
};

class Student : public Person {
public:
    Student(string name, int id) 
        : Person(name), _stuid(id) {  // 必须调用基类构造
        cout << "Student构造" << endl;
    }
    ~Student() {
        cout << "Student析构" << endl;
        // 自动调用基类析构
    }
private:
    int _stuid;
};

🎯 四、继承中的隐藏问题

Student类

Person类

被隐藏

Person类

Student类

func函数

func函数
隐藏基类func

当派生类中有与基类同名的成员时,派生类成员会隐藏基类成员

class Person {
public:
    void func() { cout << "Person::func" << endl; }
};

class Student : public Person {
public:
    void func(int i) {  // 隐藏基类的func()
        cout << "Student::func" << endl;
    }
};

int main() {
    Student s;
    s.func(10);  // 正确:调用Student::func(int)
    // s.func();  // 错误:基类func()被隐藏
    s.Person::func();  // 正确:使用作用域运算符
}

🎯 五、切片(赋值兼容性)

重要规则: 派生类对象可以赋值给基类对象,但反过来不行。

不能赋值给

Student对象

赋值给
Person对象/指针/引用

切片操作

只保留Person部分

Person对象

Student对象

class Person { /* ... */ };
class Student : public Person { /* ... */ };

int main() {
    Student stu;
    
    // ✅ 派生类 -> 基类(切片)
    Person p1 = stu;      // 对象赋值
    Person* p2 = &stu;    // 指针
    Person& p3 = stu;     // 引用
    
    // ❌ 基类 -> 派生类(不允许)
    // Student s = p1;  // 编译错误
    
    return 0;
}

🎯 六、多继承与菱形继承

多继承结构:

Student

+study()

Teacher

+teach()

Assistant

+assist()

// 一个类有多个直接基类
class Assistant : public Student, public Teacher {
    // 同时具有Student和Teacher的特性
};

菱形继承问题:

Person

+_name: string

Student

Teacher

Assistant

问题:Assistant有两份_name
导致二义性和数据冗余

class Person { string _name; };
class Student : public Person { /* ... */ };
class Teacher : public Person { /* ... */ };
class Assistant : public Student, public Teacher { /* ... */ };

Assistant a;
// a._name = "张三";  // 错误:二义性,不知道用哪个_name
a.Student::_name = "张三";  // 需要指定
a.Teacher::_name = "李四";
// 问题:Assistant对象中有两份_name,浪费空间

解决方案:虚继承

class Person { string _name; };
class Student : virtual public Person { /* ... */ };  // 虚继承
class Teacher : virtual public Person { /* ... */ };  // 虚继承
class Assistant : public Student, public Teacher { /* ... */ };

Assistant a;
a._name = "张三";  // 正确:只有一份_name

🎯 七、继承 vs 组合

代码复用方式

继承 is-a

组合 has-a

优点:支持多态

缺点:高耦合

优点:低耦合

缺点:无法多态

建议:优先使用组合,需要多态时用继承

特性 继承(is-a) 组合(has-a)
关系 "是一个"关系 "有一个"关系
示例 学生是一个人 汽车有轮胎
耦合度
复用方式 白箱复用 黑箱复用
建议 需要多态时使用 优先使用
// 继承:is-a关系
class Car { /* 汽车通用功能 */ };
class BMW : public Car { /* 宝马特有功能 */ };

// 组合:has-a关系
class Tire { /* 轮胎 */ };
class Car {
    Tire _tire1, _tire2, _tire3, _tire4;  // 汽车有4个轮胎
};

📝 总结

继承是C++面向对象编程的核心特性,使用时需要注意:

  1. 优先使用public继承
  2. 注意构造和析构的顺序
  3. 小心同名成员的隐藏问题
  4. 理解切片机制
  5. 避免菱形继承,必要时使用虚继承
  6. 优先考虑组合,需要多态时才用继承

继承让代码更易维护和扩展,但不当使用也会导致问题。掌握好继承的规则和技巧,能让你的面向对象设计更加优雅。

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐