type
Post
status
Published
date
May 3, 2026
slug
mxdx
summary
tags
算法
category
icon
password
一、面向对象基本概念
1.1 三大核心特性
1)封装(Encapsulation):数据与方法的绑定
封装的目标不是“把东西藏起来”,而是:
- 把对象的状态(数据)和操作(方法)放在一起
- 对外只暴露“我允许你做什么”(public 接口)
- 在类内部维护不变量(比如余额不能为负)
好处:
- 降低耦合:外部不需要知道内部怎么存储
- 更易维护:你可以改内部实现,不影响调用方
- 更安全:防止非法状态
C++ 用private/protected/public进行访问控制,是封装的重要工具。
2)继承(Inheritance):代码复用和扩展
继承表达的是一种 is-a(是一个) 的关系:
Circle是一种Shape
Student是一种Person
好处:
- 复用基类代码
- 扩展/改写行为(配合多态)
注意:
- 继承不是“万能复用工具”
- 很多时候 组合(has-a) 更合适(后面最佳实践会讲)
3)多态(Polymorphism):同一接口的不同实现
多态的核心是:用统一的方式调用不同对象的行为。
- 你用
Shape*或Shape&指向Circle/Square
- 调用
shape.area()会执行各自的实现
C++ 多态分两类:
- 静态多态:编译期决定(重载、模板)
- 动态多态:运行期决定(虚函数)
1.2 类和对象
- 类(class):蓝图/模板,描述“有什么数据+能做什么事”
- 对象(object):类的实例,运行时在内存中真实存在
举个直观例子:
class BankAccount是“银行卡的设计图”
BankAccount a;是一张真实银行卡对象,有自己的余额
二、类和对象基础
2.1 类定义
你给的结构非常标准:
补充两个 C++ 细节:
- class 默认访问权限是 private
- 成员函数在类外定义要用作用域解析符
::
2.2 访问控制
private:只能在类内部访问(包括友元 friend)
protected:类内部 + 派生类可以访问
public:任意位置访问(你的对外 API)
设计建议:
- 数据成员通常设为
private
- 必要时提供
public的只读/受控修改接口
protected要谨慎用:它会让派生类依赖你的内部细节
2.3 构造函数和析构函数
构造函数关键点
- 推荐使用成员初始化列表(而不是在函数体里赋值)
explicit防止隐式转换(很多 bug 源头)
析构函数关键点
- 对象生命周期结束时自动调用
- 资源管理(文件句柄、锁、内存等)常在析构释放(RAII)
重要:如果类要作为基类并通过基类指针删除对象,析构必须是 virtual
2.4 特殊成员函数(拷贝/移动等)
C++ 有一组“特殊成员函数”(编译器可能自动生成):
- 拷贝构造
T(const T&)
- 拷贝赋值
T& operator=(const T&)
- 移动构造
T(T&&)(C++11)
- 移动赋值
T& operator=(T&&)(C++11)
- 析构
~T()
三/五/零法则(非常关键)
- Rule of 3:如果你自定义了析构/拷贝构造/拷贝赋值之一,很可能需要另外两个
- Rule of 5:C++11 后再加移动构造/移动赋值
- Rule of 0:最推荐——用标准库类型(如
std::vector、std::unique_ptr)管理资源,让编译器自动生成这些函数
资源类示例(展示 Rule of 5)
三、封装
3.1 数据隐藏
- 私有数据成员
private
- 对外提供
Getter/Setter
但 Setter 不应该只是“赋值通道”,更重要是维护不变量:
3.2 实现封装:BankAccount 示例
要点:
getBalance() const:保证查询不会修改对象状态(const 正确性)
- 不直接暴露
balance,避免外部随意改成负数
四、继承
4.1 继承类型
理解三种继承最核心的一句:
- public 继承:保留 is-a 语义(最常用)
- protected/private 继承:更像“用基类实现”,对外不再是 is-a(很多时候用组合更清晰)
4.2 继承中的访问控制(public 继承下)
基类成员 | public继承后在派生类中 |
public | public |
protected | protected |
private | 不可访问(但仍存在于对象内存中) |
注意:
private 成员派生类“不能直接访问”,但它仍然是基类对象的一部分。4.3 构造/析构调用顺序
构造顺序(非常重要):
- 基类构造
- 成员对象构造(按声明顺序,不是初始化列表顺序)
- 派生类构造
析构顺序反过来:
- 派生类析构
- 成员对象析构
- 基类析构
示例:
4.4 多重继承
多重继承的典型风险:
- 同名成员冲突
- 菱形继承(Diamond Problem):Base 被重复继承两份
现代 C++ 中更常见的用法是:
- 多继承“接口”(纯虚函数类)
- 尽量避免多继承“含数据实现的类”
4.5 虚继承(解决菱形继承)
虚继承的效果:
Final中Base只有一份共享子对象
注意点:
- 最终派生类(如
Final)负责构造虚基类Base
- 复杂度增加,只有在确实需要菱形结构时使用
五、多态
5.1 静态多态(编译时)
1)函数重载(同名不同参数)
2)运算符重载(见第六章)
3)模板(泛型编程)
静态多态优点:
- 无虚函数开销
- 可内联、性能好
缺点:
- 编译时间和错误信息可能更复杂
- 需要在编译期知道类型
5.2 动态多态(运行时)
5.2.1 虚函数与纯虚函数
运行时多态的条件:
- 通过 基类指针/引用 调用虚函数
- 函数在基类中声明为
virtual
5.2.2 抽象类
- 包含纯虚函数的类就是抽象类
- 不能实例化
- 用来定义接口和公共行为
5.2.3 override / final(C++11)
override 强烈建议用:它能帮你抓住很多“签名不匹配导致没覆盖”的隐蔽 bug。动态多态常见坑:
- 对象切片(slicing)
正确做法通常是用
Base& / Base* / 智能指针。- 基类析构非 virtual +
delete basePtr会导致未定义行为(资源泄漏等)
六、运算符重载
运算符重载的原则:
- 让自定义类型表现得像内置类型
- 不要改变运算符的直觉语义(例如把
+写成减法会很糟糕)
6.1 成员函数重载
适合做成员函数的典型运算符:
=
[]
()
+=等复合赋值
6.2 友元函数重载
适合做非成员/友元的场景:
- 左操作数不是该类对象时(例如
double + Complex)
<<、>>流运算符通常用非成员函数(可声明为 friend)
6.3 常用运算符重载要点
<< 输出运算符(常见)
[] 下标运算符:通常提供 const 与非 const 两个版本
七、静态成员
7.1 静态数据成员
特点:
- 属于类本身,不属于某个对象
- 所有对象共享一份
- 传统写法需要在类外定义一次:
C++17 起可以用inline static直接在类内初始化(更方便)
7.2 静态成员函数
特点:
- 没有
this指针
- 只能访问静态成员(除非通过对象/引用传入)
八、友元(friend)
8.1 友元函数
8.2 友元类
建议:
- friend 会破坏封装边界(但有时确实必要)
- 常见用途:运算符重载、与强相关的辅助类协作、单元测试(慎用)
九、const 成员
9.1 const 成员函数
含义:
const成员函数里,this类型相当于const Array*
- 不能修改普通成员(除非成员是
mutable)
9.2 const 对象
实践中非常常见:
- 函数参数用
const T&避免拷贝且保证不修改
- getter 应写成
T get() const
十、高级特性(C++11 及以上)
10.1 移动语义(Move Semantics)
核心概念:
- 右值引用
T&&:绑定临时对象
- std::move:把对象“标记为可移动”(本质是类型转换)
- 移动构造/移动赋值:把资源所有权从一个对象转移到另一个对象
简例:
注意:
std::move不会移动任何东西,它只是允许移动发生
- 被 move 过的对象仍然“有效但状态未指定”,不要依赖其内容
10.2 智能指针与 RAII
unique_ptr:独占所有权,最常用、开销低
shared_ptr:共享所有权,有引用计数开销
weak_ptr:解决 shared_ptr 循环引用(观察者关系等)
RAII:资源获取即初始化
- 构造函数获取资源
- 析构函数释放资源
- “资源”不仅是内存,也包括锁、文件、网络连接等
10.3 委托构造函数
好处:减少重复初始化逻辑。
10.4 继承构造函数(C++11)
适用于派生类不需要额外初始化逻辑的场景。
十一、设计原则
11.1 SOLID 原则(面向对象设计核心)
- S 单一职责:一个类只做一类事情(变化原因单一)
- O 开闭原则:对扩展开放,对修改关闭(用多态/组合扩展功能)
- L 里氏替换:派生类对象必须能替换基类对象且不破坏正确性(不要违反基类契约)
- I 接口隔离:不要让类依赖它不需要的方法(拆分接口)
- D 依赖倒置:依赖抽象而不是依赖具体实现(面向接口编程)
11.2 常见设计模式(结合 C++ 常见落地)
- 工厂模式:把创建逻辑集中/隐藏,返回基类指针(常配合
unique_ptr)
- 单例模式:谨慎使用;可用局部静态变量实现线程安全(C++11 起保证)
- 观察者模式:事件订阅/通知(注意
shared_ptr/weak_ptr防循环引用)
- 策略模式:把算法作为可替换对象/函数传入(现代 C++ 也常用
std::function/模板)
- 适配器模式:接口不兼容时做一层包装
十二、最佳实践
12.1 类设计准则
- 最小化 public 接口:暴露越少越不容易被错误依赖
- 优先组合而非继承:复用实现用组合;表达 is-a 用继承
- 遵循 RAII:避免裸
new/delete,让资源跟对象生命周期绑定
- Rule of 0 / 5:
- 能用标准库管理资源就用(Rule of 0)
- 必须手写资源类就写全(Rule of 5)或
=delete明确禁用
示例:禁止拷贝
12.2 异常安全
- 基本保证:出异常时对象仍处于有效状态(不泄漏资源、不崩)
- 强保证:出异常时状态完全不变(常用 copy-and-swap 技术)
- 不抛保证:承诺不会抛异常(例如移动构造最好
noexcept,否则容器可能退化成拷贝)
12.3 性能考虑
- 小函数可
inline(但不要为了 inline 写成“头文件巨无霸”)
- 避免不必要拷贝:用
const T&、返回值优化(RVO/NRVO)、移动语义
- 认识虚函数开销:一次间接调用 + 可能失去内联机会(但很多场景完全可接受)
- 对多态对象集合:用
std::vector<std::unique_ptr<Base>>常见且安全
十三、示例代码框架(在你给的基础上补全并更现代)
下面给一个更完整、可直接运行的示例:抽象基类 + 多态使用 + 智能指针管理生命周期。
你会看到:
Shape定义了“统一接口”
Circle/Rectangle实现各自行为
- 外部代码只依赖
Shape抽象,不关心具体类型
unique_ptr自动释放资源,避免内存泄漏
- 作者:wzy
- 链接:https://wzyblogs.us.ci//article/mxdx
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

