继承与派生
目录
基类和派生类
基本概念
定义新类B时B拥有某个已有的类A的全部特点(成员)
称A-基类(父类),B-派生类(子类)
- 派生类是对基类进行修改和扩充得到的
- 扩充:添加新成员
- 修改:重新编写继承来的成员
派生类一经定义,可以独立使用,不依赖于基类
内存空间:基类对象大小+派生类对象自己的成员变量大小(基类对象存储位置在新增之前)

在派生类中访问基类同名成员(叫“隐藏”或“覆盖”)利用
基类::变量名访问,缺省情况下访问的是派生类的成员
范式
class 派生类名 : 派生说明方式符 基类名{
...
}
派生类的各个成员函数不得访问基类中的private成员
派生说明方式符:public/private/protected
场景
学生(基类)→中学生/大学生/小鞋生/研究生
共同属性:姓名、学号、性别、成绩
特有:大学专业,研究生导师,中学生竞赛
例子
class CStudent{
private:
string sName;
int nAge;
public:
bool IsThreeGood(){}
void SetName(const string &name){
sName=name;
}
// ...
};
class CUndergraduateStudent : public CStudent{
private:
int nDepartment;
public:
bool IsThreeGood(){...} //派生类同名函数将隐藏基类函数(不建议)
bool Membergroup(){...}
};
class CGraduateStudent : public CStudent{
private:
int nDepartment;
char SupervisorName[20];
public:
int CountSalary(){...}
}
继承与复合的区别:
复合:有(B里有A)
继承:“是”且更多(B是A(及其延伸))
复合关系避免循环定义
⚠️ 避免循环定义,设置对象指针数组
派生类的成员组成和访问权限
访问权限说明符:protected
protected可访问范围介于private成员和public成员之间
⚠️ 派生类的成员和友元函数可以访问当前对象的来自基类的保护成员
(此处有个话术:应该是可以访问自己/自己的亲兄弟(同类对象)的爸爸传下来的保护成员,条件是同一个爸爸的下的同一个类,结果在传下来的同一个类的兄弟或者我的保护成员)
自己的这个类的子孙的对象也可
class Base {
protected:
int x;
};
class D1 : public Base {
public:
void f(D1& other) { other.x = 1; } // ✅ OK:同类对象
void f2(D1* child) { child->x = 1; } // ✅ OK:D1 的派生类对象
void g(Base& b) { /* b.x = 1; */ } // ❌ ERROR:基类对象
};
class D2 : public Base {
// D2 也是 Base 的儿子
};
class D1 {
public:
void h(D2& d2) { /* d2.x = 1; */ } // ❌ ERROR!
// D2 和 D1 都是 Base 的派生类,但彼此无关
};
- this指针指向的对象
- 也可以访问
其他同类对象的来自基类的保护成员 - main函数可以理解为全局函数,不可以访问保护成员
具体可以参见下面这个例子

尤其是这个👇

三种继承方式
⚠️ 继承来的私有成员仍都是私有的,且都是派生类直接不可访问的!只能通过基类的public接口或者protected接口访问!(虽然真的很怪)
公有继承:保持原样
私有继承:全部私有(“孙”类访问不了)
保护继承:公有变保护,其余不变
class Base {
private:
int secret = 42;
protected:
int getSecret() { return secret; } // 提供给子类的合法通道
};
class Derived : public Base {
public:
void test() {
// secret = 10; // 错误!编译器报错:不可访问
int val = getSecret(); // 正确!通过受保护接口访问
}
};

一级警报:⚠️⚠️派生类自身的成员函数不能访问自己继承来的私有成员变量,但是派生类能访问自己私有继承来的公有和保护成员(哭笑不得)
想访问自己继承来的私有成员变量,要间接,即通过例如:继承来的基类的public接口或者protected接口访问!
派生类的构造和析构
派生类的构造函数
class Bug{
private:
int nLegs;
int nColor;
public:
int nType;
Bug(int legs,int color);
void PrintBug(){}
};
class FlyBug:public Bug{
private:
int nWings;
public:
FlyBug(int legs,int color,int wings);
};
Bug::Bug(int legs,int color){
nLegs=legs;
nColor=color;
}
// 错误!不能自己碰基类继承来的私有成员
// FlyBug::FlyBug(int legs,int color,int wings){
// nLegs=legs; //error!
// nColor=color; //error!
// nType=1; //ok
// nWings=wings;
//}
//正确如下,调用基类的构造函数
FlyBug::FlyBug(int legs,int color,int wings):Bug(legs,color){
nType=1; //⚠️一定不能放到初始化列表,只能通过赋值或者基类初始化
nWings=wings;// 可以放到初始化列表
}
int main(){
FlyBug fb(2,3,4);
fb.PrintBug();
fb.nType=1;
fb.nLegs=2; //error!
return 0;
}
因此;
- 创建派生类对象,需要调用基类的构造函数
- 初始化从基类继承来的成员
- 总是先执行基类的构造函数(析构时则相反,与封闭类类似,
脱白大褂再迎来生命的终结)
- 调用方式:
- 显示构造(如上例)
- 隐式:缺省情况下默认调用基类的默认构造函数
包含成员对象的派生类的构造函数

顺序分别同,自推不难
⚠️考点:public继承的赋值兼容原则
class base{};
class derived : public base{};
base b;
derived d;
⚠️ 派生类的对象可以赋值给基类对象
b=d⚠️ 派生类对象可以初始化基类引用
base & br=d;
- ⚠️ 派生类对象的地址可以赋值给基类指针(更详细参见后文)
base *pb=& d;
⚠️private/protected继承无上述操作
基类指针不能访问基类的私有成员,无论它指向的是基类对象还是派生类对象。
访问控制是静态的、基于类定义的,与指针类型无关:
访问场景 能否访问基类 private外部代码通过 Base*❌ 外部代码通过 Derived*❌ 派生类成员函数中通过 Base*❌ 派生类成员函数中通过 this(Derived*)❌ 基类自己的成员函数中 ✅ class Base { int private_val; // private protected: int protected_val; public: int public_val; }; class Derived : public Base {}; int main() { Derived d; Base* p = &d; // p->private_val = 1; // ❌ 编译错误! // p->protected_val = 1; // ❌ 外部也不能访问 protected p->public_val = 1; // ✅ 只能访问 public }即使在派生类内部:
class Derived : public Base { public: void func() { Base* p = this; // p->private_val = 1; // ❌ 仍然错误! p->protected_val = 1; // ✅ protected 在派生类内部可以 p->public_val = 1; // ✅ public 随时可以 } };
派生类与基类的指针类型转换
基类与派生类强制类型转换
⚠️ 公有派生下:
派生类对象的地址可以赋值给基类指针
base *pb=& d;
*pb指向一个Derived对象,可以看作一个Base类的对象,访问它的public成员(对外部来说)
即便如此,也不能通过基类指针访问基类没有,而派生类有的成员
☀️此时,通过
ptrBase去观察它指向的内存,编译器只会根据Base类的模板去匹配内存字段
可以强制类型转换
Base * ptrBase= & objDerived;
Derived *ptrDerived = (Derived*)ptrBase
(但有点危险)
#include <iostream>
using namespace std;
class Base {
protected:
int n;
public:
Base(int i) : n(i) {
cout << "Base " << n << " constructed" << endl;
}
~Base() {
cout << "Base " << n << " destructed" << endl;
}
void Print() { cout << "Base:n=" << n << endl; }
};
class Derived : public Base {
public:
int v;
Derived(int i) : Base(i), v(2 * i) {
cout << "Derived constructed" << endl;
}
~Derived() {
cout << "Derived destructed" << endl;
}
void Func() { }
void Print() {
cout << "Derived:v=" << v << endl;
cout << "Derived:n=" << n << endl;
}
};
int main() {
Base objBase(5);
Derived objDerived(3);
Base * pBase = &objDerived;
// pBase->Func(); // err; Base类没有Func()成员函数
// pBase->v = 5; // err; Base类没有v成员变量
pBase->Print(); // 调用基类函数Print()
// Derived * pDerived = &objBase; // error
Derived * pDerived = (Derived *)(&objBase); // 强制转换,慎用
pDerived->Print(); // 慎用,可能出现不可预期的错误
pDerived->v = 128; // 往别人的空间里写入数据 -> 可能引起问题
objDerived.Print();
return 0;
}
- ⚠️
pBase->Print();调用的是被隐藏的基类的print()函数,而不是派生类重载的那个- 关于扩张(多出来未知)
直接基类与间接基类
类A派生B,类B派生C,…
- 在声明派生类时,派生类的首部只要列出它的直接基类
间接基类不要在首部列出- 自动会向上继承
- 派生类的成员包括:
- 派生类自己定义的成员
- 直接基类中定义的成员
- 间接基类的全部成员(默认了为根基类)
关于”自动向上继承”
C 确实会继承 A 的全部成员(包括间接继承),但访问权限可能逐层衰减。如果 B 对 A 是
private继承,那么 C 中 A 的public和protected成员会变成 B 的private,C 就无法访问这些从 A 来的成员。不是”全部成员都能用”,而是”全部成员都在对象内存里”。关于构造顺序
虽然声明时只写直接基类
class C : public B,但构造时编译器会自动先构造 A(间接基类),再构造 B(直接基类),最后构造 C。析构顺序严格相反。
多继承(考试不要求,不过有点助于进一步理解)
多继承指一个类可以从多个基类派生,以继承多个基类的成员。
// 多继承语法格式 class derived : access-specifier₁ base₁, access-specifier₂ base₂, ... { // 派生类成员定义 };
access-specifier可取值为private、protected、public,用于指定每个基类的继承权限。
多继承的派生类构造函数
2.1 构造函数定义示例
class base1 { int i; public: base1(int n) { i = n; } }; class base2 { int j; public: base2(int n) { j = n; } }; class derived : public base1, public base2 { public: derived(int x); }; // 派生类构造函数:在初始化列表中调用基类构造函数 derived::derived(int x) : base1(x), base2(0) { }2.2 构造函数调用顺序
- 派生类对象创建时,先按继承顺序调用基类构造函数,再调用派生类构造函数。
- 若派生类包含成员对象,成员对象的构造函数在基类构造函数调用结束后依次执行,最后调用派生类构造函数。
- 完整流程:
- 按继承顺序调用基类构造函数
- 依次调用成员对象的构造函数
- 调用派生类的构造函数
多继承中基类构造函数的重复调用
3.1 代码示例
#include <iostream> using namespace std; class Base { public: int val; Base() { cout << "Base Constructor" << endl; } ~Base() { cout << "Base Destructor" << endl; } }; class Base1 : public Base { }; class Base2 : public Base { }; class Derived : public Base1, public Base2 { }; int main() { Derived d; return 0; }3.2 运行结果与问题分析
Base Constructor Base Constructor Base Destructor Base Destructor
- 问题:
Derived同时继承Base1和Base2,而Base1、Base2均继承自Base,导致Base类的构造/析构函数被调用两次,产生数据冗余与二义性。- 继承关系图:
Base / \ Base1 Base2 \ / Derived
多重继承的二义性问题
4.1 成员名冲突的二义性
class base1 { private: int b1; void set(int i) { b1 = i; } // 私有成员函数 public: int i; }; class base2 { private: int b2; public: void set(int i) { b2 = i; } // 公有成员函数 int get() { return b2; } int i; // 与base1的i同名 }; class derived : public base1, public base2 { public: void print() { printf("%d", get()); // set(5); // ❌ 二义性:编译器无法确定调用哪个set() base2::set(5); // ✅ 正确:指定调用base2的set() // base1::set(5); // ❌ 错误:base1的set()是私有成员,无法访问 } };4.2 外部访问的二义性
int main() { derived d; // d.set(10); // ❌ 二义性:存在多个同名set() // d.base1::set(10); // ❌ 错误:base1的set()是私有成员 d.base2::set(5); // ✅ 正确:指定基类访问 d.base1::i = 5; // ✅ 正确:指定访问base1的i d.base2::i = 5; // ✅ 正确:指定访问base2的i return 0; }4.3 核心规则
- 二义性检查在访问权限检查之前执行,无法通过访问权限消除二义性:
class A { public: void fun(); }; class B { private: void fun(); }; class C : public A, public B { }; C obj; obj.fun(); // ❌ 仍报二义性错误,即使B的fun()是私有- 解决方法:使用作用域解析符
::明确指定要访问的基类成员。

