优化C++设计模式:用模板代替虚函数与多态机制
文章目录
- 0. 引言
- 1. 模板编程替换虚函数和多态的必要性
- 1.1. MISRA C++对类型转换和虚函数的规定
- 1.2. 虚函数与多态问题的影响及如何适应MISRA C++要求
- 1.3. 模板编程的优势:替代虚函数和多态机制
- 2. 设计模式改进
- 2.1. 单例模式的改进
- 与静态局部变量的对比(第二种实现)
- 2.2. 工厂模式的改进
- 2.3. 观察者模式的改进
- 2.4. 适配器模式的改进
- 2.5. 桥接模式的改进
- 2.6. 装饰器模式的改进
- 2.7. 责任链模式的改进
0. 引言
MISRA C++ 标准对类型转换和虚函数使用做出严格规定,尤其在嵌入式系统中,以确保代码的可预测性和高效性。传统设计模式中虚函数和多态性引入会增加运行时开销,增加系统负担。然而,模板编程通过编译时决策代替运行时动态绑定,可以提升性能并降低内存消耗。
本文将尝试使用C++11改进常用的几个设计模式。
1. 模板编程替换虚函数和多态的必要性
1.1. MISRA C++对类型转换和虚函数的规定
MISRA C++ 标准对于类型转换和虚函数的有严格的使用要求,要求包括:
- Rule 5-0-1(MISRA C++ 2008):禁止使用不安全的类型转换,如
reinterpret_cast、const_cast、static_cast和dynamic_cast。 - Rule 5-0-2:要求类型转换必须明确指定转换类型,确保转换符合预期行为,避免使用不安全的转换方式。
- Rule 5-1-1:在进行类型转换时,必须确保转换类型合法,不破坏对象的常量性或类型。
同时,虚函数和多态机制在嵌入式系统中也有劣势:
- 运行时开销:虚函数表(vtable)的查找会带来额外的运行时开销,增加程序的资源需求。
- RTTI(运行时类型信息)开销:启用 RTTI 会增加内存使用,尤其是在
dynamic_cast或typeid被使用时。 - MISRA C++的限制:标准要求避免使用虚函数和多态机制,特别是在没有严格控制的情况下,可能会导致代码行为不可预测,增加出错的风险。
1.2. 虚函数与多态问题的影响及如何适应MISRA C++要求
虚函数和多态机制在嵌入式系统中,往往面临以下问题:
- 运行时性能问题:虚函数表查找、RTTI机制的引入,都会增加程序的运行时开销,尤其是在资源受限的嵌入式系统中,可能会显著影响系统性能。
- 内存开销:启用 RTTI 会使每个多态类占用额外的内存,这对于内存有限的嵌入式系统是一个不小的负担。
- MISRA C++的限制:虚函数和多态机制的使用在 MISRA C++ 中受到限制,因为它们增加了代码的复杂性,降低了可预测性,并且可能违反一些规则,如避免不安全的类型转换。
为了适应 MISRA C++ 的要求,可以考虑以下几点:
- 避免虚函数和多态:尽量避免使用运行时多态和虚函数,减少 RTTI 开销,避免使用
dynamic_cast和typeid。 - 设计时决策:通过明确的类型接口和结构体来实现类型转换,确保每个类型转换都能被清晰审查和验证。
- 使用强类型系统:避免使用
void*等不明确类型的指针,确保类型清晰,从而减少不必要的类型转换。
1.3. 模板编程的优势:替代虚函数和多态机制
模板编程通过在编译时绑定函数和类,避免运行时的虚函数查找和多态性问题。使用模板编程的优势如下:
- 编译时绑定:模板代码在编译时就确定了函数调用和对象行为,避免了虚函数表的查找。
- 内联优化:编译器能够对模板代码进行内联优化,直接将模板代码嵌入到调用点。
- 提高执行效率:由于没有动态绑定和运行时类型信息,模板编程能够显著降低程序的运行时开销,提升性能。
模板编程避免了虚函数和多态机制的开销,并且在遵循 MISRA C++ 标准时,能够更容易地实现高效且符合规范的设计模式。
2. 设计模式改进
在传统的设计模式中,许多模式(如单例模式、工厂模式、观察者模式等)依赖于虚函数和继承来实现多态和灵活的行为切换。然而,这种方式虽然灵活,但会带来运行时的开销,包括虚函数表查找、内存使用的增加以及与运行时类型信息(RTTI)相关的性能损失。
模板编程 和 静态多态 通过在编译时进行决策,避免了虚函数调用和动态绑定的需要。通过使用模板,我们可以在不牺牲灵活性的情况下,提高代码的执行效率,并且能够使代码更加符合 MISRA C++ 等标准的要求。
以下是一些常见设计模式的改进方式,尽量在不违背设计原则的情况下提升代码的性能。
2.1. 单例模式的改进
传统的单例模式通过 static 变量和线程同步机制来保证只创建一个实例。然而,如果使用虚函数或者多态,在某些情况下可能会影响线程安全性,增加程序的复杂度。在 C++11/14 中,可以利用 std::once_flag 和 std::call_once 来实现线程安全的单例,而无需依赖虚函数。
通过模板和 std::unique_ptr 来管理单例实例,可以避免虚函数的使用,同时保证线程安全和内存管理。
#include <iostream>
#include <memory> // for std::unique_ptr
#include <mutex>class Singleton {public:// 获取唯一实例static Singleton& getInstance() {std::call_once(initFlag_, &Singleton::initSingleton);return *instance_;}// 示例方法void doSomething() {std::cout << "Singleton instance is doing something!" << std::endl;}private:Singleton() {std::cout << "Singleton initialized!" << std::endl;}// 禁止拷贝构造和赋值操作Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 使用 std::unique_ptr 管理单例实例static std::unique_ptr<Singleton> instance_;static std::once_flag initFlag_;// 初始化实例的私有方法static void initSingleton() {instance_ = std::make_unique<Singleton>();}
};// 初始化静态成员
std::unique_ptr<Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;int main() {// 获取并使用单例对象Singleton& singleton1 = Singleton::getInstance();singleton1.doSomething();// 再次获取单例对象,确保不会重新初始化Singleton& singleton2 = Singleton::getInstance();singleton2.doSomething();return 0;
}
执行结果:
Singleton initialized!
Singleton instance is doing something!
Singleton instance is doing something!
与静态局部变量的对比(第二种实现)
请看如下的实现实现:
class Singleton {public:static Singleton& getInstance() {static Singleton instance;return instance;}private:Singleton() = default;
};
区别对比
| 特性 | 第一种实现 | 第二种实现 |
|---|---|---|
| 线程安全性 | 手动使用 std::once_flag 实现 | 静态局部变量自动线程安全 |
| 复杂性 | 较高,需维护多个静态成员和初始化方法 | 较低,逻辑直接嵌入函数内 |
| 灵活性 | 更灵活,适合复杂初始化逻辑 | 不够灵活,构造函数绑定初始化逻辑 |
| 析构时机 | 使用 std::unique_ptr,支持析构 | 依赖静态变量销毁,时机不可控 |
| 适用场景 | 更适合需要复杂初始化逻辑的场景 | 适合大多数简单单例模式需求 |
2.2. 工厂模式的改进
工厂模式通常通过继承和虚函数来实例化不同的产品对象。而在使用模板的工厂模式中,我们通过将产品类型作为模板参数来实现静态多态,从而避免了使用虚函数。这种方式通过编译时的类型推导,确保了工厂方法能够在编译时确定,而不依赖于运行时的动态绑定。
CRTP(Curiously Recurring Template Pattern)是一种常见的技巧,它通过将子类作为模板参数,允许在基类中静态地定义行为,从而避免了虚函数的开销。通过这样的改进,我们可以在不牺牲灵活性的前提下,减少运行时的性能损耗。
#include <iostream>
#include <memory>
#include <type_traits>// 基础产品接口
template <typename ProductType>
class Product {public:void operation() const {static_assert(std::is_base_of<Product, ProductType>::value, "ProductType must be derived from Product");static_cast<const ProductType*>(this)->operationImpl();}private:// 使用 CRTP (Curiously Recurring Template Pattern) 进行静态多态void operationImpl() const = delete;
};// 具体产品A
class ConcreteProductA : public Product<ConcreteProductA> {public:void operationImpl() const {std::cout << "ConcreteProductA operation." << std::endl;}
};// 具体产品B
class ConcreteProductB : public Product<ConcreteProductB> {public:void operationImpl() const {std::cout << "ConcreteProductB operation." << std::endl;}
};// 工厂类模板
template <typename ProductType>
class Factory {public:std::unique_ptr<Product<ProductType>> create() const {return std::make_unique<ProductType>();}
};int main() {Factory<ConcreteProductA> factoryA;auto productA = factoryA.create();productA->operation();Factory<ConcreteProductB> factoryB;auto productB = factoryB.create();productB->operation();return 0;
}
执行结果:
ConcreteProductA operation.
ConcreteProductB operation.
2.3. 观察者模式的改进
在传统的观察者模式中,观察者通常通过继承和虚函数来注册和通知其他对象。而在模板化的观察者模式中,我们使用 std::function 来替代虚函数,利用模板和类型擦除来实现更加高效和灵活的观察者模式。这样,我们避免了虚函数的调用和继承结构的复杂性,同时提高了类型安全性和灵活性。
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <vector>template <typename EventHandler>
class Events {public:Events() : nextKey_(0) {}// 注册事件处理函数uint32_t addObserver(EventHandler&& handler) {uint32_t key = nextKey_++;storeHandler(key, std::forward<EventHandler>(handler));return key;}uint32_t addObserver(EventHandler& handler) {uint32_t key = nextKey_++;storeHandler(key, handler);return key;}// 移除事件处理函数void removeObserver(uint32_t key) {handlers_.erase(key);}// 通知所有注册的事件处理函数template <typename... Args>void notifyAll(Args&&... args) {for (auto& it : handlers_) {for (auto& handler : it.second) {handler(std::forward<Args>(args)...);}}}// 触发事件,通知所有注册的事件处理器template <typename... Args>inline void trigger(Args&&... args) {notifyAll(std::forward<Args>(args)...);}// 删除拷贝构造和赋值操作Events(const Events&) = delete;Events& operator=(const Events&) = delete;~Events() = default;// 通过 += 操作符添加事件处理器uint32_t operator+=(EventHandler&& handler) {return addObserver(std::forward<EventHandler>(handler));}uint32_t operator+=(EventHandler& handler) {return addObserver(handler);}// 通过 -= 操作符移除事件处理器Events& operator-=(uint32_t key) {removeObserver(key);return *this;}// 清除所有事件处理器void clear() {handlers_.clear();}private:// 存储事件处理函数template <typename Handler>void storeHandler(uint32_t key, Handler&& handler) {handlers_[key].emplace_back(std::forward<Handler>(handler));}private:uint32_t nextKey_; // 唯一的事件处理函数键值std::map<uint32_t, std::vector<EventHandler>> handlers_; // 存储事件处理函数,支持多个处理器
};//
// 事件处理函数1
void handler1(int data) {std::cout << "Handler1 received data: " << data << std::endl;
}// 事件处理函数2
void handler2(int data) {std::cout << "Handler2 received data: " << data << std::endl;
}int main() {// 使用 int 作为事件的键类型,事件处理函数是 std::function<void(int)>Events<std::function<void(int)>> events;// 注册两个事件处理器到同一个事件auto eventKey1 = events += handler1;auto eventKey2 = events += handler2;// 触发事件,所有绑定到该事件的处理器都会被调用events.trigger(100); // 输出:// Handler1 received data: 100// Handler2 received data: 100// 移除事件处理器events -= eventKey1;std::cout << "After removing handler1:" << std::endl;// 再次触发事件,只有 handler2 会被调用events.trigger(200); // 输出:Handler2 received data: 200return 0;
}
执行结果:
Handler1 received data: 100
Handler2 received data: 100
After removing handler1:
Handler2 received data: 200
2.4. 适配器模式的改进
适配器模式用于解决接口不兼容的问题。假设我们有一个基于激光雷达的数据接口,但我们需要将这个接口适配到自动驾驶控制系统。
#include <iostream>
#include <memory>class RadarSensor {public:void readData() {std::cout << "Reading data from Radar Sensor.\n";}
};class CameraSensor {public:void captureImage() {std::cout << "Capturing image from Camera Sensor.\n";}
};// 适配器模板类
template <typename T>
class SensorAdapter {public:void operate() {static_assert(std::is_base_of<SensorAdapter, T>::value, "T must be derived from SensorAdapter");static_cast<T*>(this)->operateImpl();}
private:void operateImpl() = delete;
};class RadarAdapter : public SensorAdapter<RadarAdapter> {private:RadarSensor radar_;public:void operateImpl() {radar_.readData();}
};class CameraAdapter : public SensorAdapter<CameraAdapter> {private:CameraSensor camera_;public:void operateImpl() {camera_.captureImage();}
};int main() {RadarAdapter radarAdapter;CameraAdapter cameraAdapter;radarAdapter.operate(); // Reading data from Radar SensorcameraAdapter.operate(); // Capturing image from Camera Sensorreturn 0;
}
执行结果:
Reading data from Radar Sensor.
Capturing image from Camera Sensor.
2.5. 桥接模式的改进
在桥接模式中,模板类可以在编译时根据实现的类型来选择适当的操作,从而避免了虚函数的调用开销。
#include <iostream>
#include <memory>// 实现接口(通过模板类实现)
template <typename T>
class Implementor {public:void operationImpl() const {static_assert(std::is_base_of<Implementor, T>::value, "T must be derived from Implementor");static_cast<const T*>(this)->operationImpl();}
};// 具体实现A
class ConcreteImplementorA : public Implementor<ConcreteImplementorA> {public:void operationImpl() const {std::cout << "ConcreteImplementorA operation." << std::endl;}
};// 具体实现B
class ConcreteImplementorB : public Implementor<ConcreteImplementorB> {public:void operationImpl() const {std::cout << "ConcreteImplementorB operation." << std::endl;}
};// 抽象类,模板方法和组合
template <typename T>
class Abstraction {public:explicit Abstraction(std::shared_ptr<Implementor<T>> implementor) : implementor_(implementor) {}void operation() const {implementor_->operationImpl(); // 调用实现类的操作}protected:std::shared_ptr<Implementor<T>> implementor_;
};// 扩展抽象类A
template <typename T>
class RefinedAbstractionA : public Abstraction<T> {public:explicit RefinedAbstractionA(std::shared_ptr<Implementor<T>> implementor) : Abstraction<T>(implementor) {}void operation() const {std::cout << "RefinedAbstractionA operation: ";Abstraction<T>::operation();}
};// 扩展抽象类B
template <typename T>
class RefinedAbstractionB : public Abstraction<T> {public:explicit RefinedAbstractionB(std::shared_ptr<Implementor<T>> implementor) : Abstraction<T>(implementor) {}void operation() const {std::cout << "RefinedAbstractionB operation: ";Abstraction<T>::operation();}
};// 使用模板的桥接模式
int main() {// 创建具体实现auto implementorA = std::make_shared<ConcreteImplementorA>();auto implementorB = std::make_shared<ConcreteImplementorB>();// 创建抽象类A和BRefinedAbstractionA<ConcreteImplementorA> abstractionA(implementorA);RefinedAbstractionB<ConcreteImplementorB> abstractionB(implementorB);// 调用操作abstractionA.operation(); // 输出: RefinedAbstractionA operation: ConcreteImplementorA operation.abstractionB.operation(); // 输出: RefinedAbstractionB operation: ConcreteImplementorB operation.return 0;
}
执行结果:
RefinedAbstractionA operation: ConcreteImplementorA operation.
RefinedAbstractionB operation: ConcreteImplementorB operation.
2.6. 装饰器模式的改进
装饰器模式通过动态地组合不同的功能来扩展对象的行为。传统的装饰器模式依赖于继承和虚函数来实现动态行为的扩展,而模板化的装饰器模式通过组合和模板参数来实现类似的功能,避免了虚函数的调用和继承层次的复杂性。
#include <iostream>
#include <memory>// 基本组件接口
template <typename T>
class Component {public:virtual void operation() = 0;virtual ~Component() = default;
};// 具体组件
class ConcreteComponent : public Component<ConcreteComponent> {public:void operation() override {std::cout << "ConcreteComponent operation\n";}
};// 装饰器基类
template <typename T>
class Decorator : public Component<T> {protected:std::unique_ptr<Component<T>> component_; // 装饰的组件public:explicit Decorator(std::unique_ptr<Component<T>> component) : component_(std::move(component)) {}void operation() override {component_->operation();}
};// 具体装饰器A
class ConcreteDecoratorA : public Decorator<ConcreteComponent> {public:explicit ConcreteDecoratorA(std::unique_ptr<Component<ConcreteComponent>> component): Decorator<ConcreteComponent>(std::move(component)) {}void operation() override {std::cout << "ConcreteDecoratorA operation\n";Decorator<ConcreteComponent>::operation(); // 调用原始组件的操作}
};// 具体装饰器B
class ConcreteDecoratorB : public Decorator<ConcreteComponent> {public:explicit ConcreteDecoratorB(std::unique_ptr<Component<ConcreteComponent>> component): Decorator<ConcreteComponent>(std::move(component)) {}void operation() override {std::cout << "ConcreteDecoratorB operation\n";Decorator<ConcreteComponent>::operation(); // 调用原始组件的操作}
};int main() {// 创建基本组件auto component = std::make_unique<ConcreteComponent>();// 使用装饰器A包装组件auto decoratorA = std::make_unique<ConcreteDecoratorA>(std::move(component));// 使用装饰器B包装装饰器Aauto decoratorB = std::make_unique<ConcreteDecoratorB>(std::move(decoratorA));// 调用最终的操作decoratorB->operation();return 0;
}
执行结果:
ConcreteDecoratorB operation
ConcreteDecoratorA operation
ConcreteComponent operation
2.7. 责任链模式的改进
责任链模式通过一系列的处理器对象来处理请求,每个处理器对象可以选择处理请求或将请求传递给下一个处理器。传统的责任链模式通常使用虚函数来决定请求的处理逻辑,而模板化的责任链模式通过模板和静态多态来替代虚函数。
#include <iostream>
#include <memory>// 请求类型
struct Request {int value;explicit Request(int v) : value(v) {}
};// 处理器基类
class HandlerBase {public:virtual void handleRequest(Request& request) = 0;void setNext(std::shared_ptr<HandlerBase> next) {next_ = next;}protected:std::shared_ptr<HandlerBase> next_;
};// 具体处理器A
class ConcreteHandlerA : public HandlerBase {public:void handleRequest(Request& request) override {if (request.value < 10) {std::cout << "ConcreteHandlerA handled request with value " << request.value << std::endl;} else if (next_) {std::cout << "ConcreteHandlerA passing request to next handler." << std::endl;next_->handleRequest(request);}}
};// 具体处理器B
class ConcreteHandlerB : public HandlerBase {public:void handleRequest(Request& request) override {if (request.value >= 10 && request.value < 20) {std::cout << "ConcreteHandlerB handled request with value " << request.value << std::endl;} else if (next_) {std::cout << "ConcreteHandlerB passing request to next handler." << std::endl;next_->handleRequest(request);}}
};// 具体处理器C
class ConcreteHandlerC : public HandlerBase {public:void handleRequest(Request& request) override {std::cout << "ConcreteHandlerC handled request with value " << request.value << std::endl;}
};int main() {// 创建处理器实例auto handlerA = std::make_shared<ConcreteHandlerA>();auto handlerB = std::make_shared<ConcreteHandlerB>();auto handlerC = std::make_shared<ConcreteHandlerC>();// 设置责任链handlerA->setNext(handlerB);handlerB->setNext(handlerC);// 测试请求Request request1(5);handlerA->handleRequest(request1);Request request2(15);handlerA->handleRequest(request2);Request request3(25);handlerA->handleRequest(request3);return 0;
}
执行结果:
ConcreteHandlerA handled request with value 5
ConcreteHandlerA passing request to next handler.
ConcreteHandlerB handled request with value 15
ConcreteHandlerA passing request to next handler.
ConcreteHandlerB passing request to next handler.
ConcreteHandlerC handled request with value 25
相关文章:
优化C++设计模式:用模板代替虚函数与多态机制
文章目录 0. 引言1. 模板编程替换虚函数和多态的必要性1.1. MISRA C对类型转换和虚函数的规定1.2. 虚函数与多态问题的影响及如何适应MISRA C要求1.3. 模板编程的优势:替代虚函数和多态机制 2. 设计模式改进2.1. 单例模式的改进与静态局部变量的对比(第二种实现) 2.…...
浪浪云轻量服务器搭建vulfocus网络安全靶场
什么是网络安全靶场 网络安全靶场是一个模拟真实网络环境的训练平台,旨在为网络安全专业人员提供一个安全的环境来测试和提高他们的技能。靶场通常包括各种网络设备、操作系统、应用程序和安全工具,允许用户在其中进行攻击和防御练习。以下是网络安全靶…...
C++builder中的人工智能(23):在现代C++ Windows上轻松录制声音
在这篇文章中,我们将探讨如何在现代C Windows上轻松录制声音。声音以波形和数字形式存在,其音量随时间变化。在C Builder中,使用Windows设备进行录音非常简单。要录制声音,在多设备应用程序中,必须使用FMX.Media.hpp头…...
避免误差!Android 中正确计算时间差的方式
在 Android 开发中,计时和计算时间差异是非常常见的需求,比如记录事件发生的间隔、统计应用启动时间、测量网络请求的响应时间等。在实现这些功能时,我们通常需要一个可靠的时间源来确保计时的准确性。那么为什么 Android 推荐使用 SystemClo…...
unity3d————Resources异步加载
知识点一:Resources异步加载是什么? 在Unity中,资源加载可以分为同步加载和异步加载两种方式。同步加载会在主线程中直接进行,如果加载的资源过大,可能会导致程序卡顿,因为从硬盘读取数据到内存并进行处理…...
YOLOv11改进,YOLOv11添加GnConv递归门控卷积,二次创新C3k2结构
摘要 视觉 Transformer 在多种任务中取得了显著的成功,这得益于基于点积自注意力的新空间建模机制。视觉 Transformer 中的关键因素——即输入自适应、长距离和高阶空间交互——也可以通过卷积框架高效实现。作者提出了递归门控卷积(Recursive Gated Convolution,简称 gnCo…...
如何选择国产化CMS来建设政务网站?
在介绍CMS之前,我们先了解国家为什么要网站为什么要完成国产化改造? 1、信创国产化网站建站响应了国家的信息安全战略,支持自主可控的信息技术产业的发展,减少对进口软硬件的依赖,保障国家信息安全。 2、国产替代&…...
C/C++语言基础--initializer_list表达式、tuple元组、pair对组简介
本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 initializer_list表达式、tuple元组、pair对组再C日常还是比较常用的,尤其是对组在刷算法还是挺好用的,这里做一个简介;这三个语法结合C17的结构化绑定会更好用ÿ…...
paddle表格识别数据制作
数据格式 其中主要数据有两个一个表格结构的检测框,一个是tokens,注意的地方是 1、只能使用双引号,单引号不行 2、使用带引号的地方是tokens里面 "<tr>", "<td", " colspan2", ">",&quo…...
python selenium库的使用:通过兴趣点获取坐标
通过兴趣点获取坐标 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import TimeoutException# 保存Cookies到文件(可选) import pi…...
如何优化Kafka消费者的性能
要优化 Kafka 消费者性能,你可以考虑以下策略: 并行消费:通过增加消费者组中的消费者数量来并行处理更多的消息,从而提升消费速度。 批量消费:配置 fetch.min.bytes 和 fetch.max.wait.ms 参数来控制批量消费的大小和…...
机器学习 决策树
决策树-分类 1 概念 1、决策节点通过条件判断而进行分支选择的节点。如:将某个样本中的属性值(特征值)与决策节点上的值进行比较,从而判断它的流向。 2、叶子节点没有子节点的节点,表示最终的决策结果。 3、决策树的深度所有节点的最大层…...
效益登记册效益管理计划
效益登记册 benefit Register效益管理计划效益登记册汇集并列出项目集计划的效益,用于在项目集的整个持续时间内测量和沟通效益的交付。在效益识别阶段,效益登记册根据项目集商业论证、组织战略计划和其他相关项目集自标而编制。随后,登记册由…...
Go语言的零值可用性:优势与限制
Go语言以其简洁和高效的设计理念而著称,其中之一便是“零值可用”的特性。这一特性使得许多类型在未显式初始化时即可直接安全地使用,大大简化了代码的初始化过程。然而,并非所有类型都支持零值可用,且在使用时也存在一定的限制。…...
【自用】0-1背包问题与完全背包问题的Java实现
引言 背包问题是计算机科学领域的一个经典优化问题,分为多种类型,其中最常见的是0-1背包问题和完全背包问题。这两种问题的核心在于如何在有限的空间内最大化收益,但它们之间存在一些关键的区别:0-1背包问题允许每个物品只能选择…...
HTML5实现俄罗斯方块小游戏
文章目录 1.设计来源1.1 主界面1.2 皮肤风格1.2 游戏中界面1.3 游戏结束界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/143788449 HTML5实现俄罗斯方块小游戏&#x…...
Mybatis官方生成器使用示例
在这篇文章中,我们将通过实际代码示例来说明如何使用 MyBatis Generator (MBG) 来自动化生成 MyBatis 项目所需的实体类、Mapper 接口和 Mapper XML 文件。我们将使用一个 Maven 插件来执行代码生成,并提供详细的配置和解释。 1. MyBatis Generator 简介…...
演员王子辰—专注革命题材 《前行者》后再出发
2021年10月22日在北京卫视播出的由张鲁一、聂远等人主演的电视剧《前行者》,讲述了在二十世纪三十年代初,因叛徒出卖,我上海地下党组织遭到严重破坏,革命事业陷入一片白色恐怖之中。我党情报员马天目刚从法国归来,临危…...
Spring Boot基础教学:创建第一个Spring Boot项目
使用Spring Initializr生成项目 Spring Initializr是一个在线工具,用于快速生成Spring Boot项目的基本结构。以下是使用Spring Initializr创建项目的步骤: 步骤1:访问Spring Initializr 打开网址 start.spring.io。 步骤2:选择…...
基于SpringBoot+Vue实现校园多媒体信息共享平台
作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
