面向对象技术小笔记
类
static
首先 final != const;
static 属性和方法跟随类,相当于全局变量/函数,挂载在类中,普通成员和方法则跟随对象实例,这也是为什么静态成员函数不能在没有实例引用的情况下直接调用非静态成员,并且静态成员函数不需要实例化就可以使用 Math.max()
消息
组成部分:接收器.消息选择器或者说接受对象要采取的方法(参数)
替换原则
指如果类B是类A的子类,那么在任何情况下都可以用类B来替换类A,而外界毫无察觉。
改写机制
Java、Smalltalk等面向对象语言,只要子类通过同一类型签名改写父类的方法,自然便会发生所期望的行为。
C++中,需要父类中使用关键字Virtual来表明这一含义。
- 遮蔽
是指父类变量接收子类类型,并调用方法或者使用变量时候,使用的父类的方法和变量,而不发生多态的现象
比如 C++ 中父类方法没加virtual,或者 Java 中子类的方法签名和父类不一致(比如参数不同,变成了 “重载” 而非 “重写”)
点击查看代码
#include <iostream>
using namespace std;
// 父类:方法不加virtual,默认不可重写
class Animal {
public:
void eat() { // 无virtual
cout << "动物吃饭" << endl;
}
};
// 子类写同名方法,但父类没加virtual,不是“重写”
class Cat : public Animal {
public:
void eat() {
cout << "猫吃小鱼干" << endl;
}
};
// 测试:父类变量接收子类实例,但调用的是父类方法(无多态)
int main() {
Animal* animal = new Cat();
animal->eat(); // 输出“动物吃饭”——因为父类方法没加virtual
return 0;
}
点击查看代码
// Java 的方法遮蔽例子(签名不一致导致遮蔽):
class Animal {
public void eat(String food) { // 父类方法:参数是String
System.out.println("动物吃" + food);
}
}
class Cat extends Animal {
public void eat() { // 子类方法:无参数(签名不同,不是重写,是重载)
System.out.println("猫吃小鱼干");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat("肉"); // 输出“动物吃肉”——调用的是父类的方法(子类的eat()被遮蔽)
// animal.eat(); // 编译报错:父类Animal没有无参的eat()
}
}
继承的八种形式
两个判别总纲
A. “is-a 检验”:先问是不是“是一个……”
PPT 里明确把继承当作一种规范和约束,并给了“是一个检验”去判断两个概念是否适合继承关系,同时强调继承带来代码复用 + 概念复用
B. 替换原则(LSP):子类能不能无痛替换父类
笔记里说“特化子类化”天然满足“可替换性原则”,而“限制子类化/构造子类化”会违反它。
你可以把 LSP 当成一句话:凡是需要父类的地方,换成子类也不应该出问题、不该让人惊讶。
1. 特化子类化(子类型化)——最理想、最常见
一句话:子类是父类的“更具体版本”,只是在父类契约上加细节,不破坏父类承诺。
典型例子:马 → 白马(多了“颜色”属性);人 → 男人(多了“性别”属性)。
2. 规范子类化——用来“统一接口”,抽象类/抽象方法的典型场景
一句话:父类先把“必须有哪些方法”定下来,有些方法只给接口不实现,逼着子类去补齐。
3. 构造子类化——“为了复用代码而继承”,风险很高
一句话:子类几乎只是借用父类实现,甚至可以概念无关;常常不满足替换原则
例子:树→独木舟、堆栈→队列、写二进制文件→写学生信息文件。
4. 泛化子类化——子类反而更“抽象/泛”,会改动或扩展既有功能
一句话:子类把父类概念“放大”成更泛化抽象,通常通过修改/扩展已有功能实现。
从 “特殊到更一般” 的扩展,基类的功能被泛化。
示例:从 “Window”(基类,含size属性及setSize()方法)派生出 “Colored_Window”(子类):不仅继承尺寸控制,还修改了 “显示” 相关行为(增加颜色属性color及setColor()/getColor()方法),使窗口的抽象更泛化(包含颜色维度)。
5. 扩展子类化——只加新能力,不改旧承诺(很推荐)
一句话:子类只增加新行为,父类原有行为完全照用,因此不违反替换原则。
示例:从 “SET”(基类,提供集合基本操作如添加、删除)派生出 “STRINGSET”(子类):继承所有集合操作,仅新增 “按前缀查找字符串” 的方法,不改变原有集合逻辑。
6. 限制子类化——子类更“少/更严格”,典型违反替换原则
一句话:子类把父类能力砍掉或收紧,导致父类能做的事子类不能做——所以通常不算子类型。
例子:双向队列 → 堆栈(把某些操作禁掉)。
7. 变体子类化——本来就不该互为父子,最好抽共同抽象
一句话:多个类功能类似,但概念上没层次;硬让其中一个当父类很别扭,更好的做法是抽一个共同抽象类/接口。
定义:两个或多个类实现类似功能,但抽象概念间似乎不存在层次关系。
例子:控制机械鼠标 ≈ 控制轨迹球。
8. 结合子类化——把多个抽象特性合并成新抽象(多重继承)
一句话:把多个父类/特性“合体”,形成一个同时具备多方能力的新类。
定义:合并两个或更多抽象特性形成新抽象;一个类继承多个基类能力叫多重继承。
元类
第四节 UML类图

第五节 设计模式
创建型模式
工厂
客户端麦当劳想要创建板烧鸡腿堡:
-
没有工厂的时候是if else new一堆;
-
简单工厂是给一个关键字返回一个板烧鸡腿;
-
工厂方法是关联多种汉堡制造的具体工厂,new的时候客户端如麦当劳, new某一个“具体工厂对象”,然后调用它的 create() 得到产品。
-
抽象工厂解决产品族或者说套餐问题,于是在卖汉堡的抽象工厂基础上,不再创建新的抽象工厂,而是在汉堡的抽象工厂里加一个造炸鸡的抽象方法,具体工厂A做套餐A,具体工厂B做套餐B
单例
在内存中只有一个对象。
实现方式:
- 通过私有的构造方法,类内部有一静态实例
- 以自己实例为返回值的静态共有方法
饿汉式单例
#include <iostream>
using namespace std;
class Singleton {
private:
// 对应Java的「private static Singleton singleton = new Singleton()」
// C++中类内不能直接初始化非const静态成员,需在类外初始化
static Singleton instance;
// 对应Java的「private Singleton(){}」:私有构造,禁止外部new
Singleton() {
cout << "饿汉式:实例已创建(程序启动时初始化)" << endl;
}
// 🔴 C++必须加:禁用拷贝构造/赋值运算符
// (Java默认不会复制实例,但C++会自动生成拷贝逻辑,必须手动禁用)
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
return instance; // 直接返回预创建的实例
}
};
// 🔴 C++特有的:类外初始化静态成员
// (对应Java“类加载时初始化”,程序启动时自动执行)
Singleton Singleton::instance;
// 测试:验证单例唯一性
int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
// 地址相同→是同一个实例
cout << "s1地址:" << &s1 << endl;
cout << "s2地址:" << &s2 << endl;
return 0;
}
// 输出:
饿汉式:实例已创建(程序启动时初始化)
s1地址:0x404068
s2地址:0x404068
懒汉式单例
#include <iostream>
#include <mutex> // C++用互斥锁代替Java的synchronized
using namespace std;
class Singleton {
private:
static Singleton* singleton;
Singleton() {
cout << "懒汉式:实例已创建(第一次调用时初始化)" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 对应Java的「synchronized」:互斥锁保证线程安全
static mutex mtx;
public:
// 对应Java的「public static synchronized Singleton getInstance()」
static Singleton& getInstance() {
// 加锁(自动解锁,避免死锁)
lock_guard<mutex> lock(mtx);
if (singleton == nullptr) {
singleton = new Singleton();
}
return *singleton;
}
// 🔴 C++特有的:手动销毁实例(Java有GC,C++需自己释放new的内存)
static void destroyInstance() {
if (singleton != nullptr) {
delete singleton;
singleton = nullptr;
cout << "懒汉式:实例已销毁" << endl;
}
}
};
// 🔴 类外初始化静态成员:初始为nullptr
Singleton* Singleton::singleton = nullptr;
mutex Singleton::mtx;
// 测试
int main() {
Singleton& s1 = Singleton::getInstance(); // 第一次调用→创建
Singleton& s2 = Singleton::getInstance(); // 直接返回已创建的实例
cout << "s1地址:" << &s1 << endl;
cout << "s2地址:" << &s2 << endl;
Singleton::destroyInstance(); // 手动释放内存
return 0;
}
// 运行结果:
懒汉式:实例已创建(第一次调用时初始化)
s1地址:0x123456
s2地址:0x123456
懒汉式:实例已销毁
饿汉式:类内声明静态实例(告诉编译器有这个变量)→ 类外定义(编译期确定分配内存)→ 程序启动时自动调用构造函数→ 实例化(提前备好)。
懒汉式:如果像饿汉式那样 “只类内声明静态实例、不类外定义”→ 编译链接时就报错(没有内存分配信息,类内静态成员变量是 “挂在类名下的全局变量”,语法必须严格提前分配内存)→ 程序都启动不了,更没法运行时动态分配静态内存→ 实例化彻底失败。
而懒汉式用指针 / 静态局部变量,就是绕开这个问题:
- 用指针:类外定义的是 “指针”(编译期分配存地址的内存)→ 运行时用
new给 “实例” 动态分配内存 + 实例化; - 用静态局部变量:函数内声明 + 初始化(不用类外定义)→ 运行时第一次调用才分配内存 + 实例化。
结构型模式
适配器模式 Adapter
类适配器
理解:已经有USB相关代码,现在要接在VGA上面用,于是写一个适配器,它内里继承自USB,但是外壳,即类的属性,还是VGA。

- 第一步:先定义 “接口 / 抽象类”(对应 Java 的 Interface)
#include <iostream>
using namespace std;
class USB { // 对应Java的USB接口:定义USB要实现的功能
public:
virtual void showPPT() = 0; // 纯虚函数(必须被子类实现),对应Java的showPPT()
virtual ~USB() = default; // 基类析构函数设为虚函数,避免内存泄漏
};
class VGA { // 对应Java的VGA接口:定义投影仪需要的VGA功能
public:
virtual void projection() = 0;
virtual ~VGA() = default;
};
- 第二步:实现 “具体类”(对应 Java 的 Impl 类)
// 对应Java的USBImpl:实现USB接口的具体类(存PPT内容)
class USBImpl : public USB {
public:
void showPPT() override { // 重写USB的纯虚函数
cout << "PPT内容演示" << endl;
}
};
// 对应Java的VGAImpI:实现VGA接口的具体类(可选,例子中暂未用到)
class VGAImpI : public VGA {
public:
void projection() override {
cout << "VGAImpI原生投影" << endl;
}
};
- 第三步:写 “类适配器”(核心逻辑)
// 对应Java的AdapterUSB2VGA:适配器类
// 继承USBImpl(拿到USB的内容) + 继承VGA(符合投影仪的VGA要求)
class AdapterUSB2VGA : public USBImpl, public VGA {
public:
// 实现VGA要求的projection()方法
void projection() override {
// 调用从USBImpl继承来的showPPT()
// 相当于:用VGA的方法,执行USB里的内容
this->showPPT();
}
};
- 第四步:写 “投影仪类”(对应 Java 的 Projector 泛型类)
// 对应Java的Projector<T>:投影仪类(模板类)
template <typename T>
class Projector {
public:
// 对应Java的projection(T t):接收设备并尝试投影
// C++用指针(支持动态类型转换)
void projection(T* t) {
// 尝试把传入的设备转换为VGA类型(对应Java的instanceof)
VGA* v = dynamic_cast<VGA*>(t);
if (v != nullptr) { // 转换成功:是VGA设备
cout << "开始投影" << endl;
v->projection(); // 调用VGA的投影方法
} else { // 转换失败:不是VGA设备
cout << "接口不匹配,无法投影" << endl;
}
}
};
- 第五步:测试代码(对应 Java 的 @Test)
int main() {
// 1. 通过适配器创建“VGA类型的设备”(实际用的是USB的功能)
VGA* a = new AdapterUSB2VGA();
// 2. 创建投影仪对象
Projector<VGA> p;
// 3. 连接设备并投影
p.projection(a);
// 释放内存(C++需要手动管理)
delete a;
return 0;
}
- 运行结果
开始投影
PPT内容演示
** 对象适配器**

// 被适配类:有核心功能的USBImpl
class USBImpl {
public:
void showPPT() {
cout << "USB接口演示PPT" << endl;
}
};
// 目标接口:VGA
class VGA {
public:
virtual void projection() = 0;
virtual ~VGA() = default;
};
// 对象适配器:包含USBImpl对象,实现VGA接口
class AdapterUSB2VGA : public VGA {
private:
// 组合:包含被适配类的对象(核心区别)
USBImpl usb;
public:
void projection() override {
// 调用被适配对象的功能,完成适配
usb.showPPT();
}
};
类适配器和对象适配器的核心区别,在于适配器与被适配类(如USBImpl)的关联方式不同——类适配器用多继承,对象适配器用组合(类内包含被适配类的对象),这也是两者最本质的差异。
在使用适配器的时候,客户端创建一个适配器实例,并把它赋值给target(目标类型)。
不过值得注意的是,对象适配器要先new一个被适配实例,然后new一个适配器并把实例喂给他。
核心区别对比
| 特性 | 类适配器(之前讲的) | 对象适配器 |
|---|---|---|
| 与被适配类的关联方式 | 多继承:class Adapter : public USBImpl, public VGA |
组合:类内声明USBImpl对象(或指针) |
| 能否访问被适配类私有成员 | 不能(私有成员只能类自身访问) | 不能 |
| 灵活性 | 较低(继承是静态的,编译期确定) | 更高(组合是动态的,运行时可替换被适配对象) |
| 代码耦合度 | 较高(适配器与被适配类强绑定) | 较低(依赖抽象而非具体类,符合依赖倒置原则) |
| 与被适配类的关联方式 | 多继承:class Adapter : public USBImpl, public VGA |
组合:类内声明USBImpl对象(或指针) |
接口适配器
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。

Decorator模式 装饰器
- 需求: 动态给对象添加额外职责
- 以多种原料(如咖啡牛奶)和多种调料(如冰块、盐、糖)为例,制作某一种特殊产品。无限继承法为每一种可能的产品定义一个子类,会爆炸。有限继承法用布尔值标记是否加某料(如hasMocha、hasSoy),但拓展新料需改所有类、逻辑冗余。
- 装饰器模式:拆分为 “抽象构件(规则)→具体构件(原始产品)→抽象装饰器(装饰规则)→具体装饰器(单个加料)”,通过 “原始产品 + 动态叠加装饰器” 实现任意组合,拓展时只加新装饰器,不碰旧代码。
代码示例
点击查看代码
#include <iostream>
#include <string>
using namespace std;
// 1. 抽象构件(Component):饮料抽象类
class Beverage {
protected:
string description; // 饮料描述
public:
Beverage() : description("Unknown Beverage") {}
virtual ~Beverage() {} // 虚析构函数,确保子类析构正常
// 获取描述(普通虚函数,子类可复用或重写)
virtual string getDescription() {
return description;
}
// 计算成本(纯虚函数,子类必须实现)
virtual double cost() = 0;
};
// 2. 具体构件(ConcreteComponent):具体饮料
// 浓咖啡
class Espresso : public Beverage {
public:
Espresso() {
description = "Espresso"; // 重写描述
}
// 实现成本:1.99美元
double cost() override {
return 1.99;
}
};
// 烧烤咖啡
class DarkRoast : public Beverage {
public:
DarkRoast() {
description = "Dark Roast Coffee";
}
double cost() override {
return 0.99;
}
};
// 混合咖啡
class HouseBlend : public Beverage {
public:
HouseBlend() {
description = "House Blend Coffee";
}
double cost() override {
return 0.89;
}
};
// 脱咖啡因咖啡
class Decaf : public Beverage {
public:
Decaf() {
description = "Decaf Coffee";
}
double cost() override {
return 1.05;
}
};
// 3. 抽象装饰器(Decorator):调味品抽象类
class CondimentDecorator : public Beverage {
protected:
Beverage* beverage; // 持有被装饰对象的指针(组合关系)
public:
// 构造函数:传入被装饰的饮料对象
CondimentDecorator(Beverage* bev) : beverage(bev) {}
virtual ~CondimentDecorator() {
delete beverage; // 析构时释放被装饰对象,避免内存泄漏
}
// 纯虚函数:强制具体装饰器重写描述方法
virtual string getDescription() = 0;
};
// 4. 具体装饰器(ConcreteDecorator):具体调味品
// 巧克力(Mocha)
class Mocha : public CondimentDecorator {
public:
Mocha(Beverage* bev) : CondimentDecorator(bev) {}
// 扩展描述:在被装饰对象描述后添加", Mocha"
string getDescription() override {
return beverage->getDescription() + ", Mocha";
}
// 扩展成本:添加0.20美元
double cost() override {
return 0.20 + beverage->cost();
}
};
// 大豆(Soy)
class Soy : public CondimentDecorator {
public:
Soy(Beverage* bev) : CondimentDecorator(bev) {}
string getDescription() override {
return beverage->getDescription() + ", Soy";
}
double cost() override {
return 0.15 + beverage->cost();
}
};
// 奶油(Whip)
class Whip : public CondimentDecorator {
public:
Whip(Beverage* bev) : CondimentDecorator(bev) {}
string getDescription() override {
return beverage->getDescription() + ", Whip";
}
double cost() override {
return 0.10 + beverage->cost();
}
};
// 客户端测试代码
int main() {
// 测试1:纯浓咖啡(无装饰)
Beverage* espresso = new Espresso();
cout << espresso->getDescription() << " $" << espresso->cost() << endl;
delete espresso; // 释放对象
// 测试2:烧烤咖啡 + 2份巧克力 + 1份奶油
Beverage* darkRoast = new DarkRoast();
darkRoast = new Mocha(darkRoast); // 第一次装饰:加巧克力
darkRoast = new Mocha(darkRoast); // 第二次装饰:再加巧克力
darkRoast = new Whip(darkRoast); // 第三次装饰:加奶油
cout << darkRoast->getDescription() << " $" << darkRoast->cost() << endl;
delete darkRoast;
// 测试3:混合咖啡 + 大豆 + 巧克力 + 奶油
Beverage* houseBlend = new HouseBlend();
houseBlend = new Soy(houseBlend);
houseBlend = new Mocha(houseBlend);
houseBlend = new Whip(houseBlend);
cout << houseBlend->getDescription() << " $" << houseBlend->cost() << endl;
delete houseBlend;
return 0;
}

浙公网安备 33010602011771号