C++ 继承机制详解

继承的概念与定义

继承的基本概念

继承允许我们在保留原有类特性的基础上进行扩展,增加新的成员变量和方法,从而创建新的派生类。

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; // 职称 
};
int main()
{
	return 0;
}

在示例中,我们看到Student和Teacher类有许多共同属性(如姓名、地址、电话、年龄)和方法(如身份认证)。通过继承,我们可以将这些公共部分提取到基类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; // 职称 
};
int main()
{
	Student s;
	Teacher t;
	s.identity();
	t.identity();
	return 0;
}

继承的定义格式

继承的基本语法格式如下:

class 派生类名 : 继承方式 基类名 {
    // 派生类新增成员
};

其中:

  • 基类(父类):被继承的类
  • 派生类(子类):继承基类的类
  • 继承方式:public、protected或private

继承中的访问控制

访问权限变化规则

继承方式会影响基类成员在派生类中的访问权限,具体规则如下表:

基类成员访问权限 \ 继承方式public继承protected继承private继承
public成员publicprotectedprivate
protected成员protectedprotectedprivate
private成员不可见不可见不可见

总结说明:

  1. 基类的private成员在任何继承方式下对派生类都不可见
  2. protected访问限定符是专为继承设计的,它允许成员在派生类中访问,但在类外不可访问
  3. 成员在派生类中的最终访问权限 = min(成员在基类的访问权限, 继承方式)
  4. class默认private继承,struct默认public继承(但建议显式指定)

实际应用建议

实践中绝大多数情况应使用public继承,protected和private继承会限制派生类成员的访问权限,降低代码的可维护性和扩展性,通常不建议使用。

继承中的特殊问题

基类与派生类的转换

public继承的派生类对象可以赋值给基类的指针或引用,这种现象称为"切片",即只保留派生类中基类部分的内容。
在这里插入图片描述

Student sobj;
Person* pp = &sobj;  // 正确:派生类指针赋给基类指针
Person& rp = sobj;   // 正确:派生类引用赋给基类引用
Person pobj = sobj;  // 正确:调用基类的拷贝构造函数

sobj = pobj;         // 错误:基类对象不能赋给派生类对象

作用域与名称隐藏

在继承体系中,基类和派生类有独立的作用域。如果派生类定义了与基类同名的成员,会隐藏基类的成员(即使参数列表不同)。要访问被隐藏的基类成员,需要使用作用域解析运算符::

class Person {
protected:
    int _num = 111;  // 身份证号
};

class Student : public Person {
public:
    void Print() {
        cout << Person::_num << endl;  // 访问基类被隐藏的成员
        cout << _num << endl;         // 访问派生类成员
    }
protected:
    int _num = 999;  // 学号
};

派生类的默认成员函数

构造与析构顺序

派生类对象的构造和析构遵循特定顺序:

  1. 构造顺序:基类构造 → 派生类构造
  2. 析构顺序:派生类析构 → 基类析构

默认成员函数的处理

派生类的默认成员函数需要特别注意基类部分的处理:

  1. 构造函数:必须调用基类构造函数初始化基类成员
  2. 拷贝构造:必须调用基类拷贝构造完成基类部分的拷贝
  3. operator=:必须调用基类operator=完成基类部分的赋值(注意名称隐藏问题)
  4. 析构函数:会自动调用基类析构函数清理基类成员
    在这里插入图片描述
class Student : public Person {
public:
    // 构造函数
    Student(const char* name, int num) 
        : Person(name), _num(num) {}  // 显式调用基类构造
    
    // 拷贝构造
    Student(const Student& s) 
        : Person(s), _num(s._num) {}  // 调用基类拷贝构造
    
    // 赋值运算符
    Student& operator=(const Student& s) {
        if(this != &s) {
            Person::operator=(s);  // 显式调用基类operator=
            _num = s._num;
        }
        return *this;
    }
    
    // 析构函数会自动调用基类析构
    ~Student() {}
};

实现一个不能被继承的类

在某些设计场景中,我们希望禁止一个类被继承,C++提供了两种实现方式:

方法1:C++98风格——基类构造函数私有化

原理:派生类的构造函数必须调用基类的构造函数,如果将基类的构造函数设为私有,派生类就无法访问基类的构造函数,从而导致编译错误。

方法2:C++11风格——final关键字

原理:C++11引入的final关键字可以直接修饰类,明确禁止继承。

class NonInheritable final {  // 使用final禁止继承
public:
    void func() { cout << "Base function" << endl; }
};

class Derived : public NonInheritable {};  // 错误:无法继承final类

C++11的final更简洁直观,是推荐做法。

继承与友元

友元关系的不可继承性

核心规则:基类的友元函数/类 不能 自动成为派生类的友元。
问题示例

class Student;
class Person {
public:
    friend void Display(const Person& p, const Student& s);  // 声明友元
protected:
    string _name;
};

class Student : public Person {
protected:
    int _stuNum;
};

void Display(const Person& p, const Student& s) {
    cout << p._name << endl;    // 正确:访问Person的protected成员
    cout << s._stuNum << endl;  // 错误:无法访问Student的protected成员
}

解决方案
若需要访问派生类的私有/保护成员,必须将函数同时声明为派生类的友元:

class Student : public Person {
public:
    friend void Display(const Person&, const Student&);  // 额外声明
protected:
    int _stuNum;
};

关键点

  • 友元关系是单向的(基类友元 ≠ 派生类友元)。
  • 多重继承时,需为每个派生类单独声明友元。

继承与静态成员

静态成员在继承体系中的特性

核心规则:基类中的静态成员会被整个继承树共享,无论派生出多少个子类,静态成员只有一份实例。

示例

class Person {
public:
    static int _count;  // 静态成员
    string _name;
};
int Person::_count = 0;  // 初始化

class Student : public Person {
protected:
    int _stuNum;
};

int main() {
    Person p;
    Student s;
    
    // 验证静态成员共享
    cout << &p._count << endl;  // 0x1000
    cout << &s._count << endl;  // 0x1000(地址相同)

    // 修改影响所有类
    Person::_count = 10;
    cout << Student::_count;  // 输出10
}

关键行为

  1. 存储唯一性:静态成员在内存中只有一份,所有派生类共享。
  2. 访问权限:继承方式影响静态成员的访问权限(与普通成员规则一致)。
  3. 初始化:静态成员仍需在类外单独初始化。

特殊场景
若派生类定义了同名静态成员,会隐藏基类静态成员(需通过作用域解析访问):

class Student : public Person {
public:
    static int _count;  // 隐藏Person::_count
};
int Student::_count = 0;

cout << Person::_count;  // 访问基类静态成员
cout << Student::_count; // 访问派生类静态成员

多继承与菱形继承

多继承的问题

多继承是指一个派生类有多个直接基类,这可能导致菱形继承问题:
在这里插入图片描述

class Person {};
class Student : public Person {};
class Teacher : public Person {};
class Assistant : public Student, public Teacher {};  // 菱形继承

菱形继承会导致:

  1. 数据冗余:基类Person的成员在Assistant中有两份
  2. 二义性:访问Person成员时需要指明路径
    在这里插入图片描述

虚继承解决方案

使用虚继承可以解决菱形继承问题:

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

虚继承后:

  1. 消除了数据冗余
  2. 可以直接访问基类成员而无需指定路径

但虚继承会增加对象模型复杂度,降低性能,因此应尽量避免设计出菱形继承结构。

继承与组合的选择

继承与组合的比较

特性继承(is-a)组合(has-a)
关系派生类是基类的一种特例类中包含另一个类的对象
复用方式白箱复用(可见内部实现)黑箱复用(隐藏内部实现)
耦合度
灵活性较低(受基类约束)较高
  1. 优先使用组合:组合的耦合度低,更易维护
  2. 适合继承的情况:
    • 类之间确实是is-a关系
    • 需要实现多态特性
  3. 既适合继承又适合组合时,优先选择组合
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值