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++ 细节:
  1. class 默认访问权限是 private
  1. 成员函数在类外定义要用作用域解析符 ::

2.2 访问控制

  • private:只能在类内部访问(包括友元 friend)
  • protected:类内部 + 派生类可以访问
  • public:任意位置访问(你的对外 API)
设计建议:
  • 数据成员通常设为 private
  • 必要时提供 public 的只读/受控修改接口
  • protected 要谨慎用:它会让派生类依赖你的内部细节

2.3 构造函数和析构函数

构造函数关键点

  1. 推荐使用成员初始化列表(而不是在函数体里赋值)
  1. 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::vectorstd::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 构造/析构调用顺序

构造顺序(非常重要):
  1. 基类构造
  1. 成员对象构造(按声明顺序,不是初始化列表顺序)
  1. 派生类构造
析构顺序反过来:
  1. 派生类析构
  1. 成员对象析构
  1. 基类析构
示例:

4.4 多重继承

多重继承的典型风险:
  • 同名成员冲突
  • 菱形继承(Diamond Problem):Base 被重复继承两份
现代 C++ 中更常见的用法是:
  • 多继承“接口”(纯虚函数类)
  • 尽量避免多继承“含数据实现的类”

4.5 虚继承(解决菱形继承)

虚继承的效果:
  • FinalBase 只有一份共享子对象
注意点:
  • 最终派生类(如 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 自动释放资源,避免内存泄漏
深入浅出最大流算法(Maximum Flow)区间贪心算法
Loading...
目录
0%
wzy
wzy
一名初中生
公告
wc我是谁我在哪???
 
目录
0%