当前位置: 首页 > news >正文

观察者模式 C++

👂 Honey Honey - 孙燕姿 - 单曲 - 网易云音乐

目录

🌼前言

🌼描述

🎂问题

💪解决方案

🈲现实场景 + 代码

场景1 -- 报纸发行

场景

解释

代码

场景2 -- 气象资料发布

场景3 -- 过红绿灯

🏔观察者 -- 模式结构

🏦观察者 -- 适用情况

🐟实现方式

😖优缺点

🔚与其他模式的联系


🌼前言

《Linux多线程服务器端编程 使用muduo C++网络库》第 3 页提到,“本书默认大家已熟知 --观察者模式”,特地来学习

🌼描述

  • 观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时,通知多个 “观察” 该对象的其他对象

  • 定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新
  • 一种行为型的设计模式,QT 中的信号槽就是一种典型的观察者模式

🎂问题

比如你有两种类型的对象:顾客 和 商店。

顾客对某个特定品牌的产品非常感兴趣(比如最新型号的 iphone 手机),该产品很快会在商店里出售。

顾客可以每天到商店里看看 iphone 到货了没。如果最新款 iphone 没到货,大多数到达商店的顾客就会空手而归(浪费资源)

还有个问题是:为了避免 “顾客每天到商店查看到货情况”,商店可以在每次新产品到货时,向 所有顾客 发送邮件,那么部分客户就不用反复前往商店。

但是这会打扰对新产品不感兴趣的顾客

如何解决呢?

💪解决方案

观察者模式 就此登场:

发布者(publisher):自身状态的改变,通知到其他对象。

订阅者(subscribers):希望关注发布者状态变化的对象。

观察者模式中,发布者类拥有自己的  添加订阅机制,每个对象都能订阅或取消订阅发布者事件流

该机制包括:

1)一个用于存储订阅者对象的列表    2)几个用于添加或删除该列表中订阅者的方法

发布者发布通知后,都需要遍历订阅者列表,然后调用每个订阅者对象特定的通知方法

为了解耦订阅者和发布者,所有订阅者都要实现相同接口,发布者只通过接口和订阅者交互

如果有多种不同类型的发布者,而且要求订阅者要兼容所有发布者,那么就让所有发布者拥有相同的接口。

这个接口只需描述几个订阅方法

如此一来,订阅者就可以在不与具体发布者耦合的情况下,通过接口观察发布者的状态

🈲现实场景 + 代码

每个代码都是相似的,不同场景,增加熟练度 

场景1 -- 报纸发行

场景

发布者 -- 出版社

订阅者 -- 渣渣辉

比方说,你订阅了一份报纸,那么就不需要再去报摊查询新出版的报纸了。

出版社(发布者)会在报纸出版后,直接将最新一期报纸寄到你的邮箱。

出版社(发布者)负责维护订阅者列表( subscriber() ),了解订阅者对哪些报纸感兴趣。

当订阅者不感兴趣后,他们随时可以从该列表退出。

解释

解释1 -- 代码中的接口

  1. ISubscriber 和 IPublisher 是两个接口,定义了订阅者和出版者的基本行为。这两个接口是基类。

  2. ISubscriber 接口定义了一个 Update 方法,这是具体订阅者需要实现的方法,用于接收来自出版者的消息。

  3. IPublisher 接口定义了 AttachDetach 和 Notify 方法,分别用于添加订阅者、移除订阅者和通知所有订阅者。

  4. Publisher 是 IPublisher 的子类,实现了 IPublisher 接口的所有方法。它维护了一个订阅者列表,并在有新消息时通知所有订阅者。

  5. Subscriber 是 ISubscriber 的子类,实现了 ISubscriber 接口的 Update 方法。它保存了一个出版者的引用,并可以从出版者的订阅者列表中添加或移除自己。

  6. 在 ClientCode 函数中,创建了一个 Publisher 对象和多个 Subscriber 对象,并通过 Attach 和 Detach 方法管理订阅者列表。当 Publisher 有新消息时,所有在订阅者列表中的 Subscriber 都会收到通知。

总的来说,ISubscriber 和 IPublisher 是基类,定义了一组行为Subscriber 和 Publisher 是子类,实现了这些行为。这是典型的面向对象设计,通过接口和实现分离,使得代码更加灵活和可扩展

解释2 -- 整体逻辑

这段代码实现了观察者模式,

其中包含两个接口:ISubscriber(订阅者)和IPublisher(发布者)

具体的订阅者类Subscriber和发布者类Publisher分别实现了这两个接口

ISubscriber接口定义了一个Update方法,接收发布者的消息

IPublisher接口定义了Attach、Detach和Notify方法(添加一个订阅者、删除一个订阅者,通知所有订阅者)

Publisher类中的订阅者列表 list_subscriber_,存储所有订阅者

有新消息时,Publisher 通过 Notify 方法通知所有订阅者

Publisher 的 CreateMessage 方法,创建新消息 + 通知所有订阅者

Subscriber 类在构造函数中将自己添加到 Publisher 的订阅者列表,Update方法接收来自Publisher的消息

Subscriber 的 RemoveMeFromTheList(),从Publisher的订阅者列表中删除自己

main函数中,创建了一个Publisher对象和三个Subscriber对象

Publisher发布了几条消息,Subscriber接收并打印出这些消息

解释3 -- Publisher 接口

1)为什么 Subscriber 类需要通过通过统一接口 ISubscriber 实现呢?

是为了使 Publisher类 和 Subscriber类 解耦,具体的说👇

IPublisher接口 只依赖于 ISubscriber 接口,而不是具体的 Subscriber类,所以我们可以在不修改 Publisher类 的情况下,添加新的 Subsriber 类型,只要这个新的 Subscriber 类型实现了 ISubscriber 接口即可(这就是“解耦”) 

2)那么为什么 Publisher 类也需要接口 IPublisher 呢?

比如说,你以后想要创建一个新的 Publisher 类,只需要实现 IPublisher 接口即可

任何依赖 IPublisher 接口的代码,都可以无缝衔接地使用这个新的 Publisher 类 

解释4 -- 类的关系

参考博客👇

UML类图六种关系总结 - 知乎 (zhihu.com)

uml 类图依赖与关联的区别 - 掸尘 - 博客园 (cnblogs.com)

  • ​​​​​Publisher 和 ISubscriber 之间是聚合关系
    这就像一个报社(Publisher)和它的订阅者(ISubscriber)之间的关系
    报社是整体,订阅者是部分
    报社可以有很多订阅者,但是即使没有订阅者,报社依然可以存在
    这就是所谓的"has-a"关系。
  • Subscriber 实现了 ISubscriber 接口,Publisher 实现了 IPublisher 接口
    这就像一个具体的订阅者(Subscriber)是订阅者接口(ISubscriber)的具体实现,一个具体的报社(Publisher)是报社接口(IPublisher)的具体实现
    这是一种"is-a"关系

  • main函数(也就是客户端代码)关联了 Publisher,依赖了 Subscriber
    这就像客户端代码知道报社(Publisher)的存在,并且使用了订阅者(Subscriber)
    这是一种"knows-a"关系和"uses-a"关系
    客户端代码知道报社的存在,可以通过报社发布消息
    同时,客户端代码也使用了订阅者,可以创建订阅者,让订阅者订阅报社的消息

代码

#include <iostream> // 输入输出流库
#include <list> 
#include <string>// 订阅者接口
class ISubscriber {
public:virtual ~ISubscriber(){}; // 虚析构函数// 具体订阅者的更新方法;= 0 纯虚函数,子类必须自己实现virtual void Update(const std::string &message_from_publisher) = 0;
};// 出版社接口
class IPublisher {
public:// 纯虚函数:基类不能被实例化,只能在子类实现// 虚析构 -- 保证子类析构时能够调用基类析构virtual ~IPublisher(){};// 添加订阅者;= 0 子类自己实现virtual void Attach(ISubscriber *subscriber) = 0;// 移除订阅者;= 0 子类自己实现virtual void Detach(ISubscriber *subscriber) = 0;// 通知所有订阅者;= 0 子类自己实现virtual void Notify() = 0;
};// 具体出版社类
class Publisher : public IPublisher {
public:virtual ~Publisher() { // 析构std::cout << std::endl;std::cout << "报社要倒闭了!!!" << std::endl;}// override 表示子类覆盖父类的方法 -- 保证方法名和参数一致// 添加 -- 子类实现void Attach(ISubscriber *subscriber) override {list_subscriber_.push_back(subscriber);}// 删除void Detach(ISubscriber *subscriber) override {list_subscriber_.remove(subscriber);}// 通知void Notify() override {std::cout << std::endl;// 订阅者列表迭代器std::list<ISubscriber *>::iterator iterator = list_subscriber_.begin();HowManySubscriber(); // 输出订阅者数量while (iterator != list_subscriber_.end()) {// 更新所有订阅者(*iterator)->Update(message_);++iterator;}}// 创建消息 && 通知订阅者void CreateMessage(std::string message = "Empty") {// Empty 默认值,会被输入值覆盖this->message_ = message;Notify();}// 输出订阅者数量void HowManySubscriber() {std::cout << "有 " << list_subscriber_.size() << " 个傻逼看我的报纸" << std::endl;}// 其他业务逻辑,大事件发生时通知订阅者void SomeBusinessLogic() {this->message_ = "大事件!大事件!"; // 更改消息Notify(); // 通知std::cout << "房价跌到 300 块一平,快点入手!" << std::endl;}private:std::list<ISubscriber *> list_subscriber_; // 订阅者列表std::string message_; // 发送给订阅者的消息
};// 具体的订阅者类
class Subscriber : public ISubscriber {
public:// 构造函数Subscriber(Publisher &publisher) : publisher_(publisher) {this->publisher_.Attach(this); // 添加订阅者std::cout << std::endl;std::cout << "O(∩_∩)O渣渣 "<< ++Subscriber::static_number_ << " 号订阅了报纸" << std::endl;this->number_ = Subscriber::static_number_; // 订阅者编号}virtual ~Subscriber() {std::cout << std::endl;std::cout << "垃圾报纸,毁我青春,注销 " << this->number_ << " 号" << std::endl;}// 用新消息更新订阅者void Update(const std::string &message_from_publisher) override {message_from_publisher_ = message_from_publisher;PrintInfo();}// 从报社的订阅者列表,删除此订阅者void RemoveMeFromTheList() {publisher_.Detach(this);std::cout << std::endl;std::cout << "订阅者 " << number_ << " 不看报纸了" << std::endl;}// 打印新消息void PrintInfo() {std::cout << "订阅者 " << this->number_ << " 号: " << message_from_publisher_ << std::endl;}private:std::string message_from_publisher_; // 来自报社的信息Publisher &publisher_; // 报社的引用static int static_number_; // 订阅者实例的数量int number_; // 订阅者编号/*static 静态成员变量,只有一份拷贝所有实例共享通过类名::静态成员变量名访问类外初始化*/
};// static成员变量,类外初始化
int Subscriber::static_number_ = 0;// 客户端代码
int main() {Publisher *publisher = new Publisher; // 创建报社// 创建 3 个看报纸的人Subscriber *subscriber1 = new Subscriber(*publisher);Subscriber *subscriber2 = new Subscriber(*publisher);Subscriber *subscriber3 = new Subscriber(*publisher);// 报社发布消息 为空publisher->CreateMessage();// 报社发布新消息publisher->CreateMessage("房价又涨了!");// 第 2 个人退出subscriber2->RemoveMeFromTheList(); // 第 4 个人加入Subscriber *subscriber4 = new Subscriber(*publisher);// 发布新消息publisher->CreateMessage("我买了新车!!");// 发布新消息publisher->SomeBusinessLogic(); delete subscriber1;delete subscriber2;delete subscriber3;delete subscriber4;delete publisher; // 报社倒闭return 0;
}

O(∩_∩)O渣渣 1 号订阅了报纸O(∩_∩)O渣渣 2 号订阅了报纸O(∩_∩)O渣渣 3 号订阅了报纸有 3 个傻逼看我的报纸
订阅者 1 号: Empty
订阅者 2 号: Empty
订阅者 3 号: Empty有 3 个傻逼看我的报纸
订阅者 1 号: 房价又涨了!
订阅者 2 号: 房价又涨了!
订阅者 3 号: 房价又涨了!订阅者 2 不看报纸了O(∩_∩)O渣渣 4 号订阅了报纸有 3 个傻逼看我的报纸
订阅者 1 号: 我买了新车!!
订阅者 3 号: 我买了新车!!
订阅者 4 号: 我买了新车!!有 3 个傻逼看我的报纸
订阅者 1 号: 大事件!大事件!
订阅者 3 号: 大事件!大事件!
订阅者 4 号: 大事件!大事件!
房价跌到 300 块一平,快点入手!垃圾报纸,毁我青春,注销 1 号垃圾报纸,毁我青春,注销 2 号垃圾报纸,毁我青春,注销 3 号垃圾报纸,毁我青春,注销 4 号报社要倒闭了!!!

场景2 -- 气象资料发布

IDisplayA 和 IDisplayB 类是订阅者,它们实现了 IDisplay 接口并订阅了 DataCenter 发布的通知。DataCenter 类是发布者,它维护了一个订阅者列表,并在数据变化时通知所有的订阅者

气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到两个不同的显示终端(A 和B)

上层:气象站

下层:显示中断

依赖:数据中心

代码2

#include <iostream>
#include <vector>
using namespace std;// IDisplay是一个抽象基类,定义了观察者的接口
class IDisplay {
public:virtual void show(float temperature) = 0;  // 所有的观察者都需要实现这个方法virtual ~IDisplay() = default;  // 虚析构函数,用于删除派生类的对象
};// IDisplayA是一个观察者,实现了IDisplay的接口
class IDisplayA : public IDisplay {
public:void show(float temperature) override {  // 打印温度信息cout << "Display A: " << temperature << endl;}
};// IDisplayB也是一个观察者,实现了IDisplay的接口
class IDisplayB : public IDisplay {
public:void show(float temperature) override {  // 打印温度信息cout << "Display B: " << temperature << endl;}
};// WeatherData是一个数据类,用于存储天气数据
class WeatherData {// 这里可以添加一些属性和方法,例如温度、湿度等
};// DataCenter是主题,它维护了一个观察者列表,并在状态改变时通知所有的观察者
class DataCenter {
public:void Attach(IDisplay *ob) {  // 添加一个观察者obs.push_back(ob);}void Detach(IDisplay *ob) {  // 删除一个观察者obs.erase(remove(obs.begin(), obs.end(), ob), obs.end());}void Notify() {  // 通知所有的观察者float temper = CalcTemperature();for (auto iter = obs.begin(); iter != obs.end(); iter++) {(*iter)->show(temper);}}private:WeatherData* GetWeatherData() {  // 获取天气数据// 这里可以添加一些代码,例如从数据库或者API获取天气数据return new WeatherData;}float CalcTemperature() {  // 计算温度WeatherData *data = GetWeatherData();float temper = 25.0f;  // 这里只是一个示例,实际的温度应该从WeatherData中获取delete data;return temper;}vector<IDisplay*> obs;  // 观察者列表
};int main() {DataCenter *center = new DataCenter;  // 创建一个DataCenter对象IDisplay *da = new IDisplayA;  // 创建一个IDisplayA对象IDisplay *db = new IDisplayB;  // 创建一个IDisplayB对象center->Attach(da);  // 将IDisplayA添加到观察者列表center->Attach(db);  // 将IDisplayB添加到观察者列表center->Notify();  // 通知所有的观察者delete da;  // 删除IDisplayA对象delete db;  // 删除IDisplayB对象delete center;  // 删除DataCenter对象return 0;
}

场景3 -- 过红绿灯

发布者 -- 红绿灯

订阅者 --  汽车

十字路口汽车等待红绿灯变化

代码3

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// Observer class, which is an abstract class 
// that other classes can inherit from to become observers
// 订阅者接口 Observer
class Observer {
public:Observer(int num):m_number(num){}// Virtual destructor to ensure correct deletion // when deleting derived classes through a base class pointervirtual ~Observer() {}// Pure virtual function to be implemented by derived classesvirtual void update(bool flag) = 0;
protected:int m_number;
};// Subject class, which is an abstract class 
// that other classes can inherit from to become subjects
// 发布者接口 Subject
class Subject {
public:virtual ~Subject() {}virtual void attach(Observer* observer) = 0;virtual void detach(Observer* observer) = 0;virtual void notify(bool flag) = 0;
protected:vector<Observer*> observers;
};// TrafficLight class, which is a concrete subject 
// that observers can attach to
// 发布者具体实现
class TrafficLight : public Subject {
public:void attach(Observer* observer) override {observers.push_back(observer);}void detach(Observer* observer) override {observers.erase(remove(observers.begin(), observers.end(), observer), observers.end());}// Notify all attached observers with the current statevoid notify(bool flag) override {if (flag) {cout << "Green light, go." << endl;} else {cout << "Red light, stop." << endl;}for (auto observer : observers) {observer->update(flag);}}
};// Car class, which is a concrete observer that can attach to a subject
// 订阅者具体实现
class Car : public Observer {
public:Car(int num) : Observer(num) {}// Update the state of the car based on the state of the traffic lightvoid update(bool flag) override {if (flag) {cout << "Car " << m_number << ": Start." << endl;} else {cout << "Car " << m_number << ": Stop." << endl;}}
};int main() {Subject *subject = new TrafficLight;Observer *car1 = new Car(1);Observer *car2 = new Car(2);// Attach the cars to the traffic lightsubject->attach(car1);subject->attach(car2);// Notify the cars with the state of the traffic lightsubject->notify(true);subject->notify(false);/*一个类有虚函数,那么它应该有一个虚析构函数这样,当删除一个指向派生类对象的基类指针时,派生类的析构函数也会被调用,防止资源泄露*/delete car1;delete car2;delete subject;return 0;
}
Green light, go.
Car 1: Start.
Car 2: Start.
Red light, stop.
Car 1: Stop.
Car 2: Stop.

🏔观察者 -- 模式结构

结合场景1代码,看👇的模式结构图

解释 -- 类图 

- subscriber: Subscriber[]
// 私有 成员变量名: 类型
// subscriber 是类中的一个私有成员变量
// 这个变量的类型是:存储多个 Subscriber 对象的数组
+ subscribe(s: Subscriber)
// 公有 函数名(参数名: 参数类型)

  1. 发布者(Publisher)向其他对象发送信息。Notify() 会在报社自身状态改变 或 执行特定函数后发生。Publisher 类中包含 Detach() 和 Attach(),允许订阅者离开或加入。
  2. 新事件发生时,报社会遍历订阅列表 list_subscriber_,并调用每个订阅者的 Notify(),该方法在 ISubscriber(订阅者接口)声明。
  3. 订阅者接口(ISubscriber):大多数情况,该接口只包含一个 Update() 方法,该方法拥有多个参数来传递信息。
  4. 具体订阅者(Subscriber):执行一些操作回应发布者的通知。所有具体订阅者都实现了同样的接口,因此发布者不需要与具体的类耦合。
  5. 订阅者需要一些上下文信息来正确的更新。所以,发布者要将一些上下文数据,作为 Notify() 的参数传递给订阅者。发布者也可将自身作为参数传递,以便订阅者直接获取数据。
  6. 客户端(Client):分别创建发布者和订阅者,然后为订阅者注册发布者的更新。

🏦观察者 -- 适用情况

  • 当一个对象状态的改变,需要改变其他对象;或实际对象是事先未知的或动态变化时,可以用观察者模式
  • 比如说,使用图形界面时,用户创建了自定义按钮类,并允许客户端在按钮中注入自定义代码,这样当用户按下按钮,就会触发这些代码

观察者模式允许任何实现了订阅者接口的对象,订阅发布者对象的事件通知

你可以在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码

当应用中的一些对象必须观察其他对象时,可使用观察者模式

但只能在有限时间或特定情况下使用

订阅列表是动态的,因此订阅者可以随时加入或离开该列表

🐟实现方式

  1. 据业务逻辑,将 观察者模式 拆分为两部分:
    a. 独立于其他代码的核心功能(发布者)
    b. 其他代码(一组订阅者类)
  2. 声明订阅者接口:该接口至少应声明一个 update() 方法
  3. 声明发布者接口:并定义一些接口用于在列表中添加 / 删除订阅对象(注意!发布者必须且只能通过订阅者接口和订阅对象进行交互)
  4. 定存放实际订阅列表的位置,并实现订阅方法:
    a. 因为所有类型的发布者都一样,那么,列表应该放置在直接扩展自发布者接口(IPublisher)的具体发布者(Publisher)中。具体发布者会扩展 接口类,从而继承所有的订阅行为
    b. 但是,如果你需要在现有的类层次结构中应用观察者模式,最好使用组合的方式:将订阅逻辑放入一个独立的对象,然后让所有实际订阅者使用该对象
  5. 创建具体发布者类(Publisher):每次发生重要事件,发布者都要通过所有订阅者
  6. 体订阅者类(Subscriber)中实现通知更新的方法:
    a. 订阅者需要一些与事件相关的上下文数据,这些数据(message_from_publisher)作为通知方法的参数来传递
    b. 另一种选择:订阅者从通知中获取 所有数据,此时发布者通过更新方法,将自身传递出去
    c. 还有一种不太灵活的方法:通过构造函数,将发布者与订阅者永久联系起来
  7. 客户端生成所需的全部订阅者,并到相应发布者完成注册工作

😖优缺点

优点

1,开闭原则

你不用修改任何发布者代码就能引用新的订阅者类(良好的扩展性)

(如果是发布者接口,就能轻松引入发布者类)

2,可以在运行时建立对象之间的联系(稳定的信息更新传递机制)

3,耦合双方依赖于抽象,不需要了解具体

缺点

 1,订阅者(也称观察者)的通知顺序是随机的(所以在设计观察者模式时,不要依赖于订阅者的通知顺序,这回让代码变得脆弱)

a. 订阅者运行时动态注册,那么注册顺序可能会变

b. 部分订阅者的注销,会影响剩下订阅者的通知顺序

c. 发布者可以以任何顺序通知定于这

2,某个订阅者出现卡顿,可能会影响整个进程,一般采用异步机制处理,同时注意线程安全

3,订阅者过多时,挨个通知每个订阅者,耗时较长

🔚与其他模式的联系

责任链模式命令模式中介者模式观察者模式 ---- 是用于处理请求发送者和接收者间的不同连接方式

  • 责任链:按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
  • 命令:在发送者和接收者之间建立单向连接
  • 中介者:清除发送者和接收者之间的直接连接,强制它们通过一个中介对象进行间接沟通
  • 观察者:允许接收者动态的订阅或取消接收要求

中介者 和 观察者的区别往往很难记住,具体的:

  1. 中介者的主要目标是,消除一系列系统组间之间的相互依赖。这些组间依赖于同一个中介者对象
  2. 观察者的目标是,在对象间建立动态的单向连接,使得部分对象也可作为其他对象的附属发挥作用
  3. 有一种流行的中介者模式的实现,依赖于观察者模式:
    a. 中介者对象担当发布者的角色,其他组件作为订阅者,可以订阅中介者的事件或取消订阅
    b. 当中介者用这种方式实现时,它看起来和观察者一样
  4. 你可能会困惑,此时可以采用其他方式来实现中介者。比如,永久的将所有组件链接到同一个中介者对象。这种实现方式和观察者长的很像,但它仍然是中介者模式
  5. 假设有一个程序,它所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序就没有了中心化的中介者对象,只有一些分布式的观察者

参考文章👇

C++ 观察者模式讲解和代码示例 (refactoringguru.cn)

InterviewGuide大厂面试真题

设计模式之观察者模式(C++)-阿里云开发者社区 (aliyun.com)

观察者模式 (datawhalechina.github.io)

在 IDE 中使用 GitHub Copilot Chat - GitHub 文档

相关文章:

观察者模式 C++

&#x1f442; Honey Honey - 孙燕姿 - 单曲 - 网易云音乐 目录 &#x1f33c;前言 &#x1f33c;描述 &#x1f382;问题 &#x1f4aa;解决方案 &#x1f232;现实场景 代码 场景1 -- 报纸发行 场景 解释 代码 场景2 -- 气象资料发布 场景3 -- 过红绿灯 &#x…...

每日一题 --- 删除字符串中的所有相邻重复项[力扣][Go]

删除字符串中的所有相邻重复项 题目&#xff1a;1047. 删除字符串中的所有相邻重复项 给出由小写字母组成的字符串 S&#xff0c;重复项删除操作会选择两个相邻且相同的字母&#xff0c;并删除它们。 在 S 上反复执行重复项删除操作&#xff0c;直到无法继续删除。 在完成所…...

前端三剑客 —— CSS (第四节)

目录 内容回顾&#xff1a; 1.常见样式 2.特殊样式 特殊样式 过滤效果 动画效果 动画案例&#xff1a; 渐变效果 其他效果&#xff1a; 多列效果 字体图标&#xff08;icon&#xff09; 内容回顾&#xff1a; 1.常见样式 text-shadow x轴 y轴 阴影的模糊程度 阴影的…...

Linux文件IO(3):使用文件IO进行文件的打开、关闭、读写、定位等相关操作

目录 1. 文件IO的概念 2. 文件描述符概念 3. 函数介绍 3.1 文件IO-open函数 3.2 文件IO-close函数 3.3 文件IO-read函数 3.4 文件IO-write函数 3.5 文件IO-lseek函数 4. 代码练习 4.1 要求 4.2 具体实现代码 4.3 测试结果 5. 总结 1. 文件IO的概念 posix(可移植操作系统接…...

Vite 项目中环境变量的配置和使用

Vite 项目中环境变量的声明 我们要在 Vite 项目中进行环境变量的声明&#xff0c;那么需要在项目的根目录下&#xff0c;新建 .env.[mode] 文件用于声明环境变量&#xff0c;如&#xff1a; .env.test 文件用于测试环境下项目全局变量的声明.env.dev 文件用于开发环境下项目全…...

C++读取.bin二进制文件

C读取.bin二进制文件 在C中&#xff0c;可以使用文件输入/输出流来进行二进制文件的读写操作&#xff0c;方便数据的保存和读写。 //C读取bin二进制文件 int read_bin() {std::ifstream file("data_100.bin", std::ios::in | std::ios::binary);if (file) {// 按照…...

【ZZULIOJ】1038: 绝对值最大(Java)

目录 题目描述 输入 输出 样例输入 Copy 样例输出 Copy code 题目描述 输入3个整数&#xff0c;输出绝对值最大的那个数。 输入 输入包含3个int范围内的整数&#xff0c;用空格隔开。 输出 输出三个数中绝对值最大的数&#xff0c;单独占一行。若绝对值最大的数不唯…...

递归算法讲解2

前情提要 上一篇递归算法讲解在这里 递归算法讲解&#xff08;结合内存图&#xff09; 没看过的小伙伴可以进去瞅一眼&#xff0c;谢谢&#xff01; 递归算法的重要性 递归算法是非常重要的&#xff0c;如果想要进大厂&#xff0c;以递归算法为基础的动态规划是必考的&…...

机器学习第33周周报Airformer

文章目录 week33 AirFormer摘要Abstract一、论文的前置知识1. 多头注意力机制&#xff08;MSA&#xff09;2. 具有潜变量的变分模型 二、文献阅读1. 题目2. abstract3. 问题与模型阐述3.1 问题定义3.2 模型概述3.3 跨空间MSA&#xff08;DS-MSA&#xff09;3.4 时间相关MSA&…...

设计模式(12):代理模式

一.核心作用 通过代理&#xff0c;控制对对象的访问;可以详细控制访问某个对象的方法&#xff0c;在调用这个方法前做前置处理&#xff0c;调用这个方法后做后置处理。 二.核心角色 抽象角色&#xff1a; 定义代理角色和真实角色的公共对外方法;真实角色&#xff1a; 实现抽…...

前端9种图片格式基础知识, 你应该知道的

彩色深度 彩色深度标准通常有以下几种&#xff1a; 8位色&#xff0c;每个像素所能显示的彩色数为2的8次方&#xff0c;即256种颜色。16位增强色&#xff0c;16位彩色&#xff0c;每个像素所能显示的彩色数为2的16次方&#xff0c;即65536种颜色。24位真彩色&#xff0c;每个…...

ChatGPT 与 OpenAI 的现代生成式 AI(上)

原文&#xff1a;Modern Generative AI with ChatGPT and OpenAI Models 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 序言 本书以介绍生成式 AI 领域开始&#xff0c;重点是使用机器学习算法创建新的独特数据或内容。它涵盖了生成式 AI 模型的基础知识&#xff0c…...

全量知识系统 程序详细设计之架构设计:一个信息系统架构

统架构&#xff0c;整体设计分成了三部分--三种方面&#xff1a;信息nformation、系统Syste 原文 以下是对全知系统程序详细设计需要的架构规划的考虑。 全知系统架构是一个信息系统架构&#xff0c;整体设计分成了三部分&#xff08;三种“方面”&#xff09;&#xff1a;信…...

从零开始:成功进入IT行业的方法与技巧

如今&#xff0c;信息技术&#xff08;IT&#xff09;行业成为了就业市场上的热门领域。由于其快速发展和广阔的职业机会&#xff0c;许多人希望能够进入这个行业。然而&#xff0c;对于没有任何相关背景知识的人来说&#xff0c;要成功进入IT行业可能会面临一些挑战。本文将分…...

SpringCloud - 如何本地调试不会注册到线上环境(Nacos)?

问题描述 有时候我们需要本地调试注册到 Nacos 上&#xff0c;但是会影响线上服务的 Feign 请求打到本地导致不通影响了线上业务。 原因分析 一般最传统的解决方案就是修改本地 bootstrap.yml 的 spring.cloud.nacos.discovery.namespace spring:application:name: app-serv…...

1.9 面试经典150题 除自身以外数组的乘积

除自身以外数组的乘积 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0…...

【美团笔试题汇总】2023-09-02-美团春秋招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新美团近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…...

小黑逆向爬虫探索与成长之路:小黑独立破解毛毛租数据加密与解密

前言 有道和招标网的加密入口定位在前面两期做了详细的介绍&#xff0c;本小结将通过简单的关键词搜索定位到加密与解密入口 数据接口寻找与请求 根据响应数据长度&#xff0c;确定数据接口&#xff0c;发现传入的参数需要加密&#xff0c;响应的结果需要解密&#xff0c;后…...

Generative Question-Answering with Long-Term Memory

...

深入浅出 -- 系统架构之微服务架构常见的六种设计模式

面向服务的架构&#xff08;SOA&#xff09; 面向服务的架构&#xff08;SOA&#xff09;是一种设计方法&#xff0c;也是一个组件模型&#xff0c;它将应用程序的不同功能单元&#xff08;称为服务&#xff09;通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

生信服务器 | 做生信为什么推荐使用Linux服务器?

原文链接&#xff1a;生信服务器 | 做生信为什么推荐使用Linux服务器&#xff1f; 一、 做生信为什么推荐使用服务器&#xff1f; 大家好&#xff0c;我是小杜。在做生信分析的同学&#xff0c;或是将接触学习生信分析的同学&#xff0c;<font style"color:rgb(53, 1…...

项目进度管理软件是什么?项目进度管理软件有哪些核心功能?

无论是建筑施工、软件开发&#xff0c;还是市场营销活动&#xff0c;项目往往涉及多个团队、大量资源和严格的时间表。如果没有一个系统化的工具来跟踪和管理这些元素&#xff0c;项目很容易陷入混乱&#xff0c;导致进度延误、成本超支&#xff0c;甚至失败。 项目进度管理软…...

实现p2p的webrtc-srs版本

1. 基本知识 1.1 webrtc 一、WebRTC的本质&#xff1a;实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力&#xff0c;二者在架构设计和功能定位上高度相似&#xff1a; 分层协议栈架构 Linux网络协议栈&#xff1a;从底层物理层到应用层&#xff08;如…...

Pycharm的终端无法使用Anaconda命令行问题详细解决教程

很多初学者在Windows系统上安装了Anaconda后&#xff0c;在PyCharm终端中运行Conda命令时&#xff0c;会遇到以下错误&#xff1a; conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保…...

设备健康管理的范式革命:中讯烛龙全链路智能守护系统

当工业设备的“亚健康”状态导致隐性产能损失高达23%时&#xff0c;中讯烛龙推出 ​​“感知-诊断-决策-闭环”四位一体解决方案&#xff0c;让设备全生命周期健康管理成为企业增长的隐形引擎。 一、行业痛点&#xff1a;传统运维的三大断层 1. 健康感知盲区 某风电场因无法捕…...