继承与派生

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

在派生类中访问基类同名成员(叫“隐藏”或“覆盖”)利用
基类::变量名访问,缺省情况下访问的是派生类的成员
范式
1 | class 派生类名 : 派生说明方式符 基类名{ |
派生类的各个成员函数不得访问基类中的private成员
派生说明方式符:public/private/protected
场景
学生(基类)→中学生/大学生/小鞋生/研究生
共同属性:姓名、学号、性别、成绩
特有:大学专业,研究生导师,中学生竞赛
例子
1 | class CStudent{ |
继承与复合的区别:
复合:有(B里有A)
继承:“是”且更多(B是A(及其延伸))
复合关系避免循环定义
⚠️ 避免循环定义,设置对象指针数组
派生类的成员组成和访问权限
访问权限说明符:protected
protected可访问范围介于private成员和public成员之间
⚠️ 派生类的成员和友元函数可以访问当前对象的来自基类的保护成员
(此处有个话术:应该是可以访问自己/自己的亲兄弟(同类对象)的爸爸传下来的保护成员,条件是同一个爸爸的下的同一个类,结果在传下来的同一个类的兄弟或者我的保护成员)
自己的这个类的子孙的对象也可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 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接口访问!(虽然真的很怪)
公有继承:保持原样
私有继承:全部私有(“孙”类访问不了)
保护继承:公有变保护,其余不变
1 | class Base { |

一级警报:⚠️⚠️派生类自身的成员函数不能访问自己继承来的私有成员变量,但是派生类能访问自己私有继承来的公有和保护成员(哭笑不得)
想访问自己继承来的私有成员变量,要间接,即通过例如:继承来的基类的public接口或者protected接口访问!
派生类的构造和析构
派生类的构造函数
1 | class Bug{ |
因此;
- 创建派生类对象,需要调用基类的构造函数
- 初始化从基类继承来的成员
- 总是先执行基类的构造函数(析构时则相反,与封闭类类似,
脱白大褂再迎来生命的终结)
- 调用方式:
- 显示构造(如上例)
- 隐式:缺省情况下默认调用基类的默认构造函数
包含成员对象的派生类的构造函数

顺序分别同,自推不难
⚠️考点:public继承的赋值兼容原则
1 | class base{}; |
⚠️ 派生类的对象可以赋值给基类对象
b=d⚠️ 派生类对象可以初始化基类引用
base & br=d;
- ⚠️ 派生类对象的地址可以赋值给基类指针(更详细参见后文)
base *pb=& d;
⚠️private/protected继承无上述操作
基类指针不能访问基类的私有成员,无论它指向的是基类对象还是派生类对象。
访问控制是静态的、基于类定义的,与指针类型无关:
访问场景 能否访问基类 private外部代码通过 Base*❌ 外部代码通过 Derived*❌ 派生类成员函数中通过 Base*❌ 派生类成员函数中通过 this(Derived*)❌ 基类自己的成员函数中 ✅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 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
}即使在派生类内部:
1
2
3
4
5
6
7
8
9 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类的模板去匹配内存字段
可以强制类型转换
1 | Base * ptrBase= & objDerived; |
(但有点危险)
1 |
|
- ⚠️
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。析构顺序严格相反。
多继承(考试不要求,不过有点助于进一步理解)
多继承指一个类可以从多个基类派生,以继承多个基类的成员。
1
2
3
4 // 多继承语法格式
class derived : access-specifier₁ base₁, access-specifier₂ base₂, ... {
// 派生类成员定义
};
access-specifier可取值为private、protected、public,用于指定每个基类的继承权限。多继承的派生类构造函数
2.1 构造函数定义示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 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 代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 运行结果与问题分析
1
2
3
4 Base Constructor
Base Constructor
Base Destructor
Base Destructor
- 问题:
Derived同时继承Base1和Base2,而Base1、Base2均继承自Base,导致Base类的构造/析构函数被调用两次,产生数据冗余与二义性。- 继承关系图:
1
2
3
4
5 Base
/ \
Base1 Base2
\ /
Derived多重继承的二义性问题
4.1 成员名冲突的二义性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 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 外部访问的二义性
1
2
3
4
5
6
7
8
9 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 核心规则
- 二义性检查在访问权限检查之前执行,无法通过访问权限消除二义性:
1
2
3
4
5 class A { public: void fun(); };
class B { private: void fun(); };
class C : public A, public B { };
C obj;
obj.fun(); // ❌ 仍报二义性错误,即使B的fun()是私有- 解决方法:使用作用域解析符
::明确指定要访问的基类成员。












