一、派生类与基类的构造函数关系

构造函数的作用是初始化对象的成员。派生类对象包含基类子对象派生类新增子对象两部分,因此派生类构造时必须先初始化基类子对象,再初始化自身新增成员。

核心规则

  1. 默认行为:派生类的构造函数(无论无参还是有参),默认自动调用基类的无参构造函数
  2. 显式调用有参构造:如果基类没有无参构造(仅定义了有参构造),或者需要用特定参数初始化基类成员,必须在派生类构造函数的初始化列表中显式调用基类的有参构造函数。
  3. 执行顺序基类构造函数 → 派生类成员对象的构造函数(若有) → 派生类构造函数体

示例代码

#include <iostream>
#include <string>
using namespace std;

// 基类:人
class Person {
protected:
    string name;
    int age;
public:
    // 基类无参构造
    Person() : name("未知"), age(0) {
        cout << "Person 无参构造函数执行" << endl;
    }

    // 基类有参构造
    Person(string n, int a) : name(n), age(a) {
        cout << "Person 有参构造函数执行:" << name << ", " << age << endl;
    }
};

// 派生类:学生
class Student : public Person {
private:
    string id; // 学号(派生类新增成员)
public:
    // 1. 派生类无参构造:默认调用基类无参构造
    Student() : id("000000") {
        cout << "Student 无参构造函数执行" << endl;
    }

    // 2. 派生类有参构造:显式调用基类有参构造初始化继承的成员
    Student(string n, int a, string i) : Person(n, a), id(i) {
        cout << "Student 有参构造函数执行:学号" << id << endl;
    }

    void show() {
        cout << "姓名:" << name << ",年龄:" << age << ",学号:" << id << endl;
    }
};

int main() {
    cout << "===== 创建无参学生对象 =====" << endl;
    Student s1;
    s1.show();

    cout << "\n===== 创建有参学生对象 =====" << endl;
    Student s2("张三", 18, "2024001");
    s2.show();

    return 0;
}

输出结

关键说明

  • 如果基类只定义了有参构造(没有无参构造),派生类构造函数必须在初始化列表显式调用基类有参构造,否则编译报错。
  • 初始化列表的执行顺序只与成员声明顺序有关,与初始化列表中书写顺序无关(基类构造永远先于派生类)。

二、派生类与基类的析构函数关系

析构函数的作用是释放对象占用的资源,其执行顺序与构造函数完全相反:先清理派生类自身的资源,再清理基类子对象的资源。

核心规则

  1. 派生类的析构函数执行完毕后,自动调用基类的析构函数
  2. 析构函数没有参数,因此无法重载,也不需要在派生类析构中显式调用基类析构(编译器自动完成)。
  3. 重要:当用基类指针 / 引用指向派生类对象时,必须将基类析构函数声明为virtual(虚析构函数),否则删除对象时只会调用基类析构,导致派生类资源泄漏。

示例代码(基于上例扩展)

// 给Person和Student添加析构函数
class Person {
    // ... 其他成员不变
public:
    ~Person() {
        cout << "Person 析构函数执行:" << name << endl;
    }
};

class Student : public Person {
    // ... 其他成员不变
public:
    ~Student() {
        cout << "Student 析构函数执行:学号" << id << endl;
    }
};

int main() {
    cout << "===== 创建有参学生对象 =====" << endl;
    Student s("李四", 20, "2024002");
    s.show();
    cout << "===== 对象即将销毁 =====" << endl;
    return 0; // 程序结束时自动销毁局部对象
}

输出结果

===== 创建有参学生对象 =====
Person 有参构造函数执行:李四, 20
Student 有参构造函数执行:学号2024002
姓名:李四,年龄:20,学号:2024002
===== 对象即将销毁 =====
Student 析构函数执行:学号2024002
Person 析构函数执行:李四

三、派生类与基类的拷贝构造函数关系

拷贝构造函数用于用一个已存在的对象初始化新对象,继承体系中拷贝构造的执行逻辑与普通构造类似。

核心规则

  1. 默认行为:派生类的默认拷贝构造函数,会自动调用基类的拷贝构造函数,完成基类子对象的拷贝。
  2. 显式定义派生类拷贝构造:如果手动定义派生类的拷贝构造函数,编译器默认调用基类的无参构造函数(而非拷贝构造),此时必须在初始化列表中显式调用基类的拷贝构造,否则基类成员会被默认初始化,导致数据丢失。

示例代码

#include <iostream>
#include <string>
using namespace std;

class Person {
protected:
    string name;
    int age;
public:
    Person(string n, int a) : name(n), age(a) {}
    // 基类拷贝构造
    Person(const Person& p) : name(p.name), age(p.age) {
        cout << "Person 拷贝构造执行" << endl;
    }
    void show() { cout << name << ", " << age; }
};

class Student : public Person {
private:
    string id;
public:
    Student(string n, int a, string i) : Person(n, a), id(i) {}

    // 错误写法:显式定义派生类拷贝构造,未调用基类拷贝构造
    // Student(const Student& s) : id(s.id) {
    //     cout << "Student 拷贝构造执行(错误版)" << endl;
    // }

    // 正确写法:显式调用基类拷贝构造
    Student(const Student& s) : Person(s), id(s.id) {
        cout << "Student 拷贝构造执行(正确版)" << endl;
    }

    void show() {
        Person::show();
        cout << ", 学号:" << id << endl;
    }
};

int main() {
    Student s1("王五", 19, "2024003");
    cout << "原对象:";
    s1.show();

    cout << "\n拷贝生成新对象:" << endl;
    Student s2(s1); // 调用拷贝构造
    cout << "新对象:";
    s2.show();

    return 0;
}

输出结果(正确版)

原对象:王五, 19, 学号:2024003

拷贝生成新对象:
Person 拷贝构造执行
Student 拷贝构造执行(正确版)
新对象:王五, 19, 学号:2024003

错误版输出对比

如果使用注释中的错误写法,输出会变成:

原对象:王五, 19, 学号:2024003

拷贝生成新对象:
Student 拷贝构造执行(错误版)
新对象:, 0, 学号:2024003

可以看到基类的nameage被默认初始化(空字符串和 0),数据完全丢失。

四、派生类与基类的赋值运算符重载关系

赋值运算符重载用于对象之间的赋值操作(=),其继承规则与拷贝构造类似,但需要手动处理基类部分的赋值。

核心规则

  1. 默认行为:派生类的默认赋值运算符重载函数,会自动调用基类的赋值运算符,完成基类子对象的赋值。
  2. 显式定义派生类赋值重载:如果手动定义派生类的赋值运算符,编译器不会自动调用基类的赋值运算符,必须通过基类名::operator=显式调用,否则基类成员不会被正确赋值。
  3. 赋值重载需要遵循自赋值检查返回自身引用的通用规则。

示例代码

#include <iostream>
#include <string>
using namespace std;

class Person {
protected:
    string name;
    int age;
public:
    Person(string n, int a) : name(n), age(a) {}
    // 基类赋值运算符重载
    Person& operator=(const Person& p) {
        if (this != &p) { // 自赋值检查
            name = p.name;
            age = p.age;
            cout << "Person 赋值运算符执行" << endl;
        }
        return *this;
    }
    void show() { cout << name << ", " << age; }
};

class Student : public Person {
private:
    string id;
public:
    Student(string n, int a, string i) : Person(n, a), id(i) {}

    // 派生类赋值运算符重载
    Student& operator=(const Student& s) {
        if (this != &s) { // 自赋值检查
            Person::operator=(s); // 显式调用基类赋值运算符
            id = s.id; // 赋值派生类新增成员
            cout << "Student 赋值运算符执行" << endl;
        }
        return *this;
    }

    void show() {
        Person::show();
        cout << ", 学号:" << id << endl;
    }
};

int main() {
    Student s1("赵六", 21, "2024004");
    Student s2("临时", 0, "000000");
    cout << "赋值前s2:";
    s2.show();

    s2 = s1; // 调用赋值运算符
    cout << "赋值后s2:";
    s2.show();

    return 0;
}

输出结果

赋值前s2:临时, 0, 学号:000000
Person 赋值运算符执行
Student 赋值运算符执行
赋值后s2:赵六, 21, 学号:2024004

关键说明

如果注释掉Person::operator=(s);,赋值后 s2 的nameage仍为 "临时" 和 0,只有id被正确赋值。

五、静态成员的继承性

静态成员属于类本身,而非某个对象,存储在静态全局区,整个程序中只有一份副本。继承体系中静态成员的规则如下:

核心规则

  1. 共享性:基类的静态成员被所有派生类共享,无论派生多少层,静态成员只有一份。
  2. 访问性:派生类可以直接访问基类的公有 / 保护静态成员,访问方式为基类名::静态成员派生类名::静态成员(效果相同)。
  3. 隐藏性:派生类可以定义与基类同名的静态成员,此时派生类的静态成员会隐藏基类的同名静态成员(通过作用域解析符::仍可访问基类版本)。

示例代码

#include <iostream>
using namespace std;

class Base {
public:
    static int count; // 基类静态成员变量
    static void showCount() { // 基类静态成员函数
        cout << "Base::count = " << count << endl;
    }
};
int Base::count = 0; // 静态成员类外初始化

class Derived : public Base {
public:
    static int count; // 派生类重定义同名静态成员,隐藏基类版本
    static void showCount() { // 派生类重定义同名静态函数
        cout << "Derived::count = " << count << endl;
        cout << "Base::count = " << Base::count << endl; // 显式访问基类静态成员
    }
};
int Derived::count = 100; // 派生类静态成员初始化

int main() {
    // 基类静态成员访问
    Base::count = 10;
    Base::showCount(); // 输出 Base::count = 10

    // 派生类访问基类静态成员
    Derived::Base::showCount(); // 输出 Base::count = 10
    cout << "通过Derived访问Base::count = " << Derived::Base::count << endl;

    // 派生类自身静态成员
    Derived::showCount(); // 输出 Derived::count = 100 和 Base::count = 10

    // 验证共享性:修改基类静态成员,所有派生类都能看到
    Base::count = 20;
    Derived::showCount(); // 输出 Base::count = 20

    return 0;
}

输出结果

plaintext

Base::count = 10
Base::count = 10
通过Derived访问Base::count = 10
Derived::count = 100
Base::count = 10
Derived::count = 100
Base::count = 20

六、友元的继承性

友元的作用是让外部函数 / 类访问类的私有 / 保护成员,但友元关系不具有继承性

核心规则

  1. 基类的友元不能直接访问派生类的私有 / 保护成员,除非派生类显式声明该函数 / 类为自己的友元。
  2. 基类的友元可以访问派生类中从基类继承的成员(如果该成员在基类中是公有的,或友元通过基类指针 / 引用访问)。

示例代码

#include <iostream>
#include <string>
using namespace std;

class Derived; // 前向声明

class Base {
private:
    int base_private;
protected:
    int base_protected;
public:
    Base(int a, int b) : base_private(a), base_protected(b) {}
    friend void showBase(const Base& b); // 声明友元函数
};

class Derived : public Base {
private:
    int derived_private; // 派生类私有成员
public:
    Derived(int a, int b, int c) : Base(a, b), derived_private(c) {}
    // 如果取消下面注释,友元函数就能访问derived_private
    // friend void showBase(const Base& b);
};

// 友元函数实现
void showBase(const Base& b) {
    cout << "基类私有成员:" << b.base_private << endl;
    cout << "基类保护成员:" << b.base_protected << endl;

    // 错误:友元不能访问派生类的私有成员
    // const Derived& d = static_cast<const Derived&>(b);
    // cout << "派生类私有成员:" << d.derived_private << endl;
}

int main() {
    Derived d(10, 20, 30);
    showBase(d); // 派生类对象可以传给基类引用,友元能访问基类部分
    return 0;
}

输出结果

基类私有成员:10
基类保护成员:20

如果取消注释中访问derived_private的代码,编译会报错,因为showBase是 Base 的友元,不是 Derived 的友元。

七、final 类(最终类)

final是 C++11 引入的关键字,用于修饰类或虚函数,核心作用是禁止继承或重写

核心规则

  1. final 类:在类名后加final关键字,表示该类不能被任何类继承。
  2. final 虚函数:在虚函数声明后加final,表示该虚函数不能在派生类中被重写(覆盖)。

示例代码

#include <iostream>
using namespace std;

// final类:不能被继承
class FinalClass final {
public:
    void show() { cout << "这是final类的成员函数" << endl; }
};

// 错误:不能继承final类
// class Derived : public FinalClass {};

class Base {
public:
    virtual void func() final { // final虚函数:不能被重写
        cout << "Base::func() final函数" << endl;
    }
};

class Derived : public Base {
public:
    // 错误:不能重写final虚函数
    // void func() override { cout << "Derived::func()" << endl; }
};

int main() {
    FinalClass fc;
    fc.show();

    Base b;
    b.func();
    return 0;
}

关键说明

  • final 类通常用于设计不希望被扩展的类(比如工具类、封装好的底层类),防止错误继承破坏类的封装性。
  • final 虚函数用于锁定基类的某个行为,确保派生类不能修改该行为的实现。

更多推荐