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

优化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_castconst_caststatic_castdynamic_cast
  • Rule 5-0-2:要求类型转换必须明确指定转换类型,确保转换符合预期行为,避免使用不安全的转换方式。
  • Rule 5-1-1:在进行类型转换时,必须确保转换类型合法,不破坏对象的常量性或类型。

同时,虚函数和多态机制在嵌入式系统中也有劣势:

  • 运行时开销:虚函数表(vtable)的查找会带来额外的运行时开销,增加程序的资源需求。
  • RTTI(运行时类型信息)开销:启用 RTTI 会增加内存使用,尤其是在 dynamic_casttypeid 被使用时。
  • MISRA C++的限制:标准要求避免使用虚函数和多态机制,特别是在没有严格控制的情况下,可能会导致代码行为不可预测,增加出错的风险。

1.2. 虚函数与多态问题的影响及如何适应MISRA C++要求

虚函数和多态机制在嵌入式系统中,往往面临以下问题:

  • 运行时性能问题:虚函数表查找、RTTI机制的引入,都会增加程序的运行时开销,尤其是在资源受限的嵌入式系统中,可能会显著影响系统性能。
  • 内存开销:启用 RTTI 会使每个多态类占用额外的内存,这对于内存有限的嵌入式系统是一个不小的负担。
  • MISRA C++的限制:虚函数和多态机制的使用在 MISRA C++ 中受到限制,因为它们增加了代码的复杂性,降低了可预测性,并且可能违反一些规则,如避免不安全的类型转换。

为了适应 MISRA C++ 的要求,可以考虑以下几点:

  • 避免虚函数和多态:尽量避免使用运行时多态和虚函数,减少 RTTI 开销,避免使用 dynamic_casttypeid
  • 设计时决策:通过明确的类型接口和结构体来实现类型转换,确保每个类型转换都能被清晰审查和验证。
  • 使用强类型系统:避免使用 void* 等不明确类型的指针,确保类型清晰,从而减少不必要的类型转换。

1.3. 模板编程的优势:替代虚函数和多态机制

模板编程通过在编译时绑定函数和类,避免运行时的虚函数查找和多态性问题。使用模板编程的优势如下:

  • 编译时绑定:模板代码在编译时就确定了函数调用和对象行为,避免了虚函数表的查找。
  • 内联优化:编译器能够对模板代码进行内联优化,直接将模板代码嵌入到调用点。
  • 提高执行效率:由于没有动态绑定和运行时类型信息,模板编程能够显著降低程序的运行时开销,提升性能。

模板编程避免了虚函数和多态机制的开销,并且在遵循 MISRA C++ 标准时,能够更容易地实现高效且符合规范的设计模式。

2. 设计模式改进

在传统的设计模式中,许多模式(如单例模式、工厂模式、观察者模式等)依赖于虚函数和继承来实现多态和灵活的行为切换。然而,这种方式虽然灵活,但会带来运行时的开销,包括虚函数表查找、内存使用的增加以及与运行时类型信息(RTTI)相关的性能损失。

模板编程静态多态 通过在编译时进行决策,避免了虚函数调用和动态绑定的需要。通过使用模板,我们可以在不牺牲灵活性的情况下,提高代码的执行效率,并且能够使代码更加符合 MISRA C++ 等标准的要求。

以下是一些常见设计模式的改进方式,尽量在不违背设计原则的情况下提升代码的性能。

2.1. 单例模式的改进

传统的单例模式通过 static 变量和线程同步机制来保证只创建一个实例。然而,如果使用虚函数或者多态,在某些情况下可能会影响线程安全性,增加程序的复杂度。在 C++11/14 中,可以利用 std::once_flagstd::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. 模板编程的优势&#xff1a;替代虚函数和多态机制 2. 设计模式改进2.1. 单例模式的改进与静态局部变量的对比(第二种实现) 2.…...

浪浪云轻量服务器搭建vulfocus网络安全靶场

什么是网络安全靶场 网络安全靶场是一个模拟真实网络环境的训练平台&#xff0c;旨在为网络安全专业人员提供一个安全的环境来测试和提高他们的技能。靶场通常包括各种网络设备、操作系统、应用程序和安全工具&#xff0c;允许用户在其中进行攻击和防御练习。以下是网络安全靶…...

C++builder中的人工智能(23):在现代C++ Windows上轻松录制声音

在这篇文章中&#xff0c;我们将探讨如何在现代C Windows上轻松录制声音。声音以波形和数字形式存在&#xff0c;其音量随时间变化。在C Builder中&#xff0c;使用Windows设备进行录音非常简单。要录制声音&#xff0c;在多设备应用程序中&#xff0c;必须使用FMX.Media.hpp头…...

避免误差!Android 中正确计算时间差的方式

在 Android 开发中&#xff0c;计时和计算时间差异是非常常见的需求&#xff0c;比如记录事件发生的间隔、统计应用启动时间、测量网络请求的响应时间等。在实现这些功能时&#xff0c;我们通常需要一个可靠的时间源来确保计时的准确性。那么为什么 Android 推荐使用 SystemClo…...

unity3d————Resources异步加载

知识点一&#xff1a;Resources异步加载是什么&#xff1f; 在Unity中&#xff0c;资源加载可以分为同步加载和异步加载两种方式。同步加载会在主线程中直接进行&#xff0c;如果加载的资源过大&#xff0c;可能会导致程序卡顿&#xff0c;因为从硬盘读取数据到内存并进行处理…...

YOLOv11改进,YOLOv11添加GnConv递归门控卷积,二次创新C3k2结构

摘要 视觉 Transformer 在多种任务中取得了显著的成功,这得益于基于点积自注意力的新空间建模机制。视觉 Transformer 中的关键因素——即输入自适应、长距离和高阶空间交互——也可以通过卷积框架高效实现。作者提出了递归门控卷积(Recursive Gated Convolution,简称 gnCo…...

如何选择国产化CMS来建设政务网站?

在介绍CMS之前&#xff0c;我们先了解国家为什么要网站为什么要完成国产化改造&#xff1f; 1、信创国产化网站建站响应了国家的信息安全战略&#xff0c;支持自主可控的信息技术产业的发展&#xff0c;减少对进口软硬件的依赖&#xff0c;保障国家信息安全。 2、国产替代&…...

C/C++语言基础--initializer_list表达式、tuple元组、pair对组简介

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 initializer_list表达式、tuple元组、pair对组再C日常还是比较常用的&#xff0c;尤其是对组在刷算法还是挺好用的&#xff0c;这里做一个简介&#xff1b;这三个语法结合C17的结构化绑定会更好用&#xff…...

paddle表格识别数据制作

数据格式 其中主要数据有两个一个表格结构的检测框&#xff0c;一个是tokens&#xff0c;注意的地方是 1、只能使用双引号&#xff0c;单引号不行 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到文件&#xff08;可选&#xff09; import pi…...

如何优化Kafka消费者的性能

要优化 Kafka 消费者性能&#xff0c;你可以考虑以下策略&#xff1a; 并行消费&#xff1a;通过增加消费者组中的消费者数量来并行处理更多的消息&#xff0c;从而提升消费速度。 批量消费&#xff1a;配置 fetch.min.bytes 和 fetch.max.wait.ms 参数来控制批量消费的大小和…...

机器学习 决策树

决策树-分类 1 概念 1、决策节点通过条件判断而进行分支选择的节点。如&#xff1a;将某个样本中的属性值(特征值)与决策节点上的值进行比较&#xff0c;从而判断它的流向。 2、叶子节点没有子节点的节点&#xff0c;表示最终的决策结果。 3、决策树的深度所有节点的最大层…...

效益登记册效益管理计划

效益登记册 benefit Register效益管理计划效益登记册汇集并列出项目集计划的效益&#xff0c;用于在项目集的整个持续时间内测量和沟通效益的交付。在效益识别阶段&#xff0c;效益登记册根据项目集商业论证、组织战略计划和其他相关项目集自标而编制。随后&#xff0c;登记册由…...

Go语言的零值可用性:优势与限制

Go语言以其简洁和高效的设计理念而著称&#xff0c;其中之一便是“零值可用”的特性。这一特性使得许多类型在未显式初始化时即可直接安全地使用&#xff0c;大大简化了代码的初始化过程。然而&#xff0c;并非所有类型都支持零值可用&#xff0c;且在使用时也存在一定的限制。…...

【自用】0-1背包问题与完全背包问题的Java实现

引言 背包问题是计算机科学领域的一个经典优化问题&#xff0c;分为多种类型&#xff0c;其中最常见的是0-1背包问题和完全背包问题。这两种问题的核心在于如何在有限的空间内最大化收益&#xff0c;但它们之间存在一些关键的区别&#xff1a;0-1背包问题允许每个物品只能选择…...

HTML5实现俄罗斯方块小游戏

文章目录 1.设计来源1.1 主界面1.2 皮肤风格1.2 游戏中界面1.3 游戏结束界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/143788449 HTML5实现俄罗斯方块小游戏&#x…...

Mybatis官方生成器使用示例

在这篇文章中&#xff0c;我们将通过实际代码示例来说明如何使用 MyBatis Generator (MBG) 来自动化生成 MyBatis 项目所需的实体类、Mapper 接口和 Mapper XML 文件。我们将使用一个 Maven 插件来执行代码生成&#xff0c;并提供详细的配置和解释。 1. MyBatis Generator 简介…...

演员王子辰—专注革命题材 《前行者》后再出发

2021年10月22日在北京卫视播出的由张鲁一、聂远等人主演的电视剧《前行者》&#xff0c;讲述了在二十世纪三十年代初&#xff0c;因叛徒出卖&#xff0c;我上海地下党组织遭到严重破坏&#xff0c;革命事业陷入一片白色恐怖之中。我党情报员马天目刚从法国归来&#xff0c;临危…...

Spring Boot基础教学:创建第一个Spring Boot项目

使用Spring Initializr生成项目 Spring Initializr是一个在线工具&#xff0c;用于快速生成Spring Boot项目的基本结构。以下是使用Spring Initializr创建项目的步骤&#xff1a; 步骤1&#xff1a;访问Spring Initializr 打开网址 start.spring.io。 步骤2&#xff1a;选择…...

基于SpringBoot+Vue实现校园多媒体信息共享平台

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…...

WebRTC API分析

主题 本文详细描述常用的webrtc api 媒体协商类 myPeerConnection.createOffer([options]); var options { offerToReceiveAudio: true, // 告诉另一端&#xff0c;你是否想接收音频&#xff0c;默认true offerToReceiveVideo: true, // 告诉另一端&a…...

ArkTS学习笔记:ArkTS起步

ArkTS是HarmonyOS的主力应用开发语言&#xff0c;基于TypeScript扩展&#xff0c;强化了静态检查和分析&#xff0c;旨在提升程序稳定性和性能。它采用静态类型&#xff0c;禁止运行时改变对象布局&#xff0c;并对UI开发框架能力进行扩展&#xff0c;支持声明式UI描述和自定义…...

spring-gateway网关聚合swagger实现多个服务接口切换

前提条件 微服务已经集成了swagger&#xff0c;并且注册进了nacos。 gateway配置 package com.zmy.springcloud.config;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springfra…...

关于 Oracle Database Express Edition 的功能和安装

Oracle Database Express Edition&#xff0c;简称 Oracle Database XE。是一个免费的版本&#xff0c;主要用于培训和一些功能要求比较简单&#xff0c;又需要免费分发的场景。 看看官方的说明&#xff1a; Whether you are a developer, a DBA, a data scientist, an educat…...

领夹麦克风哪个品牌好,手机领夹麦克风哪个牌子好,选购推荐

​无线麦克风凭借其无与伦比的便携性与灵活性&#xff0c;成为在演讲、表演以及会议等多种场合中不可或缺的有力帮手。它挣脱了线缆的束缚&#xff0c;使得声音的传播更加自由自在。其操作十分简便&#xff0c;只需简单配对就能投入使用&#xff0c;从而可以轻松地适应各类场景…...

什么是 Go 语言?

Go 语言&#xff08;也称为 Golang&#xff09;是由 Google 开发的一种开源编程语言。它最初由 Rob Pike、Ken Thompson 和 Robert Griesemer 等人于 2007 年设计&#xff0c;经过两年的研发&#xff0c;于 2009 年首次公开发布。Go 语言的设计目标是提高编程效率&#xff0c;特…...

AI 大模型重塑软件开发流程:定义、应用、优势与挑战

随着人工智能技术的飞速发展&#xff0c;AI 大模型正在深刻影响软件开发的各个环节。从代码自动生成到智能测试&#xff0c;AI 大模型不仅提高了开发效率&#xff0c;还带来了全新的开发模式和流程变化。本文将从 AI 大模型的定义、应用场景、优势以及挑战等方面&#xff0c;探…...

微服务即时通讯系统的实现(客户端)----(1)

目录 1. 项目整体介绍1.1 项目概况1.2 界面预览和功能介绍1.3 技术重点和服务器架构 2. 项目环境搭建2.1 安装Qt62.3 安装vcpkg2.3 安装protobuf2.4 构建项目2.5 配置CMake属性 3. 项目核心数据结构的实现3.1 创建data.h存放核心的类3.2 工具函数的实现3.3 创建编译开关 4. 界面…...

【freertos】FreeRTOS时间管理

FreeRTOS时间管理 一、睡眠延时函数1、vTaskDelay2、vTaskDelayUntil3、相对延时与绝对延时对比 二、自定义延时函数1、微秒延时2、毫秒延时 一、睡眠延时函数 1、vTaskDelay \quad 在UCOSIII 中延时函数OSTimeDly()可以设置为三种模式:相对模式、周期模式和绝对模式。在FreeR…...

台式电脑没有声音怎么办?台式电脑没有声音解决详解

台式电脑一般来说都是没有内置扬声器的&#xff0c;需要连接耳机或者是音响才可以播放音乐。那么如果遇到台式电脑没有声音的问题&#xff0c;我们也需要确认这些设备硬件有没问题&#xff0c;知道原因才可以进行处理。下面本文将为你介绍台式电脑没有声音的可能原因和解决方法…...