欢迎光临
我们一直在努力

C++ 继承:子类与父类的继承关系

C++ 继承:子类与父类的继承关系

在C++面向对象编程中,我们已经学习了类的基本结构、构造函数、析构函数、this指针、静态成员,以及友元函数与友元类。上一篇博客中我们重点强调:友元机制可以突破类的访问权限限制,允许指定的外部函数、其他类直接访问当前类的私有成员,但其核心是“补充封装”而非“否定封装”,且友元关系具有单向性、无传递性、不可继承的特点。

在实际开发中,我们常常会遇到这样的场景:多个类之间存在大量重复的成员(变量/函数),比如“学生类(Student)”“教师类(Teacher)”“管理员类(Admin)”,它们都拥有“姓名、年龄、性别”等共同属性,以及“显示个人信息”等共同行为。如果为每个类都重复定义这些相同的成员,会导致代码冗余、维护成本增高——修改一个共同属性时,需要修改所有相关类的代码,极易出错。

为了解决这个问题,C++提供了面向对象编程的三大核心特性之一——继承(Inheritance)。继承的核心思想是“代码复用、层次化设计”:通过定义一个“父类(基类,Base Class)”,将所有相关类的共同成员(属性、行为)封装在父类中,然后让其他类(子类,派生类,Derived Class)“继承”父类的成员,子类无需重复定义这些共同成员,只需专注于实现自己独有的属性和行为即可。

简单来说,继承就是“子类继承父类的家产,同时拥有自己的专属财产”——父类是所有相关子类的“共性抽象”,子类是父类的“具体实现或扩展”。比如,我们可以定义一个“人员类(Person)”作为父类,封装“姓名、年龄、性别”等共性属性和“显示信息”等共性行为,然后让Student、Teacher、Admin作为子类,继承Person类的所有成员,再分别添加自己独有的成员(如学生的“学号、成绩”,教师的“工号、职称”)。

很多初学者在学习继承时,都会陷入三个核心误区:一是混淆继承的访问控制规则(子类能访问父类的哪些成员),二是不懂子类与父类的构造/析构函数执行顺序,三是滥用继承、违背“is-a”的继承原则(比如让“汽车类”继承“动物类”)。本文将从实际场景切入,拆解继承的本质、子类与父类的关系、继承的访问控制、构造/析构顺序、继承的类型,结合实战案例规避高频误区,同时衔接前文知识点,帮你彻底搞懂继承的核心价值、使用场景和注意事项,完善C++类与对象的知识体系,为后续学习多态、虚函数打下坚实基础。

核心前提回顾:类的封装性通过public、private、protected访问权限实现;友元可以突破访问权限,但不可继承;this指针指向当前对象,静态成员属于类而非对象——这些是理解继承中“成员访问、子类与父类交互”的关键。

一、先明确:为什么需要继承?(解决代码冗余)

在讲解继承的具体用法之前,我们先通过一个简单的实际场景,对比“无继承”和“有继承”的代码差异,搞清楚继承的核心需求:没有继承,我们会遇到什么问题?继承能帮我们解决什么?

场景:定义Student类和Teacher类,两个类都拥有“姓名(name)、年龄(age)”两个属性,以及“显示个人信息(showInfo)”的行为;同时,Student类独有“学号(studentId)、成绩(score)”属性和“显示成绩(showScore)”行为,Teacher类独有“工号(teacherId)、职称(title)”属性和“显示职称(showTitle)”行为。

1. 无继承实现:代码冗余严重

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

// 学生类:重复定义name、age、showInfo
class Student {
public:
// 构造函数:重复初始化name、age
Student(string name, int age, string studentId, int score) {
this->name = name;
this->age = age;
this->studentId = studentId;
this->score = score;
}

// 显示个人信息:重复实现
void showInfo() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}

// 学生独有:显示成绩
void showScore() {
cout << "学号:" << studentId << ",成绩:" << score << endl;
}

private:
// 重复成员:name、age
string name;
int age;
// 学生独有成员
string studentId;
int score;
};

// 教师类:重复定义name、age、showInfo,与Student类大量冗余
class Teacher {
public:
// 构造函数:重复初始化name、age
Teacher(string name, int age, string teacherId, string title) {
this->name = name;
this->age = age;
this->teacherId = teacherId;
this->title = title;
}

// 显示个人信息:重复实现,与Student类的showInfo逻辑完全一致
void showInfo() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}

// 教师独有:显示职称
void showTitle() {
cout << "工号:" << teacherId << ",职称:" << title << endl;
}

private:
// 重复成员:name、age(与Student类完全一致)
string name;
int age;
// 教师独有成员
string teacherId;
string title;
};

int main() {
Student s("张三", 18, "2024001", 95);
Teacher t("李老师", 35, "T2024001", "副教授");

s.showInfo();
s.showScore();
t.showInfo();
t.showTitle();

return 0;
}

运行结果(正常):

姓名:张三,年龄:18
学号:2024001,成绩:95
姓名:李老师,年龄:35
工号:T2024001,职称:副教授

问题分析(核心痛点):

  • 代码冗余:Student和Teacher类都重复定义了name、age两个属性,以及showInfo()函数,代码重复率高——如果有更多类(如Admin、Doctor),重复代码会更多;

  • 维护成本高:如果需要修改showInfo()的逻辑(比如增加“性别”显示),或者修改name的类型(比如从string改为char[]),需要修改所有相关类的代码,极易遗漏、出错;

  • 扩展性差:新增一个“管理员类(Admin)”时,需要重新编写所有重复成员,无法复用已有代码,开发效率低。

  • 2. 有继承实现:代码复用,简洁高效

    我们定义一个父类(Person),将Student和Teacher类的共同成员(name、age、showInfo())封装在父类中,然后让Student和Teacher类继承Person类,只需专注于实现自己的独有成员即可。

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

    // 父类(基类):Person,封装所有子类的共同成员
    class Person {
    public:
    // 父类构造函数:初始化共同成员name、age
    Person(string name, int age) {
    this->name = name;
    this->age = age;
    }

    // 父类成员函数:共同行为showInfo
    void showInfo() {
    cout << "姓名:" << name << ",年龄:" << age << endl;
    }

    protected:
    // 注意:此处用protected而非private(后续讲解原因)
    string name; // 共同属性:姓名
    int age; // 共同属性:年龄
    };

    // 子类(派生类):Student,继承Person类
    // 语法:class 子类名 : 继承方式 父类名
    class Student : public Person {
    public:
    // 子类构造函数:初始化父类成员 + 自身独有成员
    Student(string name, int age, string studentId, int score)
    : Person(name, age) { // 调用父类构造函数,初始化name、age
    this->studentId = studentId;
    this->score = score;
    }

    // 子类独有成员函数:显示成绩
    void showScore() {
    cout << "学号:" << studentId << ",成绩:" << score << endl;
    // 子类可访问父类的protected成员name、age
    cout << "学生" << name << "的年龄:" << age << endl;
    }

    private:
    // 子类独有成员:学号、成绩
    string studentId;
    int score;
    };

    // 子类(派生类):Teacher,继承Person类
    class Teacher : public Person {
    public:
    // 子类构造函数:调用父类构造函数,初始化自身独有成员
    Teacher(string name, int age, string teacherId, string title)
    : Person(name, age) {
    this->teacherId = teacherId;
    this->title = title;
    }

    // 子类独有成员函数:显示职称
    void showTitle() {
    cout << "工号:" << teacherId << ",职称:" << title << endl;
    // 子类可访问父类的protected成员name、age
    cout << "教师" << name << "的年龄:" << age << endl;
    }

    private:
    // 子类独有成员:工号、职称
    string teacherId;
    string title;
    };

    int main() {
    Student s("张三", 18, "2024001", 95);
    Teacher t("李老师", 35, "T2024001", "副教授");

    // 子类对象可直接调用父类的成员函数showInfo(继承而来)
    s.showInfo();
    s.showScore();
    t.showInfo();
    t.showTitle();

    return 0;
    }

    运行结果(与无继承一致):

    姓名:张三,年龄:18
    学号:2024001,成绩:95
    学生张三的年龄:18
    姓名:李老师,年龄:35
    工号:T2024001,职称:副教授
    教师李老师的年龄:35

    优势分析(继承的核心价值):

  • 代码复用:父类的共同成员(name、age、showInfo())只需定义一次,所有子类均可继承使用,彻底消除代码冗余;

  • 维护便捷:修改共同成员时,只需修改父类的代码,所有子类会自动同步更新,无需逐个修改,降低维护成本;

  • 扩展性强:新增子类(如Admin)时,只需继承父类,再添加自身独有成员即可,无需重复编写共同代码,提升开发效率;

  • 层次清晰:通过继承构建“父类-子类”的层次结构(Person → Student/Teacher),符合现实世界的逻辑,让代码结构更清晰、易懂。

  • 这里有一个关键细节:父类的name、age属性我们用了protected访问权限,而非private——如果用private,子类将无法直接访问这两个成员(即使继承了父类)。这涉及到继承的访问控制规则,我们后续会详细讲解。

    二、核心概念:父类与子类的继承关系本质

    继承的本质是“子类复用父类的成员,同时可以扩展自身的成员”,子类与父类之间是**“is-a”(是一个)**的关系——这是继承的核心原则,也是判断是否应该使用继承的关键。

    比如:

    • Student(学生)是一个Person(人员),因此Student可以继承Person;

    • Teacher(教师)是一个Person(人员),因此Teacher可以继承Person;

    • Car(汽车)不是一个Animal(动物),因此Car不能继承Animal(违背is-a原则,属于滥用继承)。

    1. 父类与子类的核心关系(必记)

  • 子类继承父类的所有成员:子类会继承父类的所有成员变量和成员函数(包括public、private、protected),但继承后子类能否访问这些成员,取决于“继承方式”和“父类成员的访问权限”(后续详解);

  • 子类可以扩展自身成员:子类在继承父类成员的基础上,可以添加自己独有的成员变量和成员函数,实现与父类的差异化;

  • 子类可以重写父类成员:子类可以定义与父类同名的成员函数(重写,后续讲解多态时重点说明),覆盖父类的原有实现,实现“子类特有的行为”;

  • 父类不能访问子类成员:继承是“单向的”——子类可以访问父类的可访问成员,但父类无法访问子类的任何成员(包括独有成员和继承的成员);

  • 子类对象包含父类部分:每个子类对象的内存中,都会包含一个“父类对象”的部分(用于存储父类的成员),子类对象可以看作是“父类对象 + 子类独有成员”的组合。

  • 2. 继承的基本语法(牢记)

    C++中继承的基本语法格式如下(重点关注“继承方式”):

    // 父类(基类)定义
    class 父类名 {
    // 父类成员(public、private、protected)
    };

    // 子类(派生类)定义:继承父类
    class 子类名 : 继承方式 父类名 {
    // 子类成员(独有成员 + 重写成员)
    };

    // 示例(public继承,最常用)
    class Student : public Person {
    // 子类成员
    };

    关键说明:

  • 继承方式:C++提供三种继承方式,分别是public(公有继承)、protected(保护继承)、private(私有继承),默认继承方式是private(如果不写继承方式,默认是private继承);

  • 继承方式的作用:决定了“父类成员在子类中的访问权限”——父类的public、private、protected成员,继承到子类后,访问权限会根据继承方式发生变化;

  • 多父类继承:C++支持多继承(子类继承多个父类),语法为class 子类名 : 继承方式1 父类名1, 继承方式2 父类名2 {},但多继承容易引发歧义(如菱形继承),实际开发中尽量避免,本文重点讲解单继承(子类只继承一个父类)。

  • 三、核心重点:继承的访问控制规则(必懂、必记)

    继承的访问控制规则,是初学者最容易混淆的知识点——核心是“子类能否访问父类的成员,取决于两个因素:父类成员的访问权限 + 子类的继承方式”。

    我们先明确三个核心前提:

  • 父类的private成员:无论子类采用哪种继承方式,父类的private成员子类都无法直接访问(子类继承了该成员,但被隐藏,无法直接使用);

  • 父类的public/protected成员:子类能否访问、访问权限如何,由继承方式决定;

  • 访问权限优先级:private < protected < public(private权限最低,只能类内访问;public权限最高,类内、类外均可访问;protected权限介于两者之间,类内、子类可访问,类外不可访问)。

  • 1. 三种继承方式的访问控制对比(表格清晰呈现)

    假设父类中存在public、protected、private三种权限的成员,子类采用不同继承方式时,父类成员在子类中的访问权限如下(重点记忆public继承,实际开发中最常用):

    父类成员的访问权限public(公有继承)(最常用)protected(保护继承)private(私有继承)
    public(父类公有成员) 子类中为public(子类内、子类外均可访问) 子类中为protected(子类内可访问,子类外不可访问) 子类中为private(仅子类内可访问)
    protected(父类保护成员) 子类中为protected(子类内可访问,子类外不可访问) 子类中为protected(子类内可访问,子类外不可访问) 子类中为private(仅子类内可访问)
    private(父类私有成员) 子类中不可访问(继承但隐藏) 子类中不可访问(继承但隐藏) 子类中不可访问(继承但隐藏)

    2. 三种继承方式的实战演示(结合前文案例)

    我们以“Person父类 + Student子类”为例,分别演示三种继承方式的访问控制效果,重点理解“子类能否访问父类成员”“子类外能否访问子类继承的成员”。

    (1)public继承(最常用,推荐)

    public继承的核心特点:父类的public成员 → 子类public成员;父类的protected成员 → 子类protected成员;父类private成员 → 子类不可访问。

    适合场景:希望子类继承父类的公开接口(public成员函数),同时子类可以访问父类的保护成员(protected),类外可以通过子类对象访问父类的public成员。

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

    class Person {
    public:
    void showInfo() { // 父类public成员函数
    cout << "姓名:" << name << ",年龄:" << age << endl;
    }

    protected:
    string name; // 父类protected成员
    int age; // 父类protected成员

    private:
    string gender; // 父类private成员,子类不可访问
    };

    // public继承(最常用)
    class Student : public Person {
    public:
    Student(string name, int age, string studentId)
    : Person() {
    this->name = name; // 正确:子类可访问父类protected成员name
    this->age = age; // 正确:子类可访问父类protected成员age
    // this->gender = "男"; // 错误:父类private成员,子类不可访问
    this->studentId = studentId;
    }

    void showStudentInfo() {
    showInfo(); // 正确:子类可访问父类public成员函数
    cout << "学号:" << studentId << endl;
    }

    private:
    string studentId;
    };

    int main() {
    Student s("张三", 18, "2024001");
    s.showInfo(); // 正确:类外可访问子类的public成员(继承自父类)
    s.showStudentInfo(); // 正确:类外可访问子类public成员
    // cout << s.name; // 错误:name在子类中是protected成员,类外不可访问
    return 0;
    }

    (2)protected继承

    protected继承的核心特点:父类的public成员 → 子类protected成员;父类的protected成员 → 子类protected成员;父类private成员 → 子类不可访问。

    适合场景:不希望类外通过子类对象访问父类的public成员,只允许子类内部访问父类的public/protected成员,或子类的子类(孙子类)访问。

    // 父类Person与上面一致
    class Student : protected Person { // protected继承
    public:
    Student(string name, int age, string studentId)
    : Person() {
    this->name = name; // 正确:子类可访问父类protected成员
    this->age = age; // 正确:子类可访问父类protected成员
    this->studentId = studentId;
    }

    void showStudentInfo() {
    showInfo(); // 正确:子类可访问父类public成员(继承后为protected)
    cout << "学号:" << studentId << endl;
    }

    private:
    string studentId;
    };

    int main() {
    Student s("张三", 18, "2024001");
    // s.showInfo(); // 错误:父类public成员继承后为子类protected成员,类外不可访问
    s.showStudentInfo(); // 正确:子类自身的public成员,类外可访问
    return 0;
    }

    (3)private继承

    private继承的核心特点:父类的public/protected成员 → 子类private成员;父类private成员 → 子类不可访问。

    适合场景:仅希望子类内部复用父类的成员,不希望子类的子类(孙子类)访问父类成员,也不希望类外访问父类的任何成员(通过子类对象)。

    // 父类Person与上面一致
    class Student : private Person { // private继承
    public:
    Student(string name, int age, string studentId)
    : Person() {
    this->name = name; // 正确:子类可访问父类protected成员(继承后为private)
    this->age = age; // 正确:子类可访问父类protected成员(继承后为private)
    this->studentId = studentId;
    }

    void showStudentInfo() {
    showInfo(); // 正确:子类可访问父类public成员(继承后为private)
    cout << "学号:" << studentId << endl;
    }

    private:
    string studentId;
    };

    // 孙子类:继承Student类
    class GraduateStudent : public Student {
    public:
    GraduateStudent(string name, int age, string studentId, string major)
    : Student(name, age, studentId) {
    this->major = major;
    }

    void showGraduateInfo() {
    // showInfo(); // 错误:父类Student的showInfo是private成员(继承自Person),孙子类不可访问
    // this->name = "李四"; // 错误:name是Student的private成员,孙子类不可访问
    cout << "专业:" << major << endl;
    }

    private:
    string major;
    };

    int main() {
    Student s("张三", 18, "2024001");
    // s.showInfo(); // 错误:父类public成员继承后为子类private成员,类外不可访问
    s.showStudentInfo(); // 正确:子类自身的public成员
    return 0;
    }

    3. 关键提醒(易错点)

  • 父类private成员的继承:子类确实继承了父类的private成员(占用子类对象的内存),但子类无法直接访问——如果子类需要访问父类的private成员,只能通过父类提供的public/protected接口(成员函数)间接访问;
  • 示例:父类private成员的间接访问

    class Person {
    public:
    // 父类public接口:间接访问private成员gender
    void setGender(string gender) {
    this->gender = gender;
    }

    string getGender() {
    return gender;
    }

    private:
    string gender; // 父类private成员
    };

    class Student : public Person {
    public:
    void showGender() {
    // cout << gender; // 错误:直接访问private成员
    cout << getGender(); // 正确:通过父类public接口间接访问
    }
    };

    int main() {
    Student s;
    s.setGender("男");
    s.showGender(); // 输出:男
    return 0;
    }

  • 继承方式与访问权限的关系:继承方式只影响“父类成员在子类中的访问权限”,不影响“父类自身的成员访问权限”——父类的成员在父类内部,依然遵循自身的访问权限规则;

  • 实际开发推荐:优先使用public继承(代码可读性高、扩展性好),尽量避免private/protected继承(容易导致代码晦涩、扩展性差)。

  • 四、重点难点:子类与父类的构造函数、析构函数执行顺序

    当子类继承父类后,子类对象的创建和销毁过程,会涉及父类和子类的构造函数、析构函数的调用——这是继承中的重点难点,也是笔试高频考点,核心规则是:先构造父类,再构造子类;先析构子类,再析构父类。

    为什么是这个顺序?类比现实世界:“先有父母,再有子女”(构造顺序);“子女先去世,父母后去世”(析构顺序)——子类对象依赖父类的成员,必须先初始化父类成员,才能初始化子类独有成员;销毁时,必须先销毁子类独有成员,再销毁父类成员(避免子类成员依赖父类成员时,父类先销毁导致的错误)。

    1. 构造函数执行顺序(实战演示)

    核心规则:

  • 创建子类对象时,先调用父类的构造函数(初始化父类成员);

  • 父类构造函数执行完毕后,再调用子类的构造函数(初始化子类独有成员);

  • 如果父类有默认构造函数(无参构造),子类构造函数可以不显式调用父类构造函数(编译器会自动调用);如果父类没有默认构造函数(只有有参构造),子类构造函数必须显式调用父类构造函数(否则编译报错)。

  • #include <iostream>
    using namespace std;

    // 父类:Person
    class Person {
    public:
    // 父类无参构造函数
    Person() {
    cout << "Person(父类)无参构造函数调用" << endl;
    }

    // 父类有参构造函数
    Person(string name, int age) {
    this->name = name;
    this->age = age;
    cout << "Person(父类)有参构造函数调用:" << name << endl;
    }

    private:
    string name;
    int age;
    };

    // 子类:Student,public继承Person
    class Student : public Person {
    public:
    // 子类无参构造函数:编译器自动调用父类无参构造函数
    Student() {
    cout << "Student(子类)无参构造函数调用" << endl;
    }

    // 子类有参构造函数:显式调用父类有参构造函数(语法:父类名(参数))
    Student(string name, int age, string studentId)
    : Person(name, age) { // 显式调用父类有参构造
    this->studentId = studentId;
    cout << "Student(子类)有参构造函数调用:" << studentId << endl;
    }

    private:
    string studentId;
    };

    int main() {
    cout << "创建子类无参对象:" << endl;
    Student s1; // 先调用父类无参构造,再调用子类无参构造

    cout << "\\n创建子类有参对象:" << endl;
    Student s2("张三", 18, "2024001"); // 先调用父类有参构造,再调用子类有参构造

    return 0;
    }

    运行结果(清晰看到构造顺序):

    创建子类无参对象:
    Person(父类)无参构造函数调用
    Student(子类)无参构造函数调用

    创建子类有参对象:
    Person(父类)有参构造函数调用:张三
    Student(子类)有参构造函数调用:2024001

    2. 析构函数执行顺序(实战演示)

    核心规则:

  • 销毁子类对象时,先调用子类的析构函数(销毁子类独有成员);

  • 子类析构函数执行完毕后,再调用父类的析构函数(销毁父类成员);

  • 析构函数的调用是自动的(编译器自动调用),无需手动调用;

  • 重点(后续多态铺垫):如果父类的析构函数不是虚函数,子类析构函数不会覆盖父类析构函数,可能导致内存泄漏(后续讲解多态时详细说明)。

  • #include <iostream>
    using namespace std;

    class Person {
    public:
    Person() {
    cout << "Person(父类)无参构造调用" << endl;
    }

    // 父类析构函数
    ~Person() {
    cout << "Person(父类)析构函数调用" << endl;
    }
    };

    class Student : public Person {
    public:
    Student() {
    cout << "Student(子类)无参构造调用" << endl;
    }

    // 子类析构函数
    ~Student() {
    cout << "Student(子类)析构函数调用" << endl;
    }
    };

    int main() {
    // 创建子类对象(栈区对象,生命周期结束时自动销毁)
    Student s;
    cout << "子类对象使用中…" << endl;
    // 主函数结束,子类对象销毁,自动调用析构函数
    return 0;
    }

    运行结果(清晰看到析构顺序):

    Person(父类)无参构造调用
    Student(子类)无参构造调用
    子类对象使用中…
    Student(子类)析构函数调用
    Person(父类)析构函数调用

    3. 高频易错点(笔试重点)

    易错点1:子类构造函数未显式调用父类有参构造,导致编译报错

    class Person {
    public:
    // 父类只有有参构造,没有无参构造
    Person(string name) {
    this->name = name;
    }

    private:
    string name;
    };

    class Student : public Person {
    public:
    Student(string name, string studentId) {
    // 错误:父类没有无参构造,子类构造函数必须显式调用父类有参构造
    this->studentId = studentId;
    }

    private:
    string studentId;
    };

    // 正确写法:显式调用父类有参构造
    class Student : public Person {
    public:
    Student(string name, string studentId)
    : Person(name) { // 显式调用父类有参构造
    this->studentId = studentId;
    }
    };

    易错点2:认为析构函数执行顺序与构造函数一致(实际相反)

    牢记:构造顺序(父 → 子),析构顺序(子 → 父),避免笔试时写错执行顺序。

    五、继承的类型(单继承、多继承、菱形继承)

    根据子类继承父类的数量,C++中的继承分为三种类型:单继承、多继承、菱形继承(多继承的特殊情况)。实际开发中,优先使用单继承,尽量避免多继承和菱形继承(容易引发歧义、内存冗余)。

    1. 单继承(最常用)

    定义:子类只继承一个父类,是最简洁、最常用的继承方式,代码可读性高、无歧义。

    语法:class 子类名 : 继承方式 父类名 {}

    示例:Student继承Person、Teacher继承Person,均为单继承(前文所有案例均为单继承)。

    2. 多继承(不推荐)

    定义:子类继承多个父类,可以复用多个父类的成员,但容易引发“成员名歧义”(多个父类有同名成员时,子类无法区分)。

    语法:class 子类名 : 继承方式1 父类名1, 继承方式2 父类名2, … {}

    示例(多继承及歧义解决):

    #include <iostream>
    using namespace std;

    // 父类1:Person(有showInfo成员函数)
    class Person {
    public:
    void showInfo() {
    cout << "Person类的showInfo" << endl;
    }
    };

    // 父类2:Animal(有showInfo成员函数,与Person类同名)
    class Animal {
    public:
    void showInfo() {
    cout << "Animal类的showInfo" << endl;
    }
    };

    // 子类:Student,多继承Person和Animal(两个父类有同名成员showInfo)
    class Student : public Person, public Animal {
    public:
    void study() {
    cout << "学生学习" << endl;
    }
    };

    int main() {
    Student s;
    // s.showInfo(); // 错误:歧义,无法确定调用Person还是Animal的showInfo

    // 解决歧义:指定类域(父类名::成员函数)
    s.Person::showInfo(); // 正确:调用Person类的showInfo
    s.Animal::showInfo(); // 正确:调用Animal类的showInfo
    return 0;
    }

    问题分析:多继承的核心问题是“成员名歧义”和“菱形继承的二义性”,即使解决了歧义,也会导致代码晦涩、维护成本高,因此实际开发中尽量避免使用多继承。

    3. 菱形继承(多继承的特殊情况,极力避免)

    定义:子类继承两个父类,而这两个父类又继承自同一个祖父类——形成“菱形”结构,会导致祖父类的成员被继承两次(内存冗余),且访问时存在歧义。

    结构示意:祖父类(A)→ 父类1(B)、父类2(C)→ 子类(D)(D继承B和C,B和C都继承A)

    #include <iostream>
    using namespace std;

    // 祖父类:A
    class A {
    public:
    int a; // 祖父类成员
    };

    // 父类1:B,继承A
    class B : public A {
    public:
    int b;
    };

    // 父类2:C,继承A
    class C : public A {
    public:
    int c;
    };

    // 子类:D,继承B和C(菱形继承)
    class D : public B, public C {
    public:
    int d;
    };

    int main() {
    D d;
    // d.a = 10; // 错误:歧义,d的内存中有两个a(来自B和C),无法确定访问哪个

    // 解决歧义:指定类域,但依然存在内存冗余(两个a占用不同内存)
    d.B::a = 10;
    d.C::a = 20;
    cout << d.B::a << "," << d.C::a << endl; // 输出:10,20

    return 0;
    }

    问题解决:可以使用“虚继承(virtual)”解决菱形继承的二义性和内存冗余(虚继承会让祖父类的成员只被继承一次),但虚继承语法复杂、底层实现繁琐,进一步增加了代码复杂度,因此最好的方式是“避免菱形继承”,通过单继承或组合替代。

    六、继承的高频误区(必避,结合前文知识点)

    结合初学者的常见错误,聚焦继承的“访问控制、构造/析构顺序、继承原则”,总结6个高频坑,每个坑对应错误示例和正确写法,帮你少走弯路,规避编译错误和逻辑问题。

    误区1:子类可以访问父类的所有成员(逻辑错误)

    class Person {
    private:
    string name; // 父类private成员

    public:
    Person(string name) { this->name = name; }
    };

    class Student : public Person {
    public:
    void showName() {
    cout << name; // 错误:父类private成员,子类不可直接访问
    }
    };

    // 正确写法:通过父类public接口间接访问
    class Person {
    private:
    string name;

    public:
    Person(string name) { this->name = name; }
    string getName() { return name; } // 父类public接口
    };

    class Student : public Person {
    public:
    void showName() {
    cout << getName(); // 正确:通过接口间接访问
    }
    };

    误区2:继承方式不影响子类访问父类成员(编译错误)

    class Person {
    public:
    void showInfo() {}
    };

    class Student : private Person { // private继承
    public:
    void test() {
    showInfo(); // 正确:子类内可访问父类public成员(继承后为private)
    }
    };

    int main() {
    Student s;
    s.showInfo(); // 错误:private继承,父类public成员在子类中为private,类外不可访问
    return 0;
    }

    误区3:子类构造函数不调用父类构造函数(编译错误)

    class Person {
    public:
    // 父类只有有参构造,无无参构造
    Person(string name) { this->name = name; }

    private:
    string name;
    };

    class Student : public Person {
    public:
    Student(string name, string studentId) {
    // 错误:未显式调用父类有参构造,编译器会尝试调用父类无参构造(不存在)
    this->studentId = studentId;
    }

    private:
    string studentId;
    };

    // 正确写法:显式调用父类有参构造
    class Student : public Person {
    public:
    Student(string name, string studentId)
    : Person(name) {
    this->studentId = studentId;
    }
    };

    误区4:析构函数执行顺序与构造函数一致(逻辑错误)

    错误认知:构造顺序是父→子,析构顺序也是父→子;

    正确认知:构造顺序(父→子),析构顺序(子→父)——前文实战演示已验证,牢记这个顺序,避免笔试出错。

    误区5:滥用继承,违背“is-a”原则(设计错误)

    // 错误示例:违背is-a原则(Car不是Animal)
    class Animal {
    public:
    void eat() {}
    };

    class Car : public Animal { // 错误:Car不是Animal,不该继承Animal
    public:
    void run() {}
    };

    // 正确设计:不使用继承,使用组合(Car包含Engine类,而非继承Animal)
    class Engine {
    public:
    void start() {}
    };

    class Car {
    private:
    Engine engine; // 组合:Car包含Engine,而非继承

    public:
    void run() {
    engine.start(); // 调用Engine的方法
    }
    };

    关键提醒:继承的核心是“is-a”关系,组合的核心是“has-a”(有一个)关系——如果两个类不是“is-a”关系,应使用组合而非继承。

    误区6:子类可以重写父类的private成员函数(逻辑错误)

    class Person {
    private:
    // 父类private成员函数
    void showInfo() {
    cout << "Person的showInfo" << endl;
    }
    };

    class Student : public Person {
    public:
    // 错误:父类private成员函数,子类无法访问,更无法重写
    void showInfo() {
    cout << "Student的showInfo" << endl;
    }
    };

    // 正确写法:父类成员函数改为public/protected,子类才能重写
    class Person {
    protected:
    void showInfo() {
    cout << "Person的showInfo" << endl;
    }
    };

    class Student : public Person {
    public:
    // 正确:重写父类protected成员函数
    void showInfo() {
    cout << "Student的showInfo" << endl;
    }
    };

    赞(0)
    未经允许不得转载:171主机测评 » C++ 继承:子类与父类的继承关系
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址