C++(5)
五、面向对象核心
1、继承(重点)
1.1 概念
继承是面向对象三大特性之一,体现代码复用思想。
- 基类(父类):已存在的类,作为基础。
- 派生类(子类):基于基类创建的新类,继承基类特性并可扩展。
示例代码:
// 基类(父类)
class Father {
private:string name = "孙";
public:void set_name(string name) { this->name = name; }string get_name() { return name; }void work() { cout << "我的工作是厨师,我负责炒菜" << endl; }
};// 派生类(子类)继承自Father
class Son : public Father {
public:void init() { set_name("王"); } // 修改基类私有属性(通过基类公有方法)void work() { cout << "我的工作是程序猿,我负责控制挖掘机炒菜" << endl; } // 函数隐藏(重写)void game() { cout << "我不光干活,我还打游戏,原神启动" << endl; } // 新增功能
};
派生类的扩展与修改
- 修改基类内容:
- 公有属性:可直接访问修改;私有属性需通过基类公有方法修改。
- 行为(函数):通过同名函数隐藏基类函数(函数覆盖是多态的基础)。
- 新增内容:派生类可添加独立于基类的新功能。
1.2 构造函数
1.2.1 派生类与基类构造函数的关系
构造函数与析构函数不能被继承。派生类构造时需先调用基类构造函数,再初始化自身成员;析构时先析构自身,再调用基类析构函数。
问题示例:若基类无默认构造函数,派生类构造时会报错(无法找到基类构造函数)。
1.2.2 解决方案
- 1.2.2.1 补充基类无参构造函数:在基类中定义无参构造函数(或带默认参数的构造函数)。
- 1.2.2.2 手动调用基类构造函数:
- 透传构造:派生类构造函数显式调用基类构造函数(编译器默认行为)。
class Son : public Father { public:Son() : Father("王") {} // 显式调用基类有参构造Son(string fn) : Father(fn) {} // 透传构造 };
- 委托构造:派生类构造函数委托给同一类的其他构造函数(避免代码冗余)。
class Son : public Father { public:Son() : Son("王") {} // 委托给带参构造Son(string fn) : Father(fn) {} // 基类构造 };
- 继承构造(C++11):通过
using 基类构造函数
自动继承基类所有构造函数。class Son : public Father { public:using Father::Father; // 继承Father的所有构造函数 };
- 透传构造:派生类构造函数显式调用基类构造函数(编译器默认行为)。
1.3 对象的创建与销毁流程
核心规律:
- 静态成员(全局/类内静态)创建早于非静态成员。
- 构造顺序:基类构造 → 派生类成员变量构造 → 派生类构造函数。
- 析构顺序:派生类析构函数 → 派生类成员变量析构 → 基类析构函数。
- 对象生命周期内,构造与析构过程对称。
1.4 多重继承
1.4.1 概念
一个派生类可继承多个基类(每个基类关系为单继承)。
示例:
class Sofa { public: void sit() { cout << "沙发可以坐" << endl; } };
class Bed { public: void lay() { cout << "床可以躺" << endl; } };
class SofaBed : public Sofa, public Bed {}; // 多重继承
1.4.2 常见问题
-
1.4.2.1 重名问题:多个基类存在同名成员时,需用
基类名::成员
消除二义性。class Sofa { public: void clean() { cout << "打扫沙发" << endl; } }; class Bed { public: void clean() { cout << "打扫床" << endl; } }; class SofaBed : public Sofa, public Bed {void clean() {Sofa::clean(); // 明确调用Sofa的cleanBed::clean(); // 明确调用Bed的clean} };
-
1.4.2.2 菱形继承:派生类通过多个路径继承同一基类,导致数据冗余和二义性。
解决方法:- 作用域限定:通过
基类名::成员
访问(仅解决二义性,不解决冗余)。 - 虚继承(C++11):基类前加
virtual
,使派生类共享基类实例(消除冗余)。class Furniture { public: void func() { cout << "家具" << endl; } }; class Sofa : virtual public Furniture {}; // 虚继承 class Bed : virtual public Furniture {}; // 虚继承 class SofaBed : public Sofa, public Bed {}; // 无冗余,可直接调用Furniture::func()
- 作用域限定:通过
2、权限(掌握)
2.1 权限修饰符
三种权限(private
/protected
/public
)在类内、派生类、全局的作用域如下:
权限 | 类内 | 派生类 | 全局 |
---|---|---|---|
private | √ | × | × |
protected | √ | √ | × |
public | √ | √ | √ |
示例:
class Base {
protected:string s = "保护权限"; // 派生类可访问
public:string str = "公有权限"; // 所有位置可访问
};class Son : public Base {
public:void test() {cout<< s << endl; // 合法(派生类访问protected)cout << str << endl; // 合法(派生类访问public)}
};
2.2 不同继承方式的权限影响
继承方式决定基类成员在派生类中的权限(仅影响派生类内部):
- 公有继承(最常用):基类
public
/protected
成员在派生类中权限不变。 - 保护继承:基类
public
/protected
成员在派生类中变为protected
。 - 私有继承:基类
public
/protected
成员在派生类中变为private
。
3、多态(重点)
3.1 概念
多态分为静态多态(编译时确定,如函数重载)和动态多态(运行时确定,通过继承与虚函数实现)。
动态多态核心:一种接口,多种实现(运行时根据对象类型调用对应函数)。
3.2 动态多态的条件
- 公有继承:派生类通过
public
继承基类。 - 函数覆盖:基类声明虚函数,派生类定义同名同参函数(
override
可选验证)。 - 基类指针/引用指向派生类对象:通过指针或引用调用虚函数时触发动态绑定。
3.3 虚函数
- 定义:用
virtual
修饰的成员函数(构造/静态函数不可为虚函数)。 - 性质:
- 虚函数具有传递性(基类虚函数→派生类覆盖函数仍为虚函数)。
- 支持
override
关键字(C++11)验证覆盖是否成功(防止函数隐藏)。
3.4 多态的实现
- 底层机制:具有虚函数的类生成虚函数表(vtable),对象内部存储虚表指针(vptr)指向对应虚表。
- 调用流程:通过
vptr
找到虚表,根据虚表中函数地址调用对应实现。
3.5 虚析构函数
- 问题:若基类指针指向派生类对象,直接
delete
基类指针仅调用基类析构函数,导致派生类资源未释放(内存泄漏)。 - 解决:基类析构函数声明为
virtual
(派生类析构函数自动成为虚函数)。class Animal { public:virtual ~Animal() { cout << "Animal析构" << endl; } }; class Dog : public Animal { public:~Dog() { cout << "Dog析构" << endl; } }; // Animal* d = new Dog; delete d; // 输出:Dog析构 → Animal析构(正确释放资源)
3.6 类型转换(C++11)
-
static_cast
:静态转换(编译时检查),用于基本类型转换或类层次上行转换(安全),下行转换不安全。 -
dynamic_cast
:动态转换(运行时检查),用于类层次安全上下行转换(失败返回空指针/抛异常)。 -
const_cast
:移除/添加const
限定符(谨慎使用,破坏封装性)。 -
reinterpret_cast
:重解释内存(风险极高,仅用于底层操作)。
3.7 多态的优缺点
- 优点:代码灵活、可扩展、易维护(新增派生类无需修改现有接口)。
- 缺点:运行时开销(虚表查找)、代码复杂度高(需理解继承与虚函数)。
4、抽象类(掌握)
- 定义:包含至少一个纯虚函数的类(无法实例化对象,仅作为接口规范)。
- 纯虚函数:声明为
virtual 返回值类型 函数名(参数) = 0;
。
示例:
class Shape { // 抽象类(包含纯虚函数)
public:virtual void area() = 0; // 纯虚函数(面积)virtual void perimeter() = 0; // 纯虚函数(周长)
};class Circle : public Shape {
public:void area() override { cout << "计算圆面积" << endl; }void perimeter() override { cout << "计算圆周长" << endl; }
};
注意事项:
- 抽象类的析构函数必须是虚析构函数(确保派生类资源正确释放)。
- 抽象类支持多态(可通过基类指针/引用操作派生类对象)。
5、纯虚析构(熟悉)
- 定义:纯虚析构函数是特殊的虚析构函数(声明为
virtual ~类名() = 0;
),需在类外实现。 - 作用:使基类成为抽象类,同时确保派生类析构函数被正确调用。
示例:
class Animal {
public:virtual ~Animal() = 0; // 纯虚析构声明
};
Animal::~Animal() { cout << "Animal析构" << endl; } // 类外实现class Dog : public Animal {
public:~Dog() { cout << "Dog析构" << endl; }
};
6、私有析构函数(熟悉)
- 特点:析构函数声明为
private
,导致:- 外部无法通过
delete
释放堆内存对象(编译错误)。 - 外部无法创建栈内存对象(构造函数虽可调用,但析构时无法访问)。
- 外部无法通过
示例:
class Test {
private:~Test() { cout << "析构" << endl; }
};int main() {Test* t = new Test; // 合法(堆对象)// delete t; // 错误(无法访问私有析构)// Test t2; // 错误(栈对象析构时无法访问私有析构)return 0;
}