CPP继承

继承

一、继承概述

1、为什么需要继承

如下示例,Person 类、Student 类、Teacher 类有大量重复的代码,造成代码冗余,降低开发效率。

我们可以通过继承来解决这一问题。在面向对象的编程语言中,继承是一个核心概念。主要作用将重复的代码统一定义在父类中,子类从父类继承,同时继承也是实现多态的重要条件。

2、什么是继承

继承就是一个新类从现有类派生的过程。新类称之为派生类或子类,原有的类称之为基类或父类;子类可以继承父类中的成员,从而可以提高代码的可重用性。

继承关系下,子类和父类存在 is a 的 关系。例如,狗是动物,猫是动物,老虎是一个动物等等。那么可以说动物类是一个父类,老虎、猫、狗都是动物类的子类。

在继承关系下父类更通用,子类更具体。也就是说父类拥有子类的共同特性,子类可以具备独有的特性。

二、继承的实现

C++ 中类实现继承的形式如下:

cpp 复制代码
class 派生类名:[继承方式]基类名 //默认是private继承方式
{
}

继承方式有 3 种类型,分别为共有型 (public),保护型 (protected) 和私有型 (private);: 表示基类和派生类之间的继承关系的符号。

示例:

Person 类

Person 类作为父类,其包含了 public 修饰的属性:

cpp 复制代码
#pragma once
#include <string>
class Person
{
public:
std::string name;
int age;
};

Student 类

继承了 Person 类,子类从父类继承 public 成员

Student.h

cpp 复制代码
#pragma once
#include "Person.h"
class Student: public Person
public:
void show();
};

Student.cpp

cpp 复制代码
#include "Student.h"
#include <iostream>
using namespace std;
void Student::show()
{
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}

Main.cpp

cpp 复制代码
#include <iostream>
#include "Student.h"
int main()
Student s;
s.name = "张三"; //从父类继承的成员
s.age = 20;
s.show();
}

三、派生类的访问控制

在 C++ 中,类成员的访问权限分为 public (公共)、protected (受保护) 或 private (私有) 3 种。其中父类的 public 和 protected 成员允许子类继承,private 成员不能被继承。

以 public 继承模式为例,访问控制权限如下:

访问 public protected private
同一个类 yes yes yes
派生类 yes yes no
外部的类 yes no no

类 A 的定义:

cpp 复制代码
#pragma once
class A
{
public:
int num_public; //公有的成员任何类都可以访问
protected:
int num_protected; //受保护的成员可以在当前类和子类中访问
private:
int num_private; //私有的成员只能在当前类中访问
};

类 B 继承于类 A:

cpp 复制代码
#pragma once
#include "A.h"
class B: public A
public:
B();
B(int a, int b);
void print();
};

类 B 中访问父类中的成员:

cpp 复制代码
#include "B.h"
#include <iostream>
using namespace std;
B::B() {}
B::B(int a, int b)
{
this->num_public = a;
this->num_protected = b;
}
void B::print()
{
cout << "public:" << num_public << endl;
cout << "protected:" << num_protected << endl;
//cout << "private:" << num_private << endl; //编译错误,私有成员,不能被子类继承
}

四、继承类型

C++ 支持三种继承类型,分别是 public、protected 及 private 类型,这些继承类型影响着基类成员在派生类中的访问权限。

1、访问权限变化总览
CPP 复制代码
基类成员权限            public继承         protected继承         private继承
---------------------------------------------------------------------------
public成员    ───►   public(外部可访问)  protected(外部不可)   private(外部不可)
protected成员 ───►   protected(外部不可) protected(外部不可)   private(外部不可)
private成员   ───►   不可访问               不可访问                不可访问
  • 继承方式只会影响基类 public / protected 成员在派生类中的可见性,不会影响派生类对自己新成员的访问控制。
  • private 成员无论哪种继承方式,子类都不能直接访问。

直观理解:

  • public继承:原汁原味 ------ public 还是 public,protected 还是 protected。
  • protected继承:降一级 ------ public 变 protected,protected 不变。
  • private继承:全收进屋 ------ public 和 protected 全变 private。
2、基类定义
CPP 复制代码
#pragma once
#include <iostream>
using namespace std;

class Base
{
public:
    void func_public() { cout << "Base::func_public()" << endl; }

protected:
    void func_protected() { cout << "Base::func_protected()" << endl; }

private:
    void func_private() { cout << "Base::func_private()" << endl; }
};
3、公有继承 (Public Inheritance)
规则
  • 基类 public 成员 → 派生类 public
  • 基类 protected 成员 → 派生类 protected
  • 外部依然可以访问继承的 public 成员
代码示例

SubPublic.h

cpp 复制代码
#pragma once
#include "Base.h"
class SubPublic : public Base
{
public:
    void func();
};

SubPublic.cpp

cpp 复制代码
#include "SubPublic.h"
#include <iostream>
using namespace std;

void SubPublic::func()
{
    cout << "[public继承] 子类内部可以访问父类 public + protected 成员" << endl;
    func_public();    // ✅
    func_protected(); // ✅
}

测试

cpp 复制代码
SubPublic pub;
pub.func();
pub.func_public(); // ✅ 外部可访问
4、私有继承 (Private Inheritance)
规则
  • 基类 public 成员 → 派生类 private
  • 基类 protected 成员 → 派生类 private
  • 外部无法访问这些继承的成员
代码示例

SubPrivate.h

cpp 复制代码
#pragma once
#include "Base.h"

class SubPrivate : private Base
{
public:
    void func();
};

SubPrivate.cpp

cpp 复制代码
#include "SubPrivate.h"
#include <iostream>
using namespace std;

void SubPrivate::func()
{
    cout << "[private继承] 子类内部可以访问父类 public + protected 成员" << endl;
    func_public();    // ✅
    func_protected(); // ✅
}

测试

cpp 复制代码
SubPrivate pri;
pri.func();
// pri.func_public(); // ❌ 外部不可访问
5、保护继承 (Protected Inheritance)
规则
  • 基类 public 成员 → 派生类 protected
  • 基类 protected 成员 → 派生类 protected
  • 外部无法直接访问,但派生类的子类可以访问
代码示例

SubProtected.h

cpp 复制代码
pragma once
#include "Base.h"

class SubProtected : protected Base
{
public:
    void func();
};

SubProtected.cpp

cpp 复制代码
#include "SubProtected.h"
#include <iostream>
using namespace std;

void SubProtected::func()
{
    cout << "[protected继承] 子类内部可以访问父类 public + protected 成员" << endl;
    func_public();    // ✅
    func_protected(); // ✅
}

6、保护继承的子类

Subclass.h

cpp 复制代码
#pragma once
#include "SubProtected.h"

class Subclass : public SubProtected
{
public:
    void test();
};

Subclass.cpp

cpp 复制代码
#include "Subclass.h"
#include <iostream>
using namespace std;

void Subclass::test()
{
    cout << "[保护继承的子类] 仍然可以访问父类的 public + protected 成员" << endl;
    func_public();    // ✅
    func_protected(); // ✅
}

测试

cpp 复制代码
Subclass subc;
subc.test();
// subc.func_public(); // ❌ 外部不可访问

五、继承中的构造函数与析构函数

基类中的构造函数、析构函数和拷贝构造函数不能被派生类继承。

1、构造函数和析构函数的执行顺序

继承关系下:

  • 当派生类对象被创建时,先调用基类的构造函数,然后再调用派生类的构造函数。
  • 析构时顺序相反,先调用派生类的析构函数,再调用基类的析构函数。
  • 基类的构造函数、析构函数以及拷贝构造函数不会被继承到派生类。
cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "A类构造函数" << endl;
    }
    ~A()
    {
        cout << "A类析构函数" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "B类构造函数" << endl;
    }
    ~B()
    {
        cout << "B类析构函数" << endl;
    }
};

class C : public B
{
public:
    C()
    {
        cout << "C类构造函数" << endl;
    }
    ~C()
    {
        cout << "C类析构函数" << endl;
    }
};

int main()
{
    C c;  // 创建C类对象
    return 0;
}

程序输出

cpp 复制代码
A类构造函数
B类构造函数
C类构造函数

C类析构函数   
B类析构函数
A类析构函数
  • 当你定义(创建)一个对象时,系统会自动调用该对象所属类的构造函数,用来完成对象的初始化。
  • 当对象的生命周期结束时,系统会自动调用对应类的析构函数,用来完成清理工作(比如释放内存、关闭文件等)。
2、子类中调用父类构造
  • 当基类只提供带参数的构造函数 且没有无参构造函数时,派生类必须在其初始化列表中显式调用基类的有参构造函数,否则编译会报错。
  • 如果基类有无参构造函数,则派生类会默认调用基类的无参构造函数。

基类 Person 示例

Person.h

cpp 复制代码
#pragma once
#include <string>

class Person
{
public:
    Person(std::string name);
    std::string getName();

private:
    std::string name;
};

Person.cpp

cpp 复制代码
#include "Person.h"

Person::Person(std::string name) : name(name)
{
}

std::string Person::getName()
{
    return name;
}

派生类 Student 示例

Student.h

cpp 复制代码
#pragma once
#include "Person.h"

class Student : public Person
{
public:
    Student();
    Student(std::string name);
};

Student.cpp

cpp 复制代码
#include "Student.h"

// 当基类无默认构造时,派生类必须显示调用基类有参构造函数
Student::Student() : Person("")
{
}

Student::Student(std::string name) : Person(name)
{
}

测试 main.cpp

cpp 复制代码
#include <iostream>
#include "Student.h"
using namespace std;

int main()
{
    Student s("张三");
    cout << s.getName() << endl;  // 输出:张三
    return 0;
}

Student::Student() : Person("") { }

这是 Student 的无参构造函数,写法表示:

  • 当创建 Student 对象时,先调用基类 Person 的构造函数,传入空字符串 "" 初始化 Person 部分。
  • 然后执行 Student 自己的构造函数体(这里为空)。

Student::Student(std::string name) : Person(name) { }

这是带参数的构造函数,表示:

  • 创建 Student 对象时,先调用基类 Person 的构造函数,传入参数 name
  • 然后执行 Student 自己的构造函数体(这里为空)。

如果基类没有无参构造函数,编译器就不知道用什么参数去初始化基类部分,编译会失败。 所以派生类构造函数中必须用初始化列表显示调用基类构造函数,告诉它该怎么初始化基类。

3、调用顺序原因
一、构造函数调用顺序:先基类后派生类
  • 当你创建一个派生类对象时,派生类通常会用到基类的成员(包括数据和方法)。
  • 如果基类还没初始化,派生类就无法安全使用基类的内容。
  • 所以必须先调用基类的构造函数,完成基类部分的初始化,再调用派生类构造函数来初始化派生类自己新增的成员。

这样做保证了派生类拥有一个"完整且有效"的基类部分,避免使用未初始化数据带来的错误。


二、析构函数调用顺序:先派生类后基类
  • 对象销毁时,派生类先清理自己新增的资源(比如动态申请的内存、打开的文件等)。
  • 清理完派生类资源后,再去销毁基类成员。
  • 如果先销毁基类,派生类成员还没清理完,就会出现访问已销毁资源的错误。

所以析构时先调用派生类析构函数释放派生类资源,再调用基类析构函数释放基类资源,符合"从内到外"的释放原则。

三、简单比喻 ---- 把对象想象成建房子

构造(建房子)

盖房子的时候,先打好地基(基类) ,确保基础稳固,

然后再盖楼层(派生类),一层一层往上建。

先有地基,楼层才能安全搭建。

析构(拆房子)

拆房子时,先拆楼层(派生类) ,再拆地基(基类),

这样避免楼层倒塌砸到地基,也保证拆除顺序安全有序。

相关推荐
咩咩大主教2 个月前
2025最新版使用VSCode和CMake图形化编译调试Cuda C++程序(保姆级教学)
c++·vscode·cmake·visual studio·cuda·cpp·cuda c++
Blue.ztl2 个月前
DP刷题练习(二)
算法·cpp
Rinai_R2 个月前
CS144 - LAB0
c语言·windows·计算机网络·cpp·计算机基础·cs144
东北马里奥4 个月前
if constexpr
cpp
阿猿收手吧!6 个月前
【MySQL】MySQL经典面试题深度解析
数据库·c++·mysql·cpp
加勒比之杰克7 个月前
【数据库初阶】Linux中库的基础操作
android·linux·数据库·mysql·增删改查·cpp·crud
talentestors8 个月前
Codeforces Round 992 (Div. 2) 解题报告
c++·c·题解·cpp
ao_lang9 个月前
剑指offer第五天
python·算法·cpp
习惯就好zz10 个月前
windows msvc2017 x64编译AWS SDK CPP库
sdk·aws·cpp·1024程序员节