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

【C++委托与事件】函数指针,回调机制,事件式编程与松耦合的设计模式(上)

前言

  • 上一次发文章已经是在两个月前了hhh,期间也是忙忙碌碌做了不少事情也鸽了不少东西…

  • 本文我们来讲讲博主最近在项目中频繁使用的,也就是广泛运用于C#或者Java的一个常用编程机制(思路)-----委托事件。由于C++在语言特性上没有像C#那样直接支持委托事件,因此本文我们借着学习这两个新的机制,学习一下如何在C++中复刻委托事件

  • 本系列将会提到:

  • 请添加图片描述

    1. (前置知识)函数指针,std::function,std::bind以及回调机制
    2. 委托事件的概念
    3. 多播委托的概念及C++实现
    4. 基于C++委托事件实现 观察者模式发布-订阅模式`
    5. 基于C++委托事件实现状态机

0 前置知识:函数指针,std::functionstd::bind以及回调机制

0-1 函数指针
  • 博主在其在C语言的一片文章提到过 【C语言备课课件】(下)指针pointer请添加图片描述

  • 同时上述概念也适用于C++,函数指针是C/C++中的一种低级机制,用于存储和调用函数的地址。它允许将函数作为参数传递或存储,并在需要时调用。


0-1-1 举例
  • 不同于C的是,在C++中我们可以使用using来简化函数指针的定义
using FuncPtr = int(*)(double, double);
FuncPtr func_ptr;
  • 我们来用这个特性实现基础的四则运算
#include <iostream>
using namespace std;// 定义函数指针类型
using FuncPtr = int(*)(int, int);int add(int a, int b) { return a + b;}
int subtract(int a, int b) {return a - b;}
int multiply(int a, int b) {return a * b;}
int divide(int a, int b) {if (b != 0) {return a / b;} else {cout << "Error: Division by zero!" << endl;return 0;  // 或者可以返回一个特殊值表示除零错误}
}int main() {// 定义一个函数指针数组,增加了除法操作FuncPtr operations[4] = { add, subtract, multiply, divide };int a = 10, b = 5;// 使用函数指针数组来调度函数cout << "Add: " << operations[0](a, b) << endl;       // 输出:15cout << "Subtract: " << operations[1](a, b) << endl;  // 输出:5cout << "Multiply: " << operations[2](a, b) << endl;  // 输出:50cout << "Divide: " << operations[3](a, b) << endl;    // 输出:2return 0;
}

0-2 std::function
  • 同样的,博主在【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(三):基于BT行为树实现复杂敌人BOSS-AI提到了std::function请添加图片描述

  • 那这里就不再多赘述咯,给个链接大家方便回去看


0-3 std::bind
0-3-1 概念
  • std::bind 是 C++11 引入的一个标准库函数,它用于将一个可调用对象(如函数、成员函数、函数对象等)与一些参数进行绑定,并返回一个新的可调用对象。这个新的可调用对象可以稍后被调用,并且能够通过提供部分参数来提前“绑定”固定的参数。
0-3-2 API
  • 他的使用API:
    • std::bind 返回一个新的可调用对象,具有与 f 相同的行为,但某些参数已经被固定。
    • f:一个可调用对象(函数指针、成员函数指针、函数对象等)。
    • args:要绑定的参数,可以是常量、占位符(std::placeholders::_1, _2 等)或变量。
#include <functional>
std::bind(Callable&& f, Args&&... args);
  • 关于 std::bind 返回的类型:std::bind 返回的类型是一个函数对象类型,通常是由编译器推导出来的,我们来看一个例子:
#include <iostream>
#include <functional>void printSum(int a, int b) {std::cout << "Sum: " << a + b << std::endl;
}int main() {// 使用 std::bind 创建一个可调用对象,将 printSum 函数的第二个参数绑定为 10auto bound_func = std::bind(printSum, std::placeholders::_1, 10);// 只需要提供第一个参数,第二个参数已经被绑定为 10bound_func(5);  // 输出:Sum: 15return 0;
}
  • 上述代码bound_func的类型是怎么被推导的呢,实际上展开是这样的
decltype(std::bind(printSum, std::placeholders::_1, 10)) bound_func;
  • 或者你可以这样理解:bound_func的类型用std::function来表示的话就是:
std::function<void(int)> 
0-3-3 用法
  • 上面的例子我们已经举了一个std::bind其中一个用法,下面我们来夸克他最常见的用法—绑定类与类的成员函数
  • 当我们需要将一个类的非静态成员函数作为参数传递时,直接传递成员函数指针是不可行的,因为成员函数有一个隐式的 this 指针,而非静态成员函数总是与类的实例相关联。因此,为了将非静态成员函数作为参数传递,需要进行一些额外的处理。
#include <iostream>
#include <functional>class MyClass {
public:void display(int value) { std::cout << "Value: " << value << std::endl;}
};void callFunction(std::function<void(int)> func) {func(42);  // 调用传入的函数对象}int main() {MyClass obj;// 使用 std::bind 绑定成员函数 display 和对象 objauto boundFunc = std::bind(&MyClass::display, &obj, std::placeholders::_1);// 将绑定后的可调用对象作为参数传递callFunction(boundFunc);return 0;
}
  • std::bind(&MyClass::display, &obj, std::placeholders::_1):这个表达式将 MyClass 的成员函数 display 与对象 obj 绑定,并返回一个新的可调用对象(即 boundFunc)。std::placeholders::_1 表示调用时传递给 boundFunc 的第一个参数会传递给 display 函数。
  • callFunction(boundFunc):这里,我们将绑定后的可调用对象传递给 callFunction,并且在 callFunction 中调用它。

0-3 回调机制
  • 在 C++ 中,回调机制(Callback Mechanism)允许你将一个函数作为参数传递给另一个函数,这样当某个事件发生时,可以通过回调函数来处理它。回调函数通常用于异步操作、事件驱动编程、处理完成后的动作等场景。
  • 一个简单的例子就是
#include <iostream>
using namespace std;using Callback = void(*)(int);// 一个函数,接受回调函数作为参数
void processData(int value, Callback callback) {cout << "Processing data: " << value << endl;// 调用回调函数callback(value);
}// 一个实际的回调函数
void onDataProcessed(int value) {cout << "Data processed: " << value << endl;
}int main() {int data = 42;// 将回调函数传递给processDataprocessData(data, onDataProcessed);return 0;
}
  • 结合上面所学的std::function,我们可以设置一个可以接收任意位置的函数指针来接收类内或者类外的不同类型的回调函数
#include <iostream>
#include <functional>
using namespace std;class Processor {
public:// 成员函数,处理回调void onDataProcessed(int value) {cout << "Data processed in member function: " << value << endl;}// 使用 std::function 来接受回调,可以是任何可调用对象(包括成员函数)void processData(int value, std::function<void(int)> callback) {callback(value);  // 调用回调函数}
};
int main() {Processor proc;proc.processData(42,[](int value_){ cout << "Processing data: " << value_ << endl;});proc.processData(42,std::bind(&Processor::onDataProcessed, &proc, std::placeholders::_1));return 0;
}
  • 请添加图片描述

  • 完成上述的前置知识补充,那么我们来看看委托事件

1 委托事件

1-1 基础概念-委托(Delegate)
  • 委托其实就是一种类型安全的函数指针,用于封装一个或多个方法。它允许将方法作为参数传递或存储,并在需要时调用。
  • 然而在C++中并没有原生的委托机制,但是我们可以通过std::function来实现。

  • 聪明的你一定会发起疑问了!为什么一定是std::function呢??
1-2 委托函数指针的区别
  • 一个表格不废话!!!!!!!
特性函数指针委托 (std::function)
类型安全不保证类型安全,容易出错。提供类型安全,绑定时会检查签名是否一致。
灵活性只能绑定普通函数,成员函数需要特殊语法。灵活,可以绑定普通函数、Lambda 表达式成员函数等。
多播支持(下面会说)默认不支持多播。支持多播(多个回调),通过容器或多次绑定实现。
绑定成员函数需要特殊的语法来绑定成员函数。可以通过 std::bind 简单绑定成员函数和对象实例。
变参支持不直接支持可变参数,必须使用变参函数。支持变参函数,并且通过 std::bind 和 Lambda 支持灵活绑定。
  • 那么委托的例子就和前面说的std::function的使用例子一样,后面我们还会举例子。

1-3 事件的概念
  • 事件委托的一个特殊化,它限制了如何使用委托。事件允许对象向外界通知某些事情的发生,但它只能由事件发布者触发,外部只能通过订阅(绑定)方法来响应事件。
1-4 委托和事件的区别
  • 咱们用一个通俗易懂 的例子来说明:
    • 委托:就像一个“电话本”,你可以在电话本中存储任意的方法(回调),随时调用它们。你既可以向电话本添加新的号码,也可以删除已有的号码,甚至可以直接拨打号码(调用方法)。
    • 事件:就像是==“广播系统”==,你可以通过广播系统宣布事件的发生,但你不能自己主动去播放广播。其他人只能通过订阅来接收广播,广播的发起完全由广播中心(事件发布者)控制。
  • 正经说就是:
    • 委托:你可以通过赋值来动态改变委托,委托的调用不受限制。
    • 事件:你只能订阅(绑定)事件,不能直接触发事件的调用。事件的触发只能由发布者控制,这是一种封装和控制事件流动的机制。

1-5 为什么用事件而不用标志位?
  • 我们来举一个例子:
    • 加入你希望当某个事件触发的时候,去执行某个函数
    • 那也许你会说,我设置一个bool标志不久行了吗?我通过反复判断bool标志位,来决定是否执行这个函数,这不行吗?(标志位仙人如是说到)
  • 通过设置一个 bool 标志位并反复判断是否触发事件的方式,确实在某些情况下看起来是可行的,但从设计和性能的角度来看,它有一些潜在的问题。
  1. 标志位是高度耦合的,而且你需要循环检查,况且一旦出现多个事件,那标志位就难以维护了
  2. 其次,使用 bool 标志位往往意味着你必须显式地管理状态,例如,标志位何时置 true,何时置 false。这种状态管理如果处理不当,容易导致逻辑错误,比如事件没有被正确重置未及时触发等。随着事件逻辑复杂化,状态管理的复杂性会大大增加。
  3. 最后通过 bool 标志位来控制事件的触发,代码的流程变得较为模糊和不易追踪。随着程序逻辑的增长,问题可能出现在 bool 标志的设置和检查位置,这会增加调试的难度。

  • 废话那么多,我们直接看例子:假设我们有一个程序,其中需要处理多个事件(例如,玩家按下按钮,或者达到某个特定时间点等),并且每个事件需要执行某个函数。
1-6 反面教材:使用 if-elsebool 标志位
  • 假设我们有一个游戏角色,角色可以执行攻击(Attack)和防御(Defend)操作,而每个操作都有一些条件需要满足。如果我们用 if-elsebool 标志位来控制事件的触发,可能会遇到一些问题。
#include <iostream>class Player {
public:bool canAttack = true;  // 是否可以攻击bool canDefend = true;  // 是否可以防御void Update() {if (canAttack) {std::cout << "Player attacks!" << std::endl;canAttack = false;  // 攻击后不能立刻再次攻击} else if (canDefend) {std::cout << "Player defends!" << std::endl;canDefend = false;  // 防御后不能立刻再次防御}}void ResetActions() {canAttack = true;  // 重置攻击标志canDefend = true;  // 重置防御标志}
};int main() {Player player;// 游戏循环中的更新player.Update();  // 执行攻击player.Update();  // 执行防御// 重置标志位后再次执行player.ResetActions();player.Update();  // 执行攻击player.Update();  // 执行防御return 0;
}
  • 可以看到canAttackcanDefend 是高度耦合的。如果游戏中有多个事件(比如跳跃、移动、使用技能等),这些标志位会迅速增加,导致事件和状态管理的复杂性增加。
  • 同时这种管理方式让人容易漏掉状态的重置(例如,如果在某个操作中忘记调用 ResetActions,就会导致攻击和防御无法触发)。
  • 随着事件的增多,if-else 的分支会变得越来越复杂,维护起来非常麻烦。如果增加新的操作(例如跳跃、技能释放),你需要对 if-else 结构进行修改,逻辑越来越杂乱。
1-7 改进设计-事件触发
  • 一个更好的方法是使用事件驱动的设计。通过事件回调和状态管理来触发和控制操作的执行,这样可以避免显式地管理 bool 标志位,减少耦合。
#include <iostream>
#include <functional>
#include <vector>class Player {
public:// 定义事件回调类型using Event = std::function<void()>;private:std::vector<Event> events;public:void RegisterEvent(Event event) {events.push_back(event);  // 注册事件}void TriggerEvents() {for (auto& event : events) {event();  // 执行事件}events.clear();  // 清除事件(已触发的事件不再执行)}
};void Attack() {std::cout << "Player attacks!" << std::endl;
}void Defend() {std::cout << "Player defends!" << std::endl;
}int main() {Player player;// 注册事件player.RegisterEvent(Attack);player.RegisterEvent(Defend);// 触发所有事件player.TriggerEvents();  // 执行攻击和防御return 0;
}
  • 通过将每个事件作为回调函数来管理,我们避免了将事件与 bool 标志位捆绑。每个事件都是独立的,通过注册和触发回调函数来控制事件的执行,而不是通过 if-else 和标志位。
  • TriggerEvents 统一管理了所有的事件触发,不需要通过显式的 if-else 来判断是否可以执行某个事件。事件逻辑更加清晰、独立,且不会互相影响。
  • 如果你需要添加新的事件(例如跳跃、技能使用等),只需要在 RegisterEvent 中添加新的事件回调,而不需要修改 if-else 结构或复杂的 bool 状态管理。



2 多播委托

2-1 概念
  • 多播委托(Multicast Delegate)是一种特殊类型的委托,它允许将多个方法(即多个事件处理程序)附加到同一个委托上。当该委托被调用时,它会依次调用所有附加的方法。多播委托常用于 事件 机制中,特别是在事件驱动的编程模型中。
  • 一句话就是多播委托允许多个回调函数在同一个事件发生时被调用。
2-2 实例
  • 这里我们使用 std::function 和容器(如 std::vector)来存储多个回调函数。
#include <iostream>
#include <functional>
#include <vector>class MulticastDelegate {
public:// 存储回调函数的容器std::vector<std::function<void(int)>> delegates;// 添加回调函数void add(std::function<void(int)> func) {delegates.push_back(func);}// 调用所有回调函数void invoke(int value) {for (auto& delegate : delegates) {delegate(value);}}
};// 示例回调函数
void callback1(int value) {std::cout << "Callback 1 called with value: " << value << std::endl;
}void callback2(int value) {std::cout << "Callback 2 called with value: " << value << std::endl;
}int main() {MulticastDelegate delegate;// 将回调函数添加到多播委托中delegate.add(callback1);delegate.add(callback2);// 调用所有回调函数delegate.invoke(42);return 0;
}



  • 详细通过上述的讲解,你一定晕头转向,不要紧,这时候我们来讲讲委托事件实际的运用----观察者模式发布-订阅模式

3 基于C++委托事件实现 观察者模式

3-1 概念

  • 观察者模式是一种 对象行为型模式,它定义了一种一对多的依赖关系,让多个观察者对象(Listener)监听一个主题对象(Subject)的状态变化。当主题对象的状态发生改变时,所有依赖于它的观察者都会得到通知并自动更新。
  • 关键点:
    • 主题(Subject):被观察的对象,负责管理所有的观察者。
    • 观察者(Observer):响应主题变化的对象。当主题状态发生变化时,观察者会做出反应。

3-2 UML 图

notifies
1
0..*
implements
1
1
Subject
+addObserver(observer: Observer)
+removeObserver(observer: Observer)
+notifyObservers(message: String)
«interface»
Observer
+update(message: String)
ConcreteObserver
+update(message: String)

3-3 C++实现观察者模式

  • 假设我们有一个新闻发布系统(主题),多个用户(观察者)订阅了这个系统,用户会在新闻更新时收到通知。
  • 这是我们将发布新闻这件事情作为事件,并允许多个用户订阅该新闻系统。我们将使用委托来传递回调函数,并且通过 事件机制来确保通知能够在新闻更新时传递给订阅的用户。
  • 我们一步步来,搜先我们这样的一个新闻数据类型需要多个用于去订阅
// 新闻结构体  
struct News {  std::string title;      // 新闻标题  std::string content;    // 新闻内容  std::string author;     // 作者  std::string timestamp;  // 发布时间  // 构造函数  News(const std::string& t, const std::string& c, const std::string& a)  : title(t), content(c), author(a) {  // 获取当前时间戳  time_t now = time(0);  timestamp = ctime(&now);  // 转换为易读的时间格式  }  // 打印新闻  void printNews() const {  std::cout << "Title: " << title << std::endl;  std::cout << "Author: " << author << std::endl;  std::cout << "Published at: " << timestamp;  std::cout << "Content: " << content << std::endl;  }  
};
  • 我们定义用户接收到事件更新的逻辑是这样:
// 用户类(观察者)  
class User {  
public:  User(const std::string& name) : name(name) {}  // 用户接收到新闻的回调函数  void onNewsReceived(const News& news) {  std::cout << name << " received the news: " << std::endl;  news.printNews();  }  private:  std::string name;  
};
  • 接着我们编写新闻发布媒体类,回顾一下 ,我们将发布新闻这件事情作为事件,并允许多个用户订阅该新闻系统。那么我们定义发布新闻为如下事件:
using NewsEvent = std::function<void(const News&)>;
  • 同时我们需要一个容器用来存储所有的用户方(也就是存储事件的容器)
// 新闻发布系统类(主题)  
class NewsPublisher {  
public:  // 使用 std::function 来存储订阅者回调  using NewsEvent = std::function<void(const News&)>;  
private:  std::vector<NewsEvent> subscribers;  // 存储订阅者  
};
  • 同时我们需要提供对外的接口和统一通知所有用户的函数
// 新闻发布系统类(主题)  
class NewsPublisher {  
public:  // 使用 std::function 来存储订阅者回调  using NewsEvent = std::function<void(const News&)>;  // 存储所有订阅者(事件处理函数)  void subscribe(NewsEvent subscriber) {  subscribers.push_back(subscriber);  }  // 发布新闻,通知所有订阅者  void publishNews(const News& news) {  std::cout << "Publishing news..." << std::endl;  // 通知所有订阅者  for (const auto& subscriber : subscribers) {  //subscribers 是 std::vector<NewsEvent>类型//subscriber  是 NewsEvent类型//也就是 std::function<void(const News&)>//也就是这里调用了每个客户的onNewsReceived(news)subscriber(news);  }  }  private:  std::vector<NewsEvent> subscribers;  // 存储订阅者  
};
  • 那么我们就可以进行测试了:(完整代码如下)
#include <iostream>  
#include <string>  
#include <vector>  
#include <functional>  
#include <ctime>  // 新闻结构体  
struct News {  std::string title;      // 新闻标题  std::string content;    // 新闻内容  std::string author;     // 作者  std::string timestamp;  // 发布时间  // 构造函数  News(const std::string& t, const std::string& c, const std::string& a)  : title(t), content(c), author(a) {  // 获取当前时间戳  time_t now = time(0);  timestamp = ctime(&now);  // 转换为易读的时间格式  }  // 打印新闻  void printNews() const {  std::cout << "Title: " << title << std::endl;  std::cout << "Author: " << author << std::endl;  std::cout << "Published at: " << timestamp;  std::cout << "Content: " << content << std::endl;  }  
};  // 新闻发布系统类(主题)  
class NewsPublisher {  
public:  // 使用 std::function 来存储订阅者回调  using NewsEvent = std::function<void(const News&)>;  // 存储所有订阅者(事件处理函数)  void subscribe(NewsEvent subscriber) {  subscribers.push_back(subscriber);  }  // 发布新闻,通知所有订阅者  void publishNews(const News& news) {  std::cout << "Publishing news..." << std::endl;  // 通知所有订阅者  for (const auto& subscriber : subscribers) {  subscriber(news);  }  }  private:  std::vector<NewsEvent> subscribers;  // 存储订阅者  
};  // 用户类(观察者)  
class User {  
public:  User(const std::string& name) : name(name) {}  // 用户接收到新闻的回调函数  void onNewsReceived(const News& news) {  std::cout << name << " received the news: " << std::endl;  news.printNews();  }  private:  std::string name;  
};  int main() {  // 创建新闻发布系统(主题)  NewsPublisher newsPublisher;  // 创建用户(观察者)  User user1("Alice");  User user2("Bob");  // 用户订阅新闻发布系统  newsPublisher.subscribe(std::bind(&User::onNewsReceived, &user1, std::placeholders::_1));  newsPublisher.subscribe(std::bind(&User::onNewsReceived, &user2, std::placeholders::_1));  // 创建新闻对象  News news("Breaking News: C++ 20 features!",  "C++ 20 introduces many new features, such as modules, coroutines, and improved constexpr.",  "John Doe");  // 发布新闻,所有订阅者都会接收到新闻  newsPublisher.publishNews(news);  return 0;  
}
  • 可以看到,发布者一次更新调用后,所有用户都被通知请添加图片描述



小结

  • 本文我们介绍了C++中如何实现委托和事件,并且通过委托和事件实现了观察者模式
  • 本系列的下篇我们将继续使用委托和事件去实现发布订阅模式,以及使用委托和事件实现状态机。
  • 如有错误,欢迎指出!
  • 感谢大家的支持!!!!

相关文章:

【C++委托与事件】函数指针,回调机制,事件式编程与松耦合的设计模式(上)

前言 上一次发文章已经是在两个月前了hhh&#xff0c;期间也是忙忙碌碌做了不少事情也鸽了不少东西… 本文我们来讲讲博主最近在项目中频繁使用的&#xff0c;也就是广泛运用于C#或者Java的一个常用编程机制&#xff08;思路&#xff09;-----委托和事件。由于C在语言特性上没…...

【Java学习】抽象类与接口

面向对象系列四 一、抽象方法 二、抽象类 三、意义检查 1.抽象方法的意义 2.意义检查 体现 四、接口 1.级别层次 2.接口变量 3.意义 4.成员 成员变量&#xff1a; 成员方法&#xff1a; 一、抽象方法 没有方法体即没有任何实现的方法是抽象方法&#xff0c;只有在…...

体育电竞比分网开发流程

开发一个体育电竞比分网的流程可以分为以下几个主要步骤&#xff1a; 1. 需求分析 目标用户&#xff1a;确定网站的主要用户群体&#xff0c;如体育迷、电竞爱好者等。 功能需求&#xff1a;列出网站需要实现的功能&#xff0c;如实时比分更新、赛事日程、新闻资讯、用户评论…...

vue2和vue3的主要区别

Vue 2 和 Vue 3 之间有几个主要区别&#xff0c;涉及到性能、功能和架构上的改进。以下是一些核心的区别&#xff1a; Composition API&#xff08;组合式 API&#xff09;&#xff1a; Vue 2 使用的是选项式 API&#xff08;Options API&#xff09;&#xff0c;即通过 data, …...

粘贴到Word里的图片显示不全

粘贴到Word里的图片显示不全&#xff0c;可从Word设置、图片本身、软件与系统等方面着手解决&#xff0c;具体方法如下&#xff1a; Word软件设置 经实践发现&#xff0c;图片在word行距的行距出现问题&#xff0c;可以按照如下调整行距进行处理 修改段落行距&#xff1a; 选…...

直角三角堰计算公式

直角三角堰的计算公式通常用于确定流经直角三角形形状的堰的流量。河北瑾航科技遥测终端机 通过采集液位数据(模拟量、串口485/232)&#xff0c;计算得到瞬时流量&#xff0c;然后通过积分进行累计算出累积量&#xff1b;直角三角堰的流量计算公式为&#xff1a; 直角三角堰 计…...

细说Java 引用(强、软、弱、虚)和 GC 流程(一)

一、引用概览 1.1 引用简介 JDK1.2中引入了 Reference 抽象类及其子类&#xff0c;来满足不同场景的 JVM 垃圾回收工作&#xff1a; SoftReference 内存不足&#xff0c;GC发生时&#xff0c;引用的对象&#xff08;没有强引用时&#xff09;会被清理&#xff1b;高速缓存使用…...

C++,设计模式,【工厂方法模式】

文章目录 如何用汽车生产线理解工厂方法模式?一、传统生产方式的困境二、工厂方法模式解决方案三、模式应用场景四、模式优势分析五、现实应用启示✅C++,设计模式,【目录篇】 如何用汽车生产线理解工厂方法模式? 某个早晨,某车企CEO看着会议室里堆积如面的新车订单皱起眉…...

分布式之分布式ID

目录 需求 1. 全局唯一性 2. 高性能 3. 高可用性 4. 可扩展性 5. 有序性 6. 时间相关 7. 长度适中 8. 安全性 9. 分布式一致性 10. 易于集成 常见解决方案 选择依据 数据库号段模式 核心概念 工作流程 优点 缺点 实现示例 优化策略 适用场景 Snowflake雪…...

Innovus中快速获取timing path逻辑深度的golden脚本

在实际项目中我们经常会遇到一条timing path级数特别多&#xff0c;可能是一两页都翻不完。此时&#xff0c;我们大都需要手工去数这条path上到底有哪些是设计本身的逻辑&#xff0c;哪些是PR工具插入的buffer和inverter。 数字IC后端手把手培训教程 | Clock Gating相关clock …...

tortoiseGit的使用和上传拉取

tortoiseGit的使用和上传拉取 下载TortoiseGit 通过网盘分享的文件&#xff1a;tortoiseGit.zip 链接: https://pan.baidu.com/s/1EOT_UsM9_OysRqXa8gES4A?pwd1234 提取码: 1234 在电脑桌面新建文件夹并进入 右击鼠标 将网址复制上去 用户名和密码是在git注册的用户名和…...

简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用

简单工厂模式&#xff08;Simple Factory Pattern&#xff09;虽然不属于 GoF 23 种经典设计模式&#xff0c;但在实际开发中非常常用&#xff0c;尤其是在 Spring Boot 项目中。它提供了一种简单的方式来创建对象&#xff0c;将对象的创建逻辑集中到一个工厂类中。 一、简单工…...

前端排序算法完全指南:从理论到实践

<!DOCTYPE html> <html> <head><title>前端排序算法终极指南</title><style>.container { max-width: 1000px; margin: 0 auto; padding: 20px; }.demo-container { margin: 30px 0; border: 1px solid #eee; padding: 20px; }.bars-wrapp…...

【LeetCode Hot100 矩阵】矩阵置零、螺旋矩阵、旋转图像、搜索二维矩阵II

矩阵 1. 矩阵置零&#xff08;Set Matrix Zeroes&#xff09;解题思路步骤&#xff1a; 代码实现 2. 螺旋矩阵&#xff08;Spiral Matrix&#xff09;解题思路具体步骤&#xff1a; 代码实现 3. 旋转矩阵 90 度解决思路代码实现 5. 搜索二维矩阵中的目标值解决思路代码实现 1. …...

最新版IDEA下载安装教程

一、下载IDEA 点击前往官网下载 或者去网盘下载 点击前往百度网盘下载 点击前往夸克网盘下载 进去后点击IDEA 然后点击Download 选择自己电脑对应的系统 点击下载 等待下载即可 二、安装IDEA 下载好后双击应用程序 点击下一步 选择好安装目录后点击下一步 勾选这两项后点击…...

Embedding模型

检索的方式有那些 关键字搜索&#xff1a;通过用户输入的关键字来查找文本数据。 语义搜索&#xff1a;它的目标是理解用户查询的真实意图&#xff0c;不仅考虑关键词的匹配&#xff0c;还考虑词汇之间的语义 &#xff08;文字&#xff0c;语音&#xff0c;语调...&#xff0…...

WSL进阶使用指南

WSL2通过 Hyper-V 技术创建了一个轻量级的虚拟机&#xff08;VM&#xff09;&#xff0c;在这个虚拟机之上可以运行一个真正的 Linux 内核&#xff0c;这给希望同时使用 Windows 和 Linux 的开发人员提供了无缝高效的体验。本文会介绍一些使用WSL的知识&#xff0c;帮助你更好地…...

JavaScript函数-函数的参数

在JavaScript编程语言中&#xff0c;函数是组织代码和实现复杂逻辑的基本单元。而函数参数则是这些功能的重要组成部分&#xff0c;它们允许我们将数据传递给函数&#xff0c;从而使得函数更加通用和灵活。本文将深入探讨JavaScript函数参数的各种特性及其最佳实践。 参数基础…...

【C语言】第五期——函数

目录 0 前言 1 定义函数 2 调用函数 3 函数的实参和形参 4 函数声明 5 作用域 5.1 局部变量和全局变量 5.2 static关键字 5.2.1 修饰局部变量 5.2.2 修饰全局变量 5.2.3 修饰函数 6 函数的返回值 6.1 return语句 6.2 函数返回值的类型 7 函数的其他形式 7.1 函…...

线结构光三维重建

利用线结构光和单目进行三维重构&#xff08;测距&#xff09;_线结构光三维重建-CSDN博客...

Spring Boot 应用(官网文档解读)

Spring Boot 启动方式 SpringApplication.run(MyApplication.class, args); Spring Boot 故障分析器 在Spring Boot 项目启动发生错误的时候&#xff0c;我们通常可以看到上面的内容&#xff0c;即 APPLICATION FAILED TO START&#xff0c;以及后面的错误描述。这个功能是通过…...

基于ffmpeg+openGL ES实现的视频编辑工具-添加转场(九)

在视频编辑的广阔领域中,转场效果无疑是提升视频流畅性与观赏性的关键要素。巧妙运用转场,能够让不同视频片段之间的衔接更为自然,同时赋予视频独特的创意魅力。本文将深入探讨如何借助 ffmpeg 和 openGL ES 技术,在视频编辑工具中实现丰富多样的转场效果。 一、转场技术原…...

库的制作与原理(一)

1.库的概念 库是写好的&#xff0c;现成的可以复用的代码。本质上库是一种可执行的二进制形式&#xff0c;可以被操作系统载入内存执行。库有俩种&#xff1a;静态库 .a[Linux] .lib[windows] 动态库 .so[Linux] .dll[windows] 就是把.c文件变成.o文件&#xff0c;把…...

Java List 自定义对象排序 Java 8 及以上版本使用 Stream API

从 Java 8 开始&#xff0c;你可以使用 Stream API 对 List 进行排序&#xff0c;这种方式更加简洁和灵活。 以下是一个示例代码&#xff1a; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors;// 自定…...

单元测试的策略有哪些,主要包括什么?

单元测试的策略及主要内容 单元测试&#xff08;Unit Testing&#xff09;是指对软件系统中的最小可测试单元&#xff08;通常是一个函数、方法或类&#xff09;进行验证&#xff0c;以确保其行为符合预期。常见的单元测试策略可以分为基于代码的策略和基于数据的策略&#xf…...

《深度剖析:AI与姿态估计技术在元宇宙VR交互中的应用困境》

在元宇宙的宏大版图里&#xff0c;虚拟现实&#xff08;VR&#xff09;交互是构建沉浸式体验的关键支柱&#xff0c;而人工智能&#xff08;AI&#xff09;与姿态估计技术的融合&#xff0c;本应成为提升交互体验的强大引擎。但在实际应用中&#xff0c;它们面临着诸多复杂且棘…...

基于YOLO11深度学习的糖尿病视网膜病变检测与诊断系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...

【QT 网络编程】HTTP协议(二)

文章目录 &#x1f31f;1.概述&#x1f31f;2.代码结构概览&#x1f31f;3.代码解析&#x1f338;Http_Api_Manager - API管理类&#x1f338;Http_Request_Manager- HTTP请求管理类&#x1f338;ThreadPool - 线程池&#x1f338;TestWindow- 测试类 &#x1f31f;4.运行效果&…...

mysql之规则优化器RBO

文章目录 MySQL 基于规则的优化 (RBO)&#xff1a;RBO 的核心思想&#xff1a;模式匹配与规则应用RBO 的主要优化规则查询重写 (Query Rewrite) / 查询转换 (Query Transformation)子查询优化 (Subquery Optimization) - RBO 的重中之重非相关子查询 (Non-Correlated Subquery)…...

Python天梯赛10分题-念数字、求整数段和、比较大小、计算阶乘和

007-念数字 输入一个整数&#xff0c;输出每个数字对应的拼音。当整数为负数时&#xff0c;先输出fu字。十个数字对应的拼音如下&#xff1a; 0: ling 1: yi 2: er 3: san 4: si 5: wu 6: liu 7: qi 8: ba 9: jiu输入格式&#xff1a; 输入在一行中给出一个整数&#xff0c;如&…...

如何进行文档类图像的校正?

可以使用OpenCV实现的图像校正算法&#xff0c;包含透视校正和旋转校正的步骤&#xff0c;并附有详细注释。 具体如下&#xff1a; import cv2 import numpy as npdef order_points(pts):"""将四个点按左上、右上、右下、左下顺序排列"""rect …...

GPIO外设

一、GPIO简介 GPIO&#xff0c;general-purpos IO port,通用输入输出引脚&#xff0c;所有的GPIO引脚都有基本的输入输出功能。 最基本的输出功能&#xff1a;STM32控制引脚输出高、低电平&#xff0c;实现开关控制&#xff1b;最基本的输入功能&#xff1a;检测外部输入电平&…...

DeepSeek-R1之二_基于Open-WebUI的AI托管平台之Pyenv-win安装与配置搭建本地AI知识库

DeepSeek-R1之二_基于Open-WebUI的AI托管平台之Pyenv-win安装与配置搭建本地AI知识库 文章目录 DeepSeek-R1之二_基于Open-WebUI的AI托管平台之Pyenv-win安装与配置搭建本地AI知识库1. 官网及前提条件1. 官网2. 前提条件1. 安装了Ollama2. 通过Ollama下载与管理了DeepSeek-R1模…...

My Metronome for Mac v1.4.2 我的节拍器 支持M、Intel芯片

应用介绍 My Metronome 是一款适用于 macOS 的专业节拍器应用程序&#xff0c;旨在帮助音乐家、作曲家、学生和任何需要精确节奏控制的人进行练习。无论是进行乐器练习、音乐创作还是演出排练&#xff0c;My Metronome 都能为用户提供精准的节拍支持和灵活的功能&#xff0c;确…...

Windows系统本地部署DeepSeek-R1+本地知识库+联网搜索+Agent功能

本文记录了Windows11 Ollama AnythingLLM&#xff0c;3步快速本地部署DeepSeek-R1模型&#xff0c;支持联网搜索、应用本地知识库和创建Agent功能。 前言 DeepSeek-R1 知识库相关 更新时间&#xff1a;截至 2025年2月&#xff0c;当前版本的 R1 基于 2024年7月之前的数据训…...

RT-Thread+STM32L475VET6——TF 卡文件系统

文章目录 前言一、板载资源二、具体步骤1.打开CubeMX进行USB配置1.1 使用外部高速时钟&#xff0c;并修改时钟树1.2 打开SPI1&#xff0c;参数默认即可(SPI根据自己需求调整&#xff09;1.3 打开串口&#xff0c;参数默认1.4 生成工程 2.配置SPI2.1 打开SPI驱动2.2 声明使用SPI…...

Jmeter进阶篇(34)如何解决jmeter.save.saveservice.timestamp_format=ms报错?

问题描述 今天使用Jmeter完成压测执行,然后使用命令将jtl文件转换成html报告时,遇到了报错! 大致就是说jmeter里定义了一个jmeter.save.saveservice.timestamp_format=ms的时间格式,但是jtl文件中的时间格式不是标准的这个ms格式,导致无法正常解析。对于这个问题,有如下…...

Javascript使用Sodium库实现 aead_xchacha20poly1305_ietf加密解密,以及与后端的密文交互

Node.js环境安装 sodium-native (其他库可能会出现加密解密失败&#xff0c;如果要使用不一样的库&#xff0c;请自行验证) npm install sodium-native 示例代码&#xff0c;使用的是 sodium-native v4.3.2 (其他版本可能会有变化&#xff0c;如果要使用&#xff0c;请自行验…...

机器学习实战(8):降维技术——主成分分析(PCA)

第8集&#xff1a;降维技术——主成分分析&#xff08;PCA&#xff09; 在机器学习中&#xff0c;降维&#xff08;Dimensionality Reduction&#xff09; 是一种重要的数据处理技术&#xff0c;用于减少特征维度、去除噪声并提高模型效率。主成分分析&#xff08;Principal C…...

0099__Visual Studio 引入外部静态库与动态库

Visual Studio 引入外部静态库与动态库_visual studio 添加库-CSDN博客...

eclips 快捷键

eclips 快捷键 类别快捷键功能描述通用Ctrl S保存当前文件Ctrl Shift S保存所有文件Ctrl Z撤销操作Ctrl Y重做操作Ctrl X剪切Ctrl C复制Ctrl V粘贴Ctrl A全选Ctrl F查找Ctrl H打开搜索对话框Ctrl /注释/取消注释当前行或选中的代码块Ctrl Shift /添加块注释Ctrl …...

VSCode ssh远程连接内网服务器(不能上网的内网环境的Linux服务器)的终极解决方案

VSCode ssh远程连接内网服务器&#xff08;不能上网的内网环境的Linux服务器&#xff09; 离线下载vscode-server并安装: 如果远程端不能联网可以下载包离线安装,下载 vscode-server 的 url 需要和 vscode 客户端版本的 commit-id 对应.通过 vscode 面板的帮助->关于可以获…...

【Gin-Web】Bluebell社区项目梳理3:社区相关接口开发

本文目录 一、接口详情1. 获取分类社区列表接口2. 根据id查询社区 二、值类型与引用类型 一、接口详情 跟社区有关的接口详情如下。 1. 获取分类社区列表接口 首先是Controller层&#xff0c;然后跳转到Logic层业务逻辑的开发。 这是Logic层&#xff0c;再做一次跳转&#…...

鸟语林-论坛系统自动化测试

文章目录 一、自动化实施步骤1.1编写Web测试用例1.2 编写自动化代码1.2.1 LoginPageTest1) 能否正确打开登录页面2) 点击去注册能否跳转注册页面3) 模拟用户登录&#xff0c;输入多组登录测试用例 1.2.2 RegisterPageTest1) 能否成功打开注册页面2) 注册测试用例3) 点击去登录按…...

图解循环神经网络(RNN)

目录 1.循环神经网络介绍 2.网络结构 3.结构分类 4.模型工作原理 5.模型工作示例 6.总结 1.循环神经网络介绍 RNN&#xff08;Recurrent Neural Network&#xff0c;循环神经网络&#xff09;是一种专门用于处理序列数据的神经网络结构。与传统的神经网络不同&#xff0c…...

c语言左值和右值的区别

在C语言中&#xff0c;左值&#xff08;lvalue&#xff09;和右值&#xff08;rvalue&#xff09;是互斥的概念&#xff0c;左值不能是右值。以下是详细的解释和总结&#xff1a; 1. 左值&#xff08;lvalue&#xff09; 定义&#xff1a;左值是一个表达式&#xff0c;表示一个…...

Scrapy:Downloader下载器设计详解

Scrapy下载器设计详解 1. 整体架构 Scrapy的下载器(Downloader)是整个爬虫框架的核心组件之一&#xff0c;负责处理所有网络请求的下载工作。它的主要职责是&#xff1a; 管理并发请求实现请求调度处理下载延迟维护下载槽(Slot) 官方文档&#xff1a;Settings中的Downloader配…...

细说STM32F407单片机2个ADC使用DMA同步采集各自的1个输入通道的方法

目录 一、示例说明 二、工程配置 1、RCC、DEBUG、CodeGenerator 2、USART6 3、TIM3 &#xff08;1&#xff09;Mode &#xff08;2&#xff09;参数设置 &#xff08;3&#xff09; TRGO &#xff08;4&#xff09;ADC1_IN0 1&#xff09;ADCs_Common_Settings 2&a…...

【分治法】线性时间选择问题

问题描述 给定线性序列中n个元素和一个整数k&#xff0c;1≤k≤n&#xff0c;要求在线性时间中找出这n个元素中第k小的元素 常规思路 常规思路是对序列先排序&#xff0c;落在第k个位置的元素就是第k小的元素。 这种方法的时间复杂度不是线性的&#xff0c;是O(nlogn)的时间…...

redis中的Lua脚本,redis的事务机制

lua脚本的特点 lua脚本可以操作redis数据库&#xff0c;并且脚本中的代码满足原子性&#xff0c;要么全部被执行&#xff0c;要么全部不执行 lua脚本的语法 脚本示例 lua脚本的草稿&#xff1a; 最终的lua脚本 lua脚本在java里调用的方法 RedisTemplete类里有一个方法&…...