CPP继承
继承
一、继承概述
1、为什么需要继承
如下示例,Person 类、Student 类、Teacher 类有大量重复的代码,造成代码冗余,降低开发效率。
我们可以通过继承来解决这一问题。在面向对象的编程语言中,继承是一个核心概念。主要作用将重复的代码统一定义在父类中,子类从父类继承,同时继承也是实现多态的重要条件。
2、什么是继承
继承就是一个新类从现有类派生的过程。新类称之为派生类或子类,原有的类称之为基类或父类;子类可以继承父类中的成员,从而可以提高代码的可重用性。
继承关系下,子类和父类存在 is a 的 关系。例如,狗是动物,猫是动物,老虎是一个动物等等。那么可以说动物类是一个父类,老虎、猫、狗都是动物类的子类。
在继承关系下父类更通用,子类更具体。也就是说父类拥有子类的共同特性,子类可以具备独有的特性。
二、继承的实现
C++ 中类实现继承的形式如下:
class 派生类名:[继承方式]基类名 //默认是private继承方式
{
}
继承方式有 3 种类型,分别为共有型 (public),保护型 (protected) 和私有型 (private);: 表示基类和派生类之间的继承关系的符号。
示例:
Person 类
Person 类作为父类,其包含了 public 修饰的属性:
#pragma once
#include <string>
class Person
{
public:
std::string name;
int age;
};
Student 类
继承了 Person 类,子类从父类继承 public 成员
Student.h
#pragma once
#include "Person.h"
class Student: public Person
public:
void show();
};
Student.cpp
#include "Student.h"
#include <iostream>
using namespace std;
void Student::show()
{
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
Main.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 的定义:
#pragma once
class A
{
public:
int num_public; //公有的成员任何类都可以访问
protected:
int num_protected; //受保护的成员可以在当前类和子类中访问
private:
int num_private; //私有的成员只能在当前类中访问
};
类 B 继承于类 A:
#pragma once
#include "A.h"
class B: public A
public:
B();
B(int a, int b);
void print();
};
类 B 中访问父类中的成员:
#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、访问权限变化总览
基类成员权限 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、基类定义
#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
#pragma once
#include "Base.h"
class SubPublic : public Base
{
public:void func();
};
SubPublic.cpp
#include "SubPublic.h"
#include <iostream>
using namespace std;void SubPublic::func()
{cout << "[public继承] 子类内部可以访问父类 public + protected 成员" << endl;func_public(); // ✅func_protected(); // ✅
}
测试
SubPublic pub;
pub.func();
pub.func_public(); // ✅ 外部可访问
4、私有继承 (Private Inheritance)
规则
- 基类
public
成员 → 派生类private
- 基类
protected
成员 → 派生类private
- 外部无法访问这些继承的成员
代码示例
SubPrivate.h
#pragma once
#include "Base.h"class SubPrivate : private Base
{
public:void func();
};
SubPrivate.cpp
#include "SubPrivate.h"
#include <iostream>
using namespace std;void SubPrivate::func()
{cout << "[private继承] 子类内部可以访问父类 public + protected 成员" << endl;func_public(); // ✅func_protected(); // ✅
}
测试
SubPrivate pri;
pri.func();
// pri.func_public(); // ❌ 外部不可访问
5、保护继承 (Protected Inheritance)
规则
- 基类
public
成员 → 派生类protected
- 基类
protected
成员 → 派生类protected
- 外部无法直接访问,但派生类的子类可以访问
代码示例
SubProtected.h
pragma once
#include "Base.h"class SubProtected : protected Base
{
public:void func();
};
SubProtected.cpp
#include "SubProtected.h"
#include <iostream>
using namespace std;void SubProtected::func()
{cout << "[protected继承] 子类内部可以访问父类 public + protected 成员" << endl;func_public(); // ✅func_protected(); // ✅
}
6、保护继承的子类
Subclass.h
#pragma once
#include "SubProtected.h"class Subclass : public SubProtected
{
public:void test();
};
Subclass.cpp
#include "Subclass.h"
#include <iostream>
using namespace std;void Subclass::test()
{cout << "[保护继承的子类] 仍然可以访问父类的 public + protected 成员" << endl;func_public(); // ✅func_protected(); // ✅
}
测试
Subclass subc;
subc.test();
// subc.func_public(); // ❌ 外部不可访问
五、继承中的构造函数与析构函数
基类中的构造函数、析构函数和拷贝构造函数不能被派生类继承。
1、构造函数和析构函数的执行顺序
继承关系下:
- 当派生类对象被创建时,先调用基类的构造函数,然后再调用派生类的构造函数。
- 析构时顺序相反,先调用派生类的析构函数,再调用基类的析构函数。
- 基类的构造函数、析构函数以及拷贝构造函数不会被继承到派生类。
#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;
}
程序输出
A类构造函数
B类构造函数
C类构造函数C类析构函数
B类析构函数
A类析构函数
- 当你定义(创建)一个对象时,系统会自动调用该对象所属类的构造函数,用来完成对象的初始化。
- 当对象的生命周期结束时,系统会自动调用对应类的析构函数,用来完成清理工作(比如释放内存、关闭文件等)。
2、子类中调用父类构造
- 当基类只提供带参数的构造函数且没有无参构造函数时,派生类必须在其初始化列表中显式调用基类的有参构造函数,否则编译会报错。
- 如果基类有无参构造函数,则派生类会默认调用基类的无参构造函数。
基类 Person
示例
Person.h
#pragma once
#include <string>class Person
{
public:Person(std::string name);std::string getName();private:std::string name;
};
Person.cpp
#include "Person.h"Person::Person(std::string name) : name(name)
{
}std::string Person::getName()
{return name;
}
派生类 Student
示例
Student.h
#pragma once
#include "Person.h"class Student : public Person
{
public:Student();Student(std::string name);
};
Student.cpp
#include "Student.h"// 当基类无默认构造时,派生类必须显示调用基类有参构造函数
Student::Student() : Person("")
{
}Student::Student(std::string name) : Person(name)
{
}
测试 main.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、调用顺序原因
一、构造函数调用顺序:先基类后派生类
- 当你创建一个派生类对象时,派生类通常会用到基类的成员(包括数据和方法)。
- 如果基类还没初始化,派生类就无法安全使用基类的内容。
- 所以必须先调用基类的构造函数,完成基类部分的初始化,再调用派生类构造函数来初始化派生类自己新增的成员。
这样做保证了派生类拥有一个“完整且有效”的基类部分,避免使用未初始化数据带来的错误。
二、析构函数调用顺序:先派生类后基类
- 对象销毁时,派生类先清理自己新增的资源(比如动态申请的内存、打开的文件等)。
- 清理完派生类资源后,再去销毁基类成员。
- 如果先销毁基类,派生类成员还没清理完,就会出现访问已销毁资源的错误。
所以析构时先调用派生类析构函数释放派生类资源,再调用基类析构函数释放基类资源,符合“从内到外”的释放原则。
三、简单比喻 ---- 把对象想象成建房子
构造(建房子):
盖房子的时候,先打好地基(基类),确保基础稳固,
然后再盖楼层(派生类),一层一层往上建。
先有地基,楼层才能安全搭建。
析构(拆房子):
拆房子时,先拆楼层(派生类),再拆地基(基类),
这样避免楼层倒塌砸到地基,也保证拆除顺序安全有序。