从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(绘图设备封装)

目录

图像层的底层抽象——绘图设备抽象

如何抽象一个绘图设备?

桥接绘图设备,特化为OLED设备

题外话:设备的属性,与设计一个相似函数化简的通用办法

使用函数指针来操作设备

总结一下


图像层的底层抽象——绘图设备抽象

在上一篇博客中,我们完成了对设备层的抽象。现在,我们终于可以卖出雄心壮志的一步了!那就是尝试去完成一个最为基础的图形库。我们要做的,就是设计一个更加复杂的绘图设备。

为什么是绘图设备呢?我们程序员都是懒蛋,想要最大程度的复用代码,省最大的力气干最多的事情。所以,我们的图像框架在未来,还会使用LCD绘制,还会使用其他形形色色的绘制设备来绘制我们的图像。而不仅限于OLED。所以,让我们抽象一个可以绘制的设备而不是一个OLED设备,是非常重要的。

一个绘图设备,是OLED设备的的子集。他可以开启关闭,完成绘制操作,刷新绘制操作,清空绘制操作。仅此而已。

typedef void*   CCDeviceRawHandle;
typedef void*   CCDeviceRawHandleConfig;
​
// 初始化设备,设备需要做一定的初始化后才能绘制图形
typedef void(*Initer)(CCDeviceHandler* handler, CCDeviceRawHandleConfig config);
​
// 清空设备
typedef void(*ClearDevice)(CCDeviceHandler* handler);
​
// 更新设备
typedef void(*UpdateDevice)(CCDeviceHandler* handler);
​
// 反色设备
typedef void(*ReverseDevice)(CCDeviceHandler* handler);
​
// 绘制点
typedef void(*SetPixel)(CCDeviceHandler* handler, uint16_t x, uint16_t y);
​
// 绘制面
typedef void(*DrawArea)(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* sources
);
​
// 面操作(清空,反色,更新等等,反正不需要外来提供绘制资源的操作)
typedef void(*AreaOperation)(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height
);
​
// 这个比较新,笔者后面讲
typedef enum{CommonProperty_WIDTH,CommonProperty_HEIGHT,CommonProperty_SUPPORT_RGB
}CommonProperty;
​
// 获取资源的属性
typedef void(*FetchProperty)(CCDeviceHandler*, void*, CommonProperty p);
​
// 一个绘图设备可以完成的操作
// 提示,其实可以化简,一些函数指针(或者说方法)是没有必要存在的,思考一下如何化简呢?
typedef struct __DeviceOperations 
{Initer          init_function;ClearDevice     clear_device_function;UpdateDevice    update_device_function;SetPixel        set_pixel_device_function;ReverseDevice   reverse_device_function;DrawArea        draw_area_device_function;AreaOperation   clearArea_function;AreaOperation   updateArea_function;AreaOperation   reverseArea_function;FetchProperty   property_function;
}CCDeviceOperations;
​
// 一个绘图设备的最终抽象
typedef struct __DeviceProperty
{/* device type */CCDeviceType            device_type;/* device raw data handle */CCDeviceRawHandle       handle;/* device functions */CCDeviceOperations      operations;
}CCDeviceHandler;

设计上笔者是自底向上设计的,笔者现在打算自顶向下带大伙解读一下我的代码。

如何抽象一个绘图设备?

这个设备是什么?是一个OLED?还是一个LCD?

/* device type */
CCDeviceType            device_type;

这个设备的底层保存资源是什么?当我们动手准备操作的时候,需要拿什么进行操作呢?

    /* device raw data handle */CCDeviceRawHandle       handle;

你不需要在使用的时候关心他到底是什么,因为我们从头至尾都在使用接口进行操作,你只需要知道,一个绘图设备可以绘制图像,这就足够了

    /* device functions */CCDeviceOperations      operations;

这里是我们的命根子,一个绘图设备可以完成的操作。我们在之后的设计会大量的见到operations这个操作。

笔者的operations借鉴了Linux是如何抽象文件系统的代码。显然,一个良好的面对对象C编写规范的参考代码就是Linux的源码

下一步,就是DeviceType有哪些呢?目前,我们开发的是OLED,也就意味着只有OLED是一个合法的DeviceType

typedef enum{OLED_Type
}CCDeviceType;

最后,我们需要思考的是,如何定义一个绘图设备的行为呢?我们知道我们现在操作的就是一个OLED,所以,我们的问题实际上就转化成为:

当我们给定了一个明确的,是OLED设备的绘图设备的时候,怎么联系起来绘图设备和OLED设备呢?

答案还是回到我们如何抽象设备层的代码上,那就是根据我们的类型来选择我们的方法。

/* calling this is not encouraged! */
void __register_paintdevice(CCDeviceHandler* blank_handler, CCDeviceRawHandle raw_handle, CCDeviceRawHandleConfig config, CCDeviceType type);
​
#define register_oled_paintdevice(handler, raw, config) \__register_paintdevice(handler, raw, config, OLED_Type)

所以,我们注册一个OLED的绘图设备,只需要调用接口register_oled_paintdevice就好了,提供一个干净的OLED_HANDLE和初始化OLED_HANDLE所需要的资源,我们的设备也就完成了初始化。

#include "Graphic/device_adapter/CCGraphic_device_oled_adapter.h"
#include "Graphic/CCGraphic_device_adapter.h"
​
void __register_paintdevice(CCDeviceHandler* blank_handler, CCDeviceRawHandle raw_handle, CCDeviceRawHandleConfig config, CCDeviceType type)
{blank_handler->handle = raw_handle;blank_handler->device_type = type;switch(type){case OLED_Type:{blank_handler->operations.init_function = (Initer)init_device_oled;blank_handler->operations.clear_device_function =clear_device_oled;blank_handler->operations.set_pixel_device_function = setpixel_device_oled;blank_handler->operations.update_device_function = update_device_oled;blank_handler->operations.clearArea_function =clear_area_device_oled;blank_handler->operations.reverse_device_function =reverse_device_oled;blank_handler->operations.reverseArea_function = reversearea_device_oled;blank_handler->operations.updateArea_function = update_area_device_oled;blank_handler->operations.draw_area_device_function =draw_area_device_oled;blank_handler->operations.property_function = property_fetcher_device_oled;}break;}blank_handler->operations.init_function(blank_handler, config);
}

这个仍然是最空泛的代码,我们只是简单的桥接了一下,声明我们的设备是OLED,还有真正完成桥接的文件:CCGraphic_device_oled_adapter文件没有给出来。所以,让我们看看实际上是如何真正的完成桥接的。

桥接绘图设备,特化为OLED设备

什么是桥接?什么是特化?桥接指的是讲一个抽象结合过度到另一个抽象上,在这里,我们讲绘图设备引渡到我们的OLED设备而不是其他更加宽泛的设备上去,而OLED设备属于绘图设备的一个子集,看起来,我们就像是把虚无缥缈的“绘图设备”落地了,把一个抽象的概念更加具体了。我们的聊天从“用绘图设备完成XXX”转向了“使用一个OLED作为绘图设备完成XXX”了。这就是特化,将一个概念明晰起来。

#include "Graphic/CCGraphic_device_adapter.h"
#include "OLED/Driver/oled_config.h"
​
/* * 提供用于 OLED 设备的相关操作函数 */
​
/*** @struct CCGraphic_OLED_Config* @brief OLED 设备的配置结构体*/
typedef struct {OLED_Driver_Type    createType;      // OLED 驱动类型(软 I2C、硬 I2C 等)void*               related_configs; // 与驱动相关的具体配置
} CCGraphic_OLED_Config;
​
/*** @brief 初始化 OLED 设备* @param blank 空的设备句柄,初始化后填充* @param onProvideConfigs OLED 配置参数指针,包含驱动类型及配置* * @note 调用此函数时需要传递初始化好的配置(软 I2C 或硬 I2C 配置等)*/
void init_device_oled(CCDeviceHandler* blank, CCGraphic_OLED_Config* onProvideConfigs);
​
/*** @brief 刷新整个 OLED 屏幕内容* @param handler 设备句柄*/
void update_device_oled(CCDeviceHandler* handler);
​
/*** @brief 清空 OLED 屏幕内容* @param handler 设备句柄*/
void clear_device_oled(CCDeviceHandler* handler);
​
/*** @brief 设置指定位置的像素点* @param handler 设备句柄* @param x 横坐标* @param y 纵坐标*/
void setpixel_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y);
​
/*** @brief 清除指定区域的显示内容* @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度*/
void clear_area_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
​
/*** @brief 更新指定区域的显示内容* @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度*/
void update_area_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
​
/*** @brief 反转整个屏幕的显示颜色* @param handler 设备句柄*/
void reverse_device_oled(CCDeviceHandler* handler);
​
/*** @brief 反转指定区域的显示颜色* @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度*/
void reversearea_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
​
/*** @brief 绘制指定区域的图像* @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度* @param sources 图像数据源指针*/
void draw_area_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* sources
);
​
/*** @brief 获取设备属性* @param handler 设备句柄* @param getter 属性获取指针* @param p 属性类型*/
void property_fetcher_device_oled(CCDeviceHandler* handler, void* getter, CommonProperty p
);
​

好在代码实际上并不困难,具体的代码含义我写在下面了,可以参考看看

#include "Graphic/device_adapter/CCGraphic_device_oled_adapter.h"
#include "OLED/Driver/oled_base_driver.h"
​
/*** @brief 初始化 OLED 设备* * 根据提供的配置(软 I2C、硬 I2C、软 SPI、硬 SPI)初始化 OLED 设备。* * @param blank 空的设备句柄,初始化后填充* @param onProvideConfigs OLED 配置参数指针,包含驱动类型及具体配置*/
void init_device_oled(CCDeviceHandler* blank, CCGraphic_OLED_Config* onProvideConfigs)
{OLED_Handle* handle = (OLED_Handle*)(blank->handle);OLED_Driver_Type type = onProvideConfigs->createType;
​switch(type){case OLED_SOFT_IIC_DRIVER_TYPE:oled_init_softiic_handle(handle,(OLED_SOFT_IIC_Private_Config*) (onProvideConfigs->related_configs));break;
​case OLED_HARD_IIC_DRIVER_TYPE:oled_init_hardiic_handle(handle, (OLED_HARD_IIC_Private_Config*)(onProvideConfigs->related_configs));break;
​case OLED_SOFT_SPI_DRIVER_TYPE:oled_init_softspi_handle(handle,(OLED_SOFT_SPI_Private_Config*)(onProvideConfigs->related_configs));break;
​case OLED_HARD_SPI_DRIVER_TYPE:oled_init_hardspi_handle(handle,(OLED_HARD_SPI_Private_Config*)(onProvideConfigs->related_configs));break;}
}
​
/*** @brief 刷新整个 OLED 屏幕内容* * @param handler 设备句柄*/
void update_device_oled(CCDeviceHandler* handler)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_update(handle);
}
​
/*** @brief 清空 OLED 屏幕内容* * @param handler 设备句柄*/
void clear_device_oled(CCDeviceHandler* handler)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_clear_frame(handle);
}
​
/*** @brief 设置指定位置的像素点* * @param handler 设备句柄* @param x 横坐标* @param y 纵坐标*/
void setpixel_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_setpixel(handle, x, y);
}
​
/*** @brief 清除指定区域的显示内容* * @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度*/
void clear_area_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_clear_area(handle, x, y, width, height);
}
​
/*** @brief 更新指定区域的显示内容* * @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度*/
void update_area_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_update_area(handle, x, y, width, height);
}
​
/*** @brief 反转整个屏幕的显示颜色* * @param handler 设备句柄*/
void reverse_device_oled(CCDeviceHandler* handler)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_reverse(handle);
}
​
/*** @brief 反转指定区域的显示颜色* * @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度*/
void reversearea_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_reversearea(handle, x, y, width, height);
}
​
/*** @brief 绘制指定区域的图像* * @param handler 设备句柄* @param x 区域起点的横坐标* @param y 区域起点的纵坐标* @param width 区域宽度* @param height 区域高度* @param sources 图像数据源指针*/
void draw_area_device_oled(CCDeviceHandler* handler, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* sources
){OLED_Handle* handle = (OLED_Handle*)handler->handle;oled_helper_draw_area(handle, x, y, width, height, sources);
}
​
/*** @brief 获取 OLED 设备属性* * @param handler 设备句柄* @param getter 属性获取指针* @param p 属性类型(如:高度、宽度、是否支持 RGB 等)*/
void property_fetcher_device_oled(CCDeviceHandler* handler, void* getter, CommonProperty p
)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;switch (p){case CommonProperty_HEIGHT:{   int16_t* pHeight = (int16_t*)getter;*pHeight = oled_height(handle);} break;
​case CommonProperty_WIDTH:{int16_t* pWidth = (int16_t*)getter;*pWidth = oled_width(handle);} break;
​case CommonProperty_SUPPORT_RGB:{uint8_t* pSupportRGB = (uint8_t*)getter;*pSupportRGB = oled_support_rgb(handle);} break;
​default:break;}
}
题外话:设备的属性,与设计一个相似函数化简的通用办法

绘图设备有自己的属性,比如说告知自己的可绘图范围,是否支持RGB色彩绘图等等,我们的办法是提供一个对外暴露的可以访问的devicePropertyEnum

typedef enum{CommonProperty_WIDTH,CommonProperty_HEIGHT,CommonProperty_SUPPORT_RGB
}CommonProperty;

设计一个接口,这个接口函数就是FetchProperty

typedef void(*FetchProperty)(CCDeviceHandler*, void*, CommonProperty p);

上层框架代码提供一个承接返回值的void*和查询的设备以及查询类型,我们就返回这个设备的期望属性

/*** @brief 获取 OLED 设备属性* * @param handler 设备句柄* @param getter 属性获取指针* @param p 属性类型(如:高度、宽度、是否支持 RGB 等)*/
void property_fetcher_device_oled(CCDeviceHandler* handler, void* getter, CommonProperty p
)
{OLED_Handle* handle = (OLED_Handle*)handler->handle;switch (p){case CommonProperty_HEIGHT:{   int16_t* pHeight = (int16_t*)getter;*pHeight = oled_height(handle);} break;
​case CommonProperty_WIDTH:{int16_t* pWidth = (int16_t*)getter;*pWidth = oled_width(handle);} break;
​case CommonProperty_SUPPORT_RGB:{uint8_t* pSupportRGB = (uint8_t*)getter;*pSupportRGB = oled_support_rgb(handle);} break;
​default:break;}
}

这个就是一种设计返回相似内容的数据的设计思路,将过多相同返回的函数简化为一个函数,将差异缩小到使用枚举宏而不是一大坨函数到处拉屎的设计方式

任务提示:笔者这里实际上做的不够好,你需要知道的是,我在这里是没有做错误处理的。啥意思?你必须让人家知道你返回的值是不是合法的,人家才知道这个值敢不敢用。

笔者提示您,两种办法:

  1. 返回值上动手脚:这个是笔者推介的,也是Linux设备代码中使用的,那就是将属性获取的函数签名返回值修改为uint8_t,或者更进一步的封装:

    typedef enum {FETCH_PROPERTY_FAILED;  // 0, YOU CAN USE AS FALSE, BUT NOT RECOMMENDED!FETCH_PROPERTY_SUCCESS; // 1, YOU CAN USE AS TRUE, BUT NOT RECOMMENDED!
    }FetchPropertyStatus;
    ​
    /*** @brief 获取 OLED 设备属性* * @param handler 设备句柄* @param getter 属性获取指针* @param p 属性类型(如:高度、宽度、是否支持 RGB 等)* @return */
    FetchPropertyStatus property_fetcher_device_oled(CCDeviceHandler* handler, void* getter, CommonProperty p
    )
    {OLED_Handle* handle = (OLED_Handle*)handler->handle;switch (p){case CommonProperty_HEIGHT:{   int16_t* pHeight = (int16_t*)getter;*pHeight = oled_height(handle);} break;
    ​case CommonProperty_WIDTH:{int16_t* pWidth = (int16_t*)getter;*pWidth = oled_width(handle);} break;
    ​case CommonProperty_SUPPORT_RGB:{uint8_t* pSupportRGB = (uint8_t*)getter;*pSupportRGB = oled_support_rgb(handle);} break;
    ​default:return FETCH_PROPERTY_FAILED; // not supported property}return FETCH_PROPERTY_SUCCESS; // fetched value can be used for further
    }

    使用上,事情也就变得非常的简单,笔者后面的一个代码

        int16_t device_width = 0;device_handle->operations.property_function(device_handle, &device_width, CommonProperty_WIDTH);int16_t device_height = 0;device_handle->operations.property_function(device_handle, &device_height, CommonProperty_HEIGHT);

    也就可以更加合理的修改为

        FetchPropertyStatus status;// fetch the width propertyint16_t device_width = 0;status = device_handle->operations.property_function(device_handle, &device_width, CommonProperty_WIDTH);// check if the value validif(!statue){// handling error, or enter HAL_Hard_Fault... anyway!}int16_t device_height = 0;statue = device_handle->operations.property_function(device_handle, &device_height, CommonProperty_HEIGHT);// check if the value validif(!statue){// handling error, or enter HAL_Hard_Fault... anyway!}// now pass the check// use the variable directly...
  2. 选取一个非法值。比如说

    #define INVALID_PROPERTY_VALUE      -1
    ...
    default:{   (int8_t*)value = (int8_t*)getter;value = INVALID_PROPERTY_VALUE;}

    但是显然不好!我们没办法区分:是不支持这个属性呢?还是设备的返回值确实就是-1呢?谁知道呢?所以笔者很不建议在这样的场景下这样做!甚至更糟糕的,如果是返回设备的长度,我们使用的是uint16_t接受,那么我们完全没办法区分究竟是设备是0xFFFF长,还是是非法值呢?我们一不小心把判断值的非法和值的含义本身混淆在一起了!

现在,我们就可以完成对一整个设备的抽象了。

使用函数指针来操作设备

笔者之前的代码已经反反复复出现了使用函数指针而不是调用函数来进行操作,从开销分析上讲,我们多了若干次的解引用操作,但是从封装上,我们明确的归属了函数隶属于绘图设备的方法,在极大量的代码下,这样起到了一种自说明的效果。

比起来,在业务层次(拿库做应用的层次,比如说开发一个OLED菜单,做一个恐龙奔跑小游戏,或者是绘制电棍突脸尖叫的动画),我们只需要强调是这个设备在绘图

device_handle->operations.updateArea_function(...);

而不是我们让绘图的是这个设备

updateArea_device(device_handle, ...);

显然前者更加的自然。

总结一下

其实,就是完成了对绘图设备的特化,现在,我们终于可以直接使用Device作为绘图设备而不是OLED_Handle,下一步,我们就开始真正的手搓设备绘制了。

目录导览

总览

协议层封装

OLED设备封装

绘图设备抽象

基础图形库封装

基础组件实现

动态菜单组件实现

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

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

相关文章

使用Edu邮箱申请一年免费的.me域名

所需材料:公立Edu教育邮箱一枚(P.S:该服务不支持所有的Edu教育邮箱,仅支持比较知名的院校) 说到域名,.me这个后缀可谓是个性十足,适合个人网站、博客等。.me是黑山的国家顶级域名(c…

7.抽象工厂(Abstract Factory)

抽象工厂与工厂方法极其类似,都是绕开new的,但是有些许不同。 动机 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。 假设案例 假设…

Visual Studio使用GitHub Copilot提高.NET开发工作效率

GitHub Copilot介绍 GitHub Copilot 是一款 AI 编码助手,可帮助你更快、更省力地编写代码,从而将更多精力集中在问题解决和协作上。 GitHub Copilot Free包含哪些功能? 每月 2000 代码补全,帮助开发者快速完成代码编写。 每月 …

[JavaWeb]搜索表单区域

一.注意事项 设置外边距:margin:(参数可省去部分)上 下 左 右 二.源代码 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <title>Tlias智能学习辅助系统</title> <style> /* 导航栏样…

NLP自然语言处理通识

目录 ELMO 一、ELMo的核心设计理念 1. 静态词向量的局限性 2. 动态上下文嵌入的核心思想 3. 层次化特征提取 二、ELMo的模型结构与技术逻辑 1. 双向语言模型&#xff08;BiLM&#xff09; 2. 多层LSTM的层次化表示 三、ELMo的运行过程 1. 预训练阶段 2. 下游任务微调 四、ELMo的…

Eureka 服务注册和服务发现的使用

1. 父子工程的搭建 首先创建一个 Maven 项目&#xff0c;删除 src &#xff0c;只保留 pom.xml 然后来进行 pom.xml 的相关配置 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xs…

OpenCV:二值化与自适应阈值

目录 简述 1. 什么是二值化 2. 二值化接口 2.1 参数说明​​​​​ 2.2 示例代码 2.3 运行结果 3. 自适应阈值 3.1 参数说明 3.2 示例代码 3.3 运行结果 4. 总结 4.1 二值化 4.2 自适应阈值 相关阅读 OpenCV&#xff1a;图像的腐蚀与膨胀-CSDN博客 简述 图像二值…

Java面试题2025-设计模式

1.说一下开发中需要遵守的设计原则&#xff1f; 设计模式中主要有六大设计原则&#xff0c;简称为SOLID &#xff0c;是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的)&#xff0c;六大设计原则分别如下&#xff1a; 1、单一职责原则 单一职责原则的定义描述非…

Win11下帝国时代2无法启动解决方法

鼠标右键点图标&#xff0c;选择属性 点开始&#xff0c;输入启用和关闭

JAVA实战开源项目:在线文档管理系统(Vue+SpringBoot) 附源码

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

Python设计模式 - 组合模式

定义 组合模式&#xff08;Composite Pattern&#xff09; 是一种结构型设计模式&#xff0c;主要意图是将对象组织成树形结构以表示"部分-整体"的层次结构。这种模式能够使客户端统一对待单个对象和组合对象&#xff0c;从而简化了客户端代码。 组合模式有透明组合…

算法每日双题精讲 —— 前缀和(【模板】一维前缀和,【模板】二维前缀和)

在算法竞赛与日常编程中&#xff0c;前缀和是一种极为实用的预处理技巧&#xff0c;能显著提升处理区间和问题的效率。今天&#xff0c;我们就来深入剖析一维前缀和与二维前缀和这两个经典模板。 一、【模板】一维前缀和 题目描述 给定一个长度为 n n n 的整数数组 a a a&…

VLLM性能调优

1. 抢占 显存不够的时候&#xff0c;某些request会被抢占。其KV cache被清除&#xff0c;腾退给其他request&#xff0c;下次调度到它&#xff0c;重新计算KV cache。 报这条消息&#xff0c;说明已被抢占&#xff1a; WARNING 05-09 00:49:33 scheduler.py:1057 Sequence gr…

知识管理系统塑造企业文化与学习型组织的变革之路

内容概要 知识管理系统&#xff08;Knowledge Management System, KMS&#xff09;是指组织内部为有效获取、存储、共享和应用知识而建立的结构和技术体系。这一系统不仅是信息技术的运用&#xff0c;更是推动企业文化和学习型组织发展的重要工具。在当今快速变化的商业环境中…

智能汽车网络安全威胁报告

近年来随着智能汽车技术的快速发展&#xff0c;针对智能汽车的攻击也逐渐从传统的针对单一车辆控制器的攻击转变为针对整车智能化服务的攻击&#xff0c;包括但不限于对远程控制应用程序的操控、云服务的渗透、智能座舱系统的破解以及对第三方应用和智能服务的攻击。随着WP.29 …

Python练习(2)

今日题单 吃鱼还是吃肉 PTA | 程序设计类实验辅助教学平台 降价提醒机器人PTA | 程序设计类实验辅助教学平台 幸运彩票 PTA | 程序设计类实验辅助教学平台 猜帽子游戏 PTA | 程序设计类实验辅助教学平台 谁管谁叫爹 PTA | 程序设计类实验辅助教学平台 就不告诉你 PTA | 程…

Ubuntu-手动安装 SBT

文章目录 前言Ubuntu-手动安装 SBT1. SBT是什么?1.1. SBT 的特点1.2. SBT 的基本功能1.3. SBT 的常用命令 2. 安装2.1. 下载2.2. 解压 sbt 二进制包2.3. 确认 sbt 可执行文件的位置2.4. 设置执行权限2.5. 创建符号链接2.6. 更新 PATH 环境变量2.7. 验证 sbt 安装 前言 如果您觉…

240. 搜索二维矩阵||

参考题解&#xff1a;https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/2361487/240-sou-suo-er-wei-ju-zhen-iitan-xin-qin-7mtf 将矩阵旋转45度&#xff0c;可以看作一个二叉搜索树。 假设以左下角元素为根结点&#xff0c; 当target比root大的时候&#xff…

Golang笔记——常用库context和runtime

大家好&#xff0c;这里是Good Note&#xff0c;关注 公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Golang的常用库context和runtime&#xff0c;包括库的基本概念和基本函数的使用等。 文章目录 contextcontext 包的基本概念主要类型和函数1. **…

Leetcode:350

1&#xff0c;题目 2&#xff0c;思路 首先判断那个短为什么呢因为我们用短的数组去挨个点名长的数组主要用map装长的数组max判断map里面有几个min数组的元素&#xff0c;list保存交集最后用数组返回list的内容 3&#xff0c;代码 import java.util.*;public class Leetcode…