C++ 设计模式——观察者模式

观察者模式

    • 观察者模式
      • 主要组成部分
      • 例一:工作流程
        • 第一步:定义观察者接口
        • 第二步:定义主题接口
        • 第三步:实现具体主题
        • 第四步:实现具体观察者
        • 第五步:主函数
        • UML 图
          • UML 图解析
      • 例二:工作流程
        • 第一步:定义观察者接口
        • 第二步:定义主题接口
        • 第三步:实现具体主题(玩家类)
        • 第四步:实现具体观察者(聊天通知器)
        • 第五步:主函数
        • UML 图
          • UML 图解析
      • 优缺点
      • 适用场景
      • 例一:完整代码
      • 例二:完整代码

观察者模式

观察者模式(Observer Pattern)是一种常用的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象(主题)状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

引入“观察者”设计模式的定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。

主要组成部分

  1. 主题(Subject)

    • 也叫观察目标,指被观察的对象。
    • 维护观察者列表,提供添加、删除观察者的方法。
    • 当状态变化时,通知所有的观察者。
  2. 观察者(Observer)

    • 定义一个更新接口,以便主题在状态变化时调用。
    • 每个观察者都可以根据主题的变化更新自身状态。
  3. 具体主题(ConcreteSubject)

    • 实现主题的接口,维护具体的状态。
    • 在状态变化时,调用通知方法来更新所有观察者。
  4. 具体观察者(ConcreteObserver)

    • 实现观察者接口,更新自身状态以反映主题的变化。
    • 每个观察者可以根据主题的状态执行特定的操作。

例一:工作流程

  1. 观察者注册到主题:在主函数中,使用 attach 方法将观察者注册到主题。
  2. 主题的状态发生变化:调用 setState 方法改变主题的状态。
  3. 主题调用通知方法:在 setState 方法中,调用 notify 方法通知所有注册的观察者。
  4. 观察者接收到通知后,更新自身状态:每个观察者实现 update 方法,接收到通知后更新自身状态并输出。
第一步:定义观察者接口

首先,定义一个观察者接口,所有观察者都需要实现这个接口。

// 观察者接口
class Observer {
public:virtual void update(int state) = 0; // 更新接口
};
第二步:定义主题接口

接下来,定义一个主题接口,主题需要维护观察者列表,并提供添加、删除观察者的方法。

// 主题接口
class Subject {
public:virtual void attach(Observer* observer) = 0; // 添加观察者virtual void detach(Observer* observer) = 0; // 移除观察者virtual void notify() = 0; // 通知观察者
};
第三步:实现具体主题

实现一个具体主题类,维护观察者列表和状态,并在状态变化时通知观察者。

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove// 具体主题
class ConcreteSubject : public Subject {
private:std::vector<Observer*> observers; // 观察者列表int state; // 主题的状态public:void attach(Observer* observer) override {observers.push_back(observer); // 添加观察者}void detach(Observer* observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者}void notify() override {for (Observer* observer : observers) {observer->update(state); // 通知所有观察者}}void setState(int newState) {state = newState; // 更新状态notify(); // 状态变化时通知观察者}int getState() const {return state; // 获取当前状态}
};
第四步:实现具体观察者

实现观察者类,更新自身状态以反映主题的变化。

// 具体观察者
class ConcreteObserver : public Observer {
private:std::string name; // 观察者名称int observedState; // 观察者的状态public:ConcreteObserver(const std::string& name) : name(name) {}void update(int state) override {observedState = state; // 更新观察者的状态std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;}
};
第五步:主函数

在主函数中,创建主题和观察者对象,注册观察者,并演示状态变化。

// 主函数
int main() {ConcreteSubject subject; // 创建具体主题ConcreteObserver observer1("Observer 1"); // 创建观察者1ConcreteObserver observer2("Observer 2"); // 创建观察者2subject.attach(&observer1); // 注册观察者1subject.attach(&observer2); // 注册观察者2subject.setState(1);  // 状态变化,通知所有观察者subject.setState(2);  // 状态变化,通知所有观察者return 0;
}
UML 图

观察者模式 UML 图一描述

UML 图解析

Subject(主题):也叫作观察目标,指被观察的对象。这里指 Subject 类。提供增加和删除观察者对象的接口,如 attachdetach

ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 ConcreteSubject 类。

Observer(观察者):定义了观察者的接口,所有具体观察者都需要实现这个接口。当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Observer 类。

ConcreteObserver(具体观察者):实现了观察者接口,并注册到主题。当具体目标状态发生变化时,自身会接到通知。这里指 ConcreteObserver 类。

例二:工作流程

下面是逐步实现游戏聊天系统,使用观察者模式来管理玩家之间的聊天信息,每个玩家可以选择加入或退出家族聊天,系统会自动处理通知。将按照设计思路,逐步构建代码。

  1. 观察者注册到被观察者:在主函数中,使用 addToList 方法将玩家注册到聊天通知器。
  2. 被观察者的状态发生变化:调用 SayWords 方法改变聊天内容。
  3. 被观察者调用通知方法:在 SayWords 方法中,调用 notify 方法通知所有注册的玩家。
  4. 观察者接收到通知后,更新自身状态:每个玩家实现 NotifyWords 方法,接收到通知后输出聊天信息。
第一步:定义观察者接口

首先,定义一个观察者接口,所有玩家都需要实现这个接口,以接收聊天信息的更新通知。

// 观察者接口
class Notifier {
public:virtual void addToList(Fighter* player) = 0; // 添加玩家virtual void removeFromList(Fighter* player) = 0; // 移除玩家virtual void notify(Fighter* talker, const std::string& message) = 0; // 通知玩家virtual ~Notifier() {}
};
第二步:定义主题接口

接下来,定义一个定义主题接口,玩家需要维护聊天通知器,并提供添加、删除玩家的方法。

// 被观察者接口
class Fighter {
public:virtual void NotifyWords(Fighter* talker, const std::string& message) = 0; // 更新接口virtual void SetFamilyID(int id) = 0; // 设置家族IDvirtual int GetFamilyID() const = 0; // 获取家族IDvirtual ~Fighter() {}
};
第三步:实现具体主题(玩家类)

实现一个具体玩家类,维护玩家名称和家族ID,并在收到聊天信息时更新状态。

#include <iostream>
#include <string>
#include <list>
#include <map>
#include <algorithm> // 用于 std::remove// 具体主题:玩家
class ConcreteFighter : public Fighter {
private:std::string name; // 玩家名称int familyID; // 家族IDpublic:ConcreteFighter(const std::string& playerName) : name(playerName), familyID(-1) {}void SetFamilyID(int id) override {familyID = id;}int GetFamilyID() const override {return familyID;}void NotifyWords(Fighter* talker, const std::string& message) override {std::cout << "玩家 " << name << " 收到了来自 " << talker->GetFamilyID() << " 的消息: " << message << std::endl;}
};
第四步:实现具体观察者(聊天通知器)

实现聊天通知器类,维护玩家列表,并在聊天内容变化时通知玩家。

// 具体观察者:聊天通知器
class TalkNotifier : public Notifier {
private:std::map<int, std::list<Fighter*>> familyList; // 家族ID与玩家列表的映射public:void addToList(Fighter* player) override {int familyID = player->GetFamilyID();if (familyID != -1) {familyList[familyID].push_back(player); // 添加玩家到对应家族}}void removeFromList(Fighter* player) override {int familyID = player->GetFamilyID();if (familyID != -1) {familyList[familyID].remove(player); // 移除玩家}}void notify(Fighter* talker, const std::string& message) override {int familyID = talker->GetFamilyID();if (familyID != -1) {for (Fighter* player : familyList[familyID]) {player->NotifyWords(talker, message); // 通知所有玩家}}}
};
第五步:主函数

在主函数中,创建玩家和聊天通知器对象,注册玩家,并演示聊天信息的通知过程。

// 主函数
int main() {// 创建聊天通知器TalkNotifier notifier;// 创建玩家ConcreteFighter player1("张三");player1.SetFamilyID(100);ConcreteFighter player2("李四");player2.SetFamilyID(100);ConcreteFighter player3("王五");player3.SetFamilyID(200);// 注册玩家到通知器notifier.addToList(&player1);notifier.addToList(&player2);notifier.addToList(&player3);// 玩家发送消息player1.SayWords("大家集合,准备进攻!", &notifier); // 张三发送消息player2.SayWords("听从指挥,前往目标!", &notifier); // 李四发送消息// 移除玩家notifier.removeFromList(&player2); // 移除李四// 再次发送消息player1.SayWords("李四已经不在了,继续行动!", &notifier); // 张三发送消息return 0;
}
UML 图

观察者模式 UML 图2

UML 图解析

Subject(主题):也叫作观察目标,指被观察的对象。这里指 Notifier 类。提供增加和删除观察者对象的接口,如 addToListremoveFromList

ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 TalkNotifier 子类。

Observer(观察者):当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Fighter 类。

ConcreteObserver(具体观察者):调用观察目标的 addToList 成员函数将自身加入到观察者列表中,当具体目标状态发生变化时,自身会接到通知(NotifyWords 成员函数会被调用)。这里指 F_WarriorF_Mage 子类。

优缺点

优点

  • 松耦合:观察者和主题之间的关系是松散的,降低了模块间的耦合度。
  • 动态添加观察者:可以在运行时增加或减少观察者。
  • 多对一的通知:一个主题可以通知多个观察者,适合事件驱动的场景。

缺点

  • 通知开销:如果观察者数量较多,通知开销可能较大。
  • 循环依赖:若观察者和主题相互依赖,可能导致循环更新。

适用场景

  • 事件处理系统:GUI框架中,用户操作(如点击按钮)会触发事件,多个组件需要对这些事件做出反应。

  • 数据绑定:在MVC(模型-视图-控制器)架构中,当模型数据变化时,视图需要自动更新以反映最新的数据。

  • 发布-订阅系统:在消息传递系统中,多个订阅者会对特定主题的消息做出反应,发布者只需发布消息而不需要关心订阅者的具体实现。

  • 状态监控:在监控系统中,多个观察者需要监控同一状态(如温度传感器的读数),当状态变化时,所有观察者都能及时获取更新。

  • 社交媒体通知:当用户发布新内容时,所有关注该用户的粉丝会收到更新通知。

例一:完整代码

将以上步骤组合在一起,完整代码如下:

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove// 观察者接口
class Observer {
public:virtual void update(int state) = 0; // 更新接口
};// 主题接口
class Subject {
public:virtual void attach(Observer* observer) = 0; // 添加观察者virtual void detach(Observer* observer) = 0; // 移除观察者virtual void notify() = 0; // 通知观察者
};// 具体主题
class ConcreteSubject : public Subject {
private:std::vector<Observer*> observers; // 观察者列表int state; // 主题的状态public:void attach(Observer* observer) override {observers.push_back(observer); // 添加观察者}void detach(Observer* observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者}void notify() override {for (Observer* observer : observers) {observer->update(state); // 通知所有观察者}}void setState(int newState) {state = newState; // 更新状态notify(); // 状态变化时通知观察者}int getState() const {return state; // 获取当前状态}
};// 具体观察者
class ConcreteObserver : public Observer {
private:std::string name; // 观察者名称int observedState; // 观察者的状态public:ConcreteObserver(const std::string& name) : name(name) {}void update(int state) override {observedState = state; // 更新观察者的状态std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;}
};// 主函数
int main() {ConcreteSubject subject; // 创建具体主题ConcreteObserver observer1("Observer 1"); // 创建观察者1ConcreteObserver observer2("Observer 2"); // 创建观察者2subject.attach(&observer1); // 注册观察者1subject.attach(&observer2); // 注册观察者2subject.setState(1);  // 状态变化,通知所有观察者subject.setState(2);  // 状态变化,通知所有观察者return 0;
}

例二:完整代码

#include <iostream>
#include <list>
#include <map>using namespace std;class Fighter; //类前向声明
class Notifier //通知器父类
{
public:virtual void addToList(Fighter* player) = 0; //把要被通知的玩家加到列表中virtual void removeFromList(Fighter* player) = 0; //把不想被通知的玩家从列表中去除virtual void notify(Fighter* talker, string tmpContent) = 0; //通知的一些细节信息virtual ~Notifier() {}
};
class Fighter
{
public:Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName)//构造函数{m_iFamilyID = -1; //-1表示没加入任何家族}virtual ~Fighter() {} //析构函数public:void SetFamilyID(int tmpID)  //加入家族时设置家族ID{m_iFamilyID = tmpID;}int GetFamilyID() //获取家族ID{return m_iFamilyID;}void SayWords(string tmpContent, Notifier* notifier) //玩家说了某句话{notifier->notify(this, tmpContent);}//通知该玩家接收到其他玩家发送来的聊天信息,虚函数,子类可以覆盖以实现不同的动作virtual void NotifyWords(Fighter* talker, string tmpContent){//显示信息cout << "玩家:" << m_sPlayerName << " 收到了玩家:" << talker->m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl;}
private:int m_iPlayerID; //玩家ID,全局唯一string m_sPlayerName;  //玩家名字int m_iFamilyID;   //家族ID
};
//“战士”类玩家,父类为Fighter
class F_Warrior :public Fighter
{
public:F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};//“法师”类玩家,父类为Fighter
class F_Mage :public Fighter
{
public:F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};//----------------------------------
class TalkNotifier :public Notifier  //聊天信息通知器
{
public://将玩家增加到家族列表中来virtual void addToList(Fighter* player){int tmpfamilyid = player->GetFamilyID();if (tmpfamilyid != -1) //加入了某个家族{auto iter = m_familyList.find(tmpfamilyid);if (iter != m_familyList.end()){//该家族id在map中已经存在iter->second.push_back(player); //直接把该玩家加入到该家族}else{//该家族id在map中不存在list<Fighter*> tmpplayerlist;m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist));//以该家族id为key,增加条目到map中m_familyList[tmpfamilyid].push_back(player); //向该家族中增加第一个玩家}}}//将玩家从家族列表中删除virtual void removeFromList(Fighter* player){int tmpfamilyid = player->GetFamilyID();if (tmpfamilyid != -1) //加入了某个家族{auto iter = m_familyList.find(tmpfamilyid);if (iter != m_familyList.end()){m_familyList[tmpfamilyid].remove(player);}}}//家族中某玩家说了句话,调用该函数来通知家族中所有人virtual void notify(Fighter* talker, string tmpContent) //talker是讲话的玩家{int tmpfamilyid = talker->GetFamilyID();if (tmpfamilyid != -1){auto itermap = m_familyList.find(tmpfamilyid);if (itermap != m_familyList.end()){//遍历该玩家所属家族的所有成员for (auto iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist){(*iterlist)->NotifyWords(talker, tmpContent);}}}}
private://map中的key表示家族id,value代表该家族中所有玩家列表map<int, list<Fighter*> > m_familyList; //增加#include <map>
};int main()
{//创建游戏玩家Fighter* pplayerobj1 = new F_Warrior(10, "张三");pplayerobj1->SetFamilyID(100);Fighter* pplayerobj2 = new F_Warrior(20, "李四");pplayerobj2->SetFamilyID(100);Fighter* pplayerobj3 = new F_Mage(30, "王五");pplayerobj3->SetFamilyID(100);Fighter* pplayerobj4 = new F_Mage(50, "赵六");pplayerobj4->SetFamilyID(200);//创建通知器Notifier* ptalknotify = new TalkNotifier();//玩家增加到家族列表中来,这样才能收到家族聊天信息ptalknotify->addToList(pplayerobj1);ptalknotify->addToList(pplayerobj2);ptalknotify->addToList(pplayerobj3);ptalknotify->addToList(pplayerobj4);//某游戏玩家聊天,同族人都应该收到该信息pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!", ptalknotify);cout << "王五不想再收到家族其他成员的聊天信息了---" << endl;ptalknotify->removeFromList(pplayerobj3); //将王五从家族列表中删除pplayerobj2->SayWords("请大家听从族长的调遣,前往沼泽地!", ptalknotify);//释放资源delete pplayerobj1;delete pplayerobj2;delete pplayerobj3;delete pplayerobj4;delete ptalknotify;return 0;
}

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

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

相关文章

动态规划之买卖股票篇-代码随想录算法训练营第三十八天| 买卖股票的最佳时机ⅠⅡⅢⅣ,309.最佳买卖股票时机含冷冻期,714.买卖股票的最佳时机含手续费

121. 买卖股票的最佳时机 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 讲解视频&#xff1a; 动态规划之 LeetCode&#xff1a;121.买卖股票的最佳时机1 题目描述&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定…

[数据集][目标检测]电力场景输电线异物检测数据集VOC+YOLO格式2060张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2060 标注数量(xml文件个数)&#xff1a;2060 标注数量(txt文件个数)&#xff1a;2060 标注…

K8s节点状态 NotReady排查

k8s节点由 Ready变成 NotReady izbp12ghzy6koox6fqt0suz NotReady slave 97d v1.23.3 izbp12ghzy6koox6fqt0svz Ready control-plane,master 98d v1.23.3节点进入 NotReady 状态可能是由于多种原因引起的&#xff0c;尤其是在资源过量分配&am…

环绕音效是什么意思,电脑环绕音效怎么开

Boom 3D是一款专业的音效增强软件&#xff0c;它拥有先进的音效处理技术和丰富的音效设置选项&#xff0c;可以为用户打造出高度定制化的音频体验&#xff0c;Boom 3D还拥有简洁直观的界面&#xff0c;操作简单易懂&#xff0c;即使是音频技术的新手也能轻松上手。本篇文章就将…

微信小程序引入全局环境变量

有时候一套代码要在多个小程序appId下使用,其中又有一些数据(文字)需要做区分.可以使用下面的方法 把要配置的数据以export default 形式导出 在app.js中,引入project.config.0.js文件,将导出的数据放在globalData中 在页面目录中,即可利用getApp()方法使用全局变量 也可以放数…

buuctf [HDCTF2019]Maze

前言&#xff1a;做题笔记。 常规 下载 解压 查壳 脱壳后用32IDA Pro打开。 得&#xff0c;迷宫类型的题目。(字符串有说。) 咳&#xff0c;此前思路对半分不行了。。。 合理猜测步数为&#xff1a;14。 那可以看看7 * 10的迷宫类型。(手动猜测的时候去取倍数如&#xff1a;0 2…

【三维深度补全模型】PENet

【版权声明】本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 参考书籍&#xff1a;《人工智能点云处理及深度学习算法》 本文为专栏《Python三维点云实战宝典》系列文章&#xff0c;专栏介绍地址“【python三维深度学习】python…

shell脚本中$0 $1 $# $@ $* $? $$ 的各种符号意义详解

文章目录 一、概述1.1、普通字符1.2、元字符 二、转义字符$2.1、实例12.2、实例22.3、实例32.4、实例42.5、实例5 三、linux命令执行返回值$?说明 一、概述 shell中有两类字符&#xff1a;普通字符、元字符。 1.1、普通字符 在Shell中除了本身的字面意思外没有其他特殊意义…

校友林小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;树木管理管理&#xff0c;所属科管理&#xff0c;树木领取管理&#xff0c;树跟踪状态管理&#xff0c;用户信息统计管理&#xff0c;树木捐款管理&#xff0c;留言板管理 微信端…

基于vue框架的毕业设计管理系统5n36i(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,教师,课题信息,题目分类,选题信息,任务书,中期检查,提交论文,论文成绩,答辩成绩,校园公告,教研主任,申报课题 开题报告内容 基于Vue框架的毕业设计管理系统开题报告 一、引言 随着高等教育的不断发展&#xff0c;毕业设计作为培…

AITDK SEO扩展:为网站优化提供一站式解决方案

AITDK SEO扩展&#xff1a;为网站优化提供一站式解决方案 想提升你的网站在搜索引擎中的排名&#xff1f;让我们来看看AITDK SEO扩展&#xff0c;它是你网站优化的得力助手&#xff01;在这篇文章中&#xff0c;我将为你介绍AITDK SEO扩展的功能特点&#xff0c;以及它如何帮助…

警惕!低血糖来袭,这些“隐形信号”你中招了吗?

在这个快节奏的时代&#xff0c;我们往往忙于工作、学习与生活&#xff0c;却容易忽视身体发出的微妙警告。其中&#xff0c;低血糖作为一种常见但易被忽视的健康问题&#xff0c;正悄悄影响着许多人的生活质量。今天&#xff0c;就让我们一起揭开低血糖的神秘面纱&#xff0c;…

Java:包装类

文章目录 引入原因包装类代码演示包装类的其他常见操作 使用到的有关ArrayList的方法 引入原因 泛型和集合不支持基本数据类型&#xff0c;只能支持引用数据类型 包装类 包装类就是把基本类型的数据包装成对象 就是说不再是一个int类型的数&#xff0c;而是一个Integer类型的…

Stable Diffusion 使用详解(8)--- layer diffsuion

背景 layer diffusion 重点在 layer&#xff0c;顾名思义&#xff0c;就是分图层的概念&#xff0c;用过ps 的朋友再熟悉不过了。没使用过的&#xff0c;也没关系&#xff0c;其实很简单&#xff0c;本质就是各图层自身的编辑不会影响其他图层&#xff0c;这好比OS中运行了很多…

文件树控件开发

文件树控件和获取驱动信息功能 然后添加上查看文件信息的按钮 双击这个按钮添加上如下代码 void CRemoteClientDlg::OnBnClickedBtnFileinfo() {int ret SendCommandPacket(1);if (ret -1) {AfxMessageBox(_T("命令处理失败!!!"));return;}ClientSocket* pClient…

AI大模型独角兽 MiniMax 基于 Apache Doris 升级日志系统,PB 数据秒级查询响应

作者&#xff1a;MiniMax 基础架构研发工程师 Koyomi、香克斯、Tinker 导读&#xff1a;早期 MiniMax 基于 Grafana Loki 构建了日志系统&#xff0c;在资源消耗、写入性能及系统稳定性上都面临巨大的挑战。为此 MiniMax 开始寻找全新的日志系统方案&#xff0c;并基于 Apache …

Ubuntu 22安装和配置PyCharm详细教程(图文详解)

摘要&#xff1a;本文提供了在 Ubuntu 22 上通过官方 .tar.gz 文件安装 PyCharm 的详细教程。包括从 JetBrains 官方网站下载适合的 PyCharm 版本&#xff08;Community 或 Professional&#xff09;&#xff0c;在终端中解压并将其移动到 /opt 目录&#xff0c;配置适当的权限…

【C++题解】1147. 求1/1+1/2+2/3+3/5+5/8+8/13+13/21……的前n项的和

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1147. 求1/11/22/33/55/88/1313/21……的前n项的和 类型&#xff1a;函数 题目描述&#xff1a; 求1/11/22/33/55/88/1313/2121/34…的前 n 项的和。 输入&#xff1a; 输入一个…

Unity读取Android本地图片

unity读取Android本地图片 一、安卓读取路径 安卓路径&#xff1a;“file:///storage/emulated/0/”自己图片的路径 例&#xff1a;“file:///storage/emulated/0/small.jpg” 二、unity搭建 使用UI简单搭个界面 三、新建一个脚本 代码内容如下 using System.Collectio…

谷粒商城实战笔记-251-商城业务-消息队列-Exchange类型

文章目录 一&#xff0c;Exchange二&#xff0c;Exchange的四种类型1&#xff0c;direct2&#xff0c;fanout3&#xff0c;topic 三&#xff0c;实操1&#xff0c;创建一个exchange2&#xff0c;创建一个queue3&#xff0c;将queue绑定到exchange 一&#xff0c;Exchange AMQP …