C++ 类的继承与派生 学习文档

Search for a command to run...

No comments yet. Be the first to comment.
一、有序对和笛卡尔积 1、有序对(序偶):由两个元素 x 和 y 按照确定顺序排列组成的二元组,记作⟨x, y⟩ 2、笛卡尔积:以 A 中元素为第一元、B 中元素为第二元,构造所有有序对⟨x,y⟩; 由全部这类有序对构成的集合,称为 A 与 B 的笛卡尔积,记作 AXB 例题: 已知 A={a,b}, B={0,1,2},求笛卡尔积 A × B、B × A A × B(前元取自 A,后元取自 B

1、集合的概念 N元子集:含有n个元素的子集叫做N元子集. 例题:已知集合 A={1,2,3},按元素个数对 A 的所有子集分类 0元子集:∅ 1元子集:{1}, {2}, {3} 2 元子集:{1,2}, {1,3}, {2,3} 3 元子集:{1,2,3} 幂集:设 A 为集合,由 A 的全部子集构成的集合称为 A 的幂集,记作P(A). 例题:A={1,2,3},求A的幂集 答案:P

一、基本元件 元件 符号 说明 例子 个体词 a, b, c... 代表具体对象(常元) a:小明 个体变元 x, y, z... 代表任意对象 x 表示论域中任一元素 谓词 P(x), Q(x,y)... 表示性质或关系 P(x):x是学生; L(x,y):x喜欢y 量词 ∀, ∃ 修饰个体范围 ∀x(所有x); ∃x(存在x) 连接词 ¬, ∧, ∨, →, ↔ 命

本质是"逻辑推导游戏",给定几个前提,用固定规则一步步推出结论。套路固定,背下规则就能拿分。 一、核心推理规则公式: 假言推理(MP)A→B, A ⇒ B肯定前件→肯定后件拒取式(MT)A→B, ¬B ⇒ ¬A否定后件→否定前件假言三段论(HS)A→B, B→C ⇒ A→C蕴含传递析取三段论(DS)A∨B, ¬A ⇒ B否定一边得另一边附加律A ⇒ A∨B或上一个随便什么化简律A∧B ⇒ A (或

定义:S是一个联结词集合,若任一个命题公式都可以由s中的联结词表示出来命题公式与之等价,则称S是一个联结词完备集。 也就是说一个连接词集合,能表达出所有真值函数,称为完备集 以下是完备集: S1={¬,∧,∨} —— 否定、合取、析取 S2={¬,∧,∨,→} —— 否定、合取、析取、蕴涵 S3={¬,∧,∨,→,↔} —— 否定、合取、析取、蕴涵、等价 S4={¬,∧} —— 否定、

本文章由尚方咸鱼编写,后经是天创呀修改
本文档系统整理 C++ 面向对象编程中基类与派生类、派生类实现、访问控制、构造析构、成员标识、虚基类等核心知识点,包含定义、语法、代码示例与核心规则,适合学习与复习使用。
类的继承:面向对象的核心机制,在保持原有类特性的基础上,进行更具体、更详细的定义。
核心用途:代码重用性(继承) + 可扩充性(派生)。
示例:植物大战僵尸中,普通僵尸为基类,路障僵尸、铁门僵尸为派生类。
继承:新类从已有类获取原有特性(站在已有类角度)。
派生:从已有类创建新类,并新增自身特性(站在新类角度)。
关系:继承与派生是同一过程,仅观察角度不同。
目的:
继承:实现代码重用;
派生:改造原有程序,解决新问题。
基类 / 父类:被继承的已有类;
派生类 / 子类:通过派生得到的新类;
一个基类可以派生多个派生类;
一个派生类可以继承多个基类。
直接基类:直接参与派生当前类的基类;
间接基类:基类的基类及更高层级的基类。
单继承:派生类仅有1 个基类(多继承的特例);
多继承:派生类拥有多个基类(多个单继承的组合)。
// 多继承语法
class 派生类名: 继承方式 基类名1, 继承方式 基类名2,... //默认私有继承
{
派生类成员声明;
};
示例:
// c1公有继承c2,私有继承c3,保护继承c4
class c1:public c2,private c3,protected c4
{
private:
int c1_mem=0; // 新增数据成员
public:
int fun(); // 新增函数成员
};
#include <iostream>
using namespace std;
// 基类:点
class Point
{
public:
Point(float xx = 0, float yy = 0) : x(xx), y(yy) {}
void initPoint(float x = 0, float y = 0)
{
this->x = x;
this->y = y;
}
// 移动坐标
void move(float offX, float offY)
{
x += offX;
y += offY;
}
void show() { cout << "this is Point's Show:" << x << y << endl; }
float getX() const { return x; }
float getY() const { return y; }
private:
float x, y;
};
// 派生类:矩形,公有继承Point
class Rectangle : public Point
{
public:
void initRectangle(float x = 0, float y = 0, float w = 0, float h = 0)
{
initPoint(x, y); // 调用基类成员函数
this->w = w;
this->h = h;
}
void show() { cout << "this is Rectangle's Show:" << w << h << endl; }
float getW() const { return w; }
float getH() const { return h; }
private:
float w, h; // 派生类新增成员
};
int main()
{
Rectangle r1;
r1.initRectangle(1, 1, 3.0, 4.0);
r1.move(2, 1); // 调用基类成员函数
cout << r1.getX() << ',' << r1.getY() << ',' << r1.getW() << ',' << r1.getH() << endl;
r1.show();
return 0;
}
规则:派生类会继承基类所有非静态成员(构造函数、析构函数除外)。
| 基类成员 | 公有继承 (public) | 私有继承 (private) | 保护继承 (protected) |
|---|---|---|---|
| public | public | private | protected |
| protected | protected | private | protected |
| private | 不可访问 | 不可访问 | 不可访问 |
详细说明:
基类public成员:
公有继承:子类内 / 外均可访问;
私有 / 保护继承:子类内可访问,类外不可访问。
基类protected成员:三种继承方式下,子类内可访问,类外不可访问。
基类private成员:所有继承方式下,子类均不可直接访问。
简易编程建议: 测试程序中,成员声明为public/protected,使用公有继承,简化访问逻辑。
基类private成员 → 派生类永远无法直接访问;
基类非private成员 → 访问权限向保守方向变化(public→protected→private);
仅基类 public 成员 + 公有继承,可通过子类对象在类外访问。
| 成员类型 | 类内访问 | 类外访问(通过对象) |
|---|---|---|
| public | 允许 | 允许 |
| protected | 允许 | 禁止 |
| private | 允许 | 禁止 |
核心定义
在需要基类对象的任何位置,都可以使用公有派生类对象替代,且派生类仅表现出基类的功能。
派生类对象 → 隐式转换为基类对象
A a1;
B b1;
a1 = b1;
派生类对象 → 初始化基类引用
B b1;
A &rf = b1;
派生类对象地址 → 转换为基类指针
A *p;
B b;
p = &b;
示例:基类指针指向派生类对象,调用的是基类成员函数。
#include <iostream>
using namespace std;
class Base1{
public:
void display() const{ cout << "Base1::display()" << endl; }
};
class Base2 : public Base1{
public:
void display() const{ cout << "Base2::display()" << endl; }
};
class Derived : public Base2{
public:
void display() const{ cout << "Derived::display()" << endl; }
};
// 函数参数为基类指针
void fun(Base1 *p){ p->display(); }
int main()
{
Base1 b1; Base2 b2; Derived d1;
fun(&b1); fun(&b2); fun(&d1);
return 0;
}
// 运行结果:全部输出 Base1::display()
// 派生类构造函数完整语法(可主动调用基类有参构造)
派生类名::派生类名(形参表):
基类名1(基类1实参), 基类名2(基类2实参), // 主动调用基类有参构造
成员对象名(对象实参), // 初始化内嵌对象有参构造
普通成员(初始值) // 初始化普通数据成员
{
其他初始化操作;
}
// 核心写法示例:基类含带参构造,派生类主动调用
class Base{
public:
// 仅定义带参构造,无默认无参构造
Base(int a){ cout << "基类有参构造调用" << endl; }
};
class Derived : public Base{
public:
// 必须在初始化列表主动调用基类有参构造,否则编译报错
Derived(int x) : Base(x) {
cout << "派生类构造调用" << endl;
}
};
默认构造函数使用条件:
无需调用基类带参构造函数;
无需调用内嵌对象带参构造函数。
系统自动生成默认构造函数,调用基类 / 对象的默认构造函数(无参构造函数)。
核心补充规则(高频易错):
默认调用规则:派生类默认构造函数,会自动调用所有直接基类、内嵌成员对象的无参构造函数,无需手动书写。
报错场景:若基类只定义了带参构造函数、未定义无参构造函数(系统不会自动生成默认无参构造),派生类默认构造时找不到可用的无参构造,直接编译报错。
解决方案1:基类手动补充无参构造函数。
解决方案2:派生类显式定义构造函数,在初始化列表中主动调用基类的有参构造函数,强制覆盖默认调用逻辑。
基类构造函数:严格按照类继承声明顺序调用,与构造函数初始化列表书写顺序无关;未手动指定则默认调用无参构造,需传参则必须在初始化列表显式调用。
内嵌对象构造函数:按照类内成员声明先后顺序调用。
派生类自身构造函数:最后执行派生类构造函数函数体内部逻辑。
#include <iostream>
using namespace std;
class Base1{public: Base1(int i) { cout << "Constructing Base1 " << i << endl; }};
class Base2{public: Base2(int i) { cout << "Constructing Base2 " << i << endl; }};
class Base3{public: Base3() { cout << "Constructing Base3 *" << endl; }};
// 继承顺序:Base2 → Base1 → Base3
class Derived : public Base2, public Base1, public Base3
{
public:
Derived(int a,int b,int c,int d):Base1(a),m2(d),m1(c),Base2(b) {}
private:
Base1 m1;
Base2 m2;
Base3 m3;
};
int main(){ Derived obj(1,2,3,4); return 0; }
运行结果:
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *
// 语法
Derived::Derived(const Derived &v):Base(v){}
系统默认生成复制构造函数,自动调用基类复制构造函数;
基类delete复制构造函数,派生类也无法拷贝。
语法:Derived::~Derived(){},与普通类写法一致;
自动调用基类和内嵌对象的析构函数;
执行顺序:与构造函数严格相反。
析构顺序:派生类析构 → 内嵌对象析构 → 基类析构。
对 3.3 代码添加析构函数,运行结果中析构顺序与构造完全相反。
用于区分同名成员,格式:类名::成员名
派生类定义与基类同名函数(无论参数是否相同),基类所有重载函数被隐藏;
同名隐藏≠函数重载(重载要求同一作用域);
调用基类同名函数:必须加基类名::限定。
示例:
class Base{
public:
void print();
void print(int i);
};
class Derived:public Base{
public:
void print(int i,int j,int k); // 隐藏基类所有print
};
// 错误用法:d.print() / d.print(1)
// 正确用法:d.Base::print()
多个基类有同名成员,派生类未定义 → 访问产生二义性;
解决方案:
方式 1:基类名::限定;
方式 2:派生类定义同名成员,隐藏基类成员。
多个派生类继承同一个基类,会导致成员冗余、赋值不一致 → 虚基类解决。
消除多层多继承中共同基类带来的成员冗余、数据不一致问题。
直白来说:在多层 + 多重继承场景下,当多个父类又继承了同一个公共父类时,普通继承会让最终子类里重复保留多份公共父类的成员,造成数据冗余、数据错乱。
class 派生类: virtual 继承方式 基类名{};
关键:第一级继承就必须声明为虚基类。
#include <iostream>
using namespace std;
// 虚基类
class Base0
{
public:
int var0;
void fun0() { cout << "Base0 var0=" << var0 << endl; }
};
// 虚继承
class Base1 : virtual public Base0 {};
class Base2 : virtual public Base0 {};
// 最终派生类
class Derived : public Base1, public Base2 {};
int main()
{
Derived d;
d.var0 = 2; // 直接访问,无冗余、无二义性
d.fun0();
return 0;
}
虚基类成员由最远派生类的构造函数初始化;
仅最远派生类调用虚基类构造函数,其他类调用被忽略;
虚基类有带参构造函数时,所有派生类必须在初始化列表中初始化。
承接前文类型兼容性,解决的核心问题,是继承体系的终极用途。
实现:基类指针/引用指向派生类对象时,优先调用派生类的重写方法,而非基类方法。
基类中声明:virtual 返回值 函数名(参数) const;
核心:虚函数支持子类重写,实现不同子类的差异化逻辑。
1、公有继承:class 子类 : public 基类(默认私有继承无法重写);
2、函数签名必须完全一致:返回值、函数名、参数列表、const 常量修饰,四项缺一不可;
3、基类函数必须是 virtual 虚函数。
强制编译器校验:当前函数是否正确重写匹配基类的虚函数(含const匹配),不满足条件直接报错,杜绝隐性错误。
常见报错原因(实战高频):
① 缺少 public 公有继承;② 子类函数少写/多写 const;③ 函数参数、返回值不匹配;④ 基类非虚函数。
class Shape {
public:
// 基类虚函数,带const
virtual double area() const = 0;
virtual void print() const = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
public:
double r;
Circle(double r):r(r) {}
// 必须带const + override,完全匹配基类签名
void print() const override {
cout << "Circle(" << r << ")";
}
double area() const override {
return 3.14 * r * r;
}
};
virtual 返回值 函数名(参数) const = 0;
1、无函数体,仅做接口声明;
2、包含纯虚函数的类为抽象类;
3、抽象类,只能作为基类被继承;
4、派生类必须全部重写所有纯虚函数,且严格匹配 const 修饰,否则依然是抽象类,无法创建对象。
结合继承、虚函数、重写、override,实现统一接口调用不同子类逻辑
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() const = 0; // 纯虚函数:计算面积
virtual void print() const = 0; // 纯虚函数:打印图形信息
virtual ~Shape() {} // 虚析构:避免内存泄漏
};
const double PI = 3.14;
class Circle:public Shape {
public:
double r;
Circle(double r):r(r) {}
void print() const override {
cout << "Circle(" << r << ")";
}
double area() const override {
return PI * r * r;
}
};
class Rectangle:public Shape {
public:
double l, w;
Rectangle(double len, double wid):l(len), w(wid) {}
void print() const override {
cout << "Rectangle(" << l << "," << w << ")";
}
double area() const override {
return l * w;
}
};
int main() {
double r, l, w;
cin >> r >> l >> w;
Circle c(r);
Rectangle rect(l, w);
Shape* shapes[2] = { &c, &rect };
for (int i = 0; i < 2; ++i) {
shapes[i]->print();
cout << " area=" << shapes[i]->area() << endl;
}
return 0;
}
基类指针删除派生类对象时,保证先调用派生类析构,再调用基类析构,彻底避免内存泄漏。若不使用虚析构,仅会调用基类析构,派生类资源无法释放。
#include <iostream>
using namespace std;
class Base {
public:
// 普通析构,非虚析构
~Base() {
cout << "基类析构函数调用" << endl;
}
virtual void test() {}
};
class Derived : public Base {
public:
~Derived() {
cout << "派生类析构函数调用" << endl;
}
};
int main() {
// 基类指针指向派生类对象
Base* p = new Derived();
delete p;
// 仅调用基类析构,派生类析构不执行,内存泄漏
return 0;
}
错误运行结果:仅输出「基类析构函数调用」,派生类析构失效。
#include <iostream>
using namespace std;
class Base {
public:
// 虚析构函数
virtual ~Base() {
cout << "基类析构函数调用" << endl;
}
virtual void test() {}
};
class Derived : public Base {
public:
~Derived() {
cout << "派生类析构函数调用" << endl;
}
};
int main() {
Base* p = new Derived();
delete p;
// 先析构派生类、再析构基类,资源完全释放
return 0;
}
正确运行结果:
派生类析构函数调用
基类析构函数调用
1、只要类中声明了任意虚函数,该类析构函数必须加 virtual;
2、多态场景下(基类指针/引用操作派生类对象),必须使用虚析构;
3、虚析构不影响构造函数、不影响多态逻辑,只负责安全释放内存。
只要类存在虚函数,必须将析构函数声明为 virtual。
继承实现代码重用,派生实现功能扩展,公有继承是最常用方式;
派生类构造顺序:基类→内嵌对象→自身,析构顺序相反;
同名成员用::区分,多继承二义性通过虚基类解决;
类型兼容性规则:公有派生类对象可替代基类对象,仅表现基类功能;
虚函数实现多态,override 强制校验重写合法性,纯虚函数定义抽象类接口,是面向对象核心考点。