观察者模式 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 文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/794578.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频分块上传Vue3+SpringBoot3+Minio

文章目录 一、简化演示分块上传、合并分块断点续传秒传 二、更详细的逻辑和细节问题可能存在的隐患 三、代码示例前端代码后端代码 一、简化演示 分块上传、合并分块 前端将完整的视频文件分割成多份文件块&#xff0c;依次上传到后端&#xff0c;后端将其保存到文件系统。前…

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) {// 按照…

【拓扑空间】示例及详解1

例1 度量空间的任意两球形邻域的交集是若干球形邻域的并集 Proof&#xff1a; 任取空间的两个球形邻域、&#xff0c;令 任取,令 球形领域 例2 规定X的子集族,证明是X上的一个拓扑 Proof&#xff1a; 1. 2., &#xff08;若干个球形邻域的并集都是的元素&#xff0c;元素…

SSM 项目学习(Vue3+ElementPlus+Axios+SSM)

文章目录 1 项目介绍1.1 项目功能/界面 2 项目基础环境搭建2.1 创建项目2.2 项目全局配置 web.xml2.3 SpringMVC 配置2.4 配置 Spring 和 MyBatis , 并完成整合2.5 创建表&#xff0c;使用逆向工程生成 Bean、XxxMapper 和 XxxMapper.xml2.6 注意事项和细节说明 3 实现功能 01-…

Java 开发者必备:JDK 版本详解与选择策略(含安装与验证)

1. JDK 版本 (Oracle Java SE 支持路线图) 数据来源&#xff1a;Oracle Java SE 支持路线图 | 甲骨文中国: https://www.oracle.com/cn/java/technologies/java-se-support-roadmap.html 版本GA DatePremier Support UntilExtended Support Until&#xff08;限 LTS&#xff09…

虚幻UE5数字孪生蓝图开发教程

一、背景 这几年&#xff0c;智慧城市/智慧交通/智慧水利等飞速发展&#xff0c;骑士特意为大家做了一个这块的学习路线。 二、这是学习大纲 1.给虚幻UE5初学者准备的智慧城市/数字孪生蓝图开发教程 https://www.bilibili.com/video/BV1894y1u78G 2.UE5数字孪生蓝图开发教学…

亚马逊跨境电商平台真人测评和自养号测评有什么区别?

下面来讲一下真人测评和自养号测评的优缺点有哪些 真人测评 优点&#xff1a;权重高&#xff0c;可以有效提升转化率 缺点&#xff1a;市面上的渠道良莠不齐&#xff0c;质量难以保证&#xff0c;且较难选择 真人测评是通过真人的买家在页面留下review的方式来提高权重&…

关于 QSound播放wav音频文件,播放失败“using null output device, none available” 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/137264493 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

MySQL介绍和安装

MySQL介绍和安装 文章目录 MySQL介绍和安装1.MySQL介绍2.MySQL安装2.1 主机初始化2.1.1 设置网卡名和ip地址2.1.2 配置镜像源2.1.3 关闭防火墙2.1.4 禁用SELinux2.1.5 设置时区 2.2 包安装2.2.1 Rocky和CentOS 安装 MySQL2.2.2 Ubuntu 安装 MySQL 2.3 二进制安装安装MySQL2.3.1…

【随笔】Git 高级篇 -- 整理提交记录(上)(十五)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

地质地貌卫星影像集锦(三 矿产资源篇)

1. 元古代沉积岩的抬升 这个地区位于Leigh Creek中部&#xff0c;距离澳大利亚南部的阿德莱德约500km&#xff0c;弗林德斯山脉的北面是Gawler克拉通。弗林德斯山脉是由元古代沉积岩抬升后形成的块体&#xff0c;在其之下的是寒武纪的岩石&#xff0c;它座落在距阿德莱德北…

Docker 容器编排技术解析与实践

探索了容器编排技术的核心概念、工具和高级应用&#xff0c;包括 Docker Compose、Kubernetes 等主要平台及其高级功能如网络和存储管理、监控、安全等。此外&#xff0c;文章还探讨了这些技术在实际应用中的案例&#xff0c;提供了对未来趋势的洞见。 一、容器编排介绍 容器编…

蓝桥杯(5):python动态规划DF[2:背包问题]

1 0-1背包介绍【每件物品只能拿1件或者不拿】 1.1 简介 贪心是不可以的&#xff01;&#xff01;&#xff01; 1.2 状态 及状态转移 转移解释&#xff1a;要么不选 则上一个直接转移过来【dp[i-1][j]】&#xff0c;要么是选这个之后体积为j 则上一个对应的就是【dp[i-1][j-wi]…

数据结构day2--双向链表

双向链表: 即可以从头遍历到尾部和从尾部遍历到头部的链表&#xff0c;每个结点包括两个链域&#xff1a;前驱指针域和后继指针域&#xff0c;所以比起单向链表&#xff0c;其可以在任意一个结点访问前后两个结点 关于双向链表的一个完整步骤为&#xff1a; 创建一个表头结构…

MySQL 全景图

前言 MySQL 是啥&#xff1f;我一直以为 MySQL 是数据库&#xff0c;直到我最近看了很多关于 MySQL 的文章、专栏以及书籍后&#xff0c;我对 MySQL 的才有了更加深刻的体会。 原来 MySQL 并不是数据库&#xff0c;又或者说&#xff0c;我认为“ MySQL 是数据库这种想法”是片…

该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系

该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系 这个去集群主机cm界面上看会出现这个错误 排查思路&#xff1a; 一般比较常见的原因可能是出问题的主机和集群主节点的时间对应不上了。还有就是cm agent服务出现问题了 去该主机的…

基于SpringBoot+Vue的汽车租赁管理系统的设计和实现【附源码】

1、系统演示视频&#xff08;演示视频&#xff09; 2、需要交流和学习请联系

【氮化镓】缓冲层结构对GaN HEMT射频性能的影响

【Effect of different layer structures on the RF performance of GaN HEMT devices】 研究总结&#xff1a; 本研究探讨了不同缓冲层结构对氮化镓高电子迁移率晶体管&#xff08;GaN HEMT&#xff09;射频性能的影响。通过对比三种不同缓冲层结构的GaN HEMT设备&#xff0…

B3631 单向链表(结构体模拟链表)

输入格式 第一行一个整数 q表示操作次数。 接下来 q行&#xff0c;每行表示一次操作&#xff0c;操作具体间题目描述。 输出格式 对于每个操作 2&#xff0c;输出一个数字&#xff0c;用换行隔开。 #include<iostream> #include<map> #include<algorithm> …

软著说明文档生成/辅助填写工具

软著说明文档生成/辅助填写工具&#xff0c;自行申请软著的话&#xff0c;软著60页源码还比较容易搞定&#xff0c;但是说明文档有格式和字数要求&#xff0c;就很烦。这个网站可以进行格式和内容的辅助填写&#xff0c;不用再把精力浪费到没用的调整格式上&#xff0c;网站地址…