从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;}
}
这个就是一种设计返回相似内容的数据的设计思路,将过多相同返回的函数简化为一个函数,将差异缩小到使用枚举宏而不是一大坨函数到处拉屎的设计方式
任务提示:笔者这里实际上做的不够好,你需要知道的是,我在这里是没有做错误处理的。啥意思?你必须让人家知道你返回的值是不是合法的,人家才知道这个值敢不敢用。
笔者提示您,两种办法:
返回值上动手脚:这个是笔者推介的,也是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...
选取一个非法值。比如说
#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设备封装
绘图设备抽象
基础图形库封装
基础组件实现
动态菜单组件实现
相关文章:

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(绘图设备封装)
目录 图像层的底层抽象——绘图设备抽象 如何抽象一个绘图设备? 桥接绘图设备,特化为OLED设备 题外话:设备的属性,与设计一个相似函数化简的通用办法 使用函数指针来操作设备 总结一下 图像层的底层抽象——绘图设备抽象 在…...

Android学习19 -- 手搓App
1 前言 之前工作中,很多时候要搞一个简单的app去验证底层功能,Android studio又过于重型,之前用gradle,被版本匹配和下载外网包折腾的堪称噩梦。所以搞app都只有找应用的同事帮忙。一直想知道一些简单的app怎么能手搓一下&#x…...

pytorch基于GloVe实现的词嵌入
PyTorch 实现 GloVe(Global Vectors for Word Representation) 的完整代码,使用 中文语料 进行训练,包括 共现矩阵构建、模型定义、训练和测试。 1. GloVe 介绍 基于词的共现信息(不像 Word2Vec 使用滑动窗口预测&…...

SpringCloud篇 微服务架构
1. 工程架构介绍 1.1 两种工程架构模型的特征 1.1.1 单体架构 上面这张图展示了单体架构(Monolithic Architecture)的基本组成和工作原理。单体架构是一种传统的软件架构模式,其中所有的功能都被打包在一个单一的、紧密耦合的应用程序中。 …...

背包问题和单调栈
背包问题(动态规划) 动态五步曲 dp数组及下标索引的含义递推公式dp数组如何初始化遍历顺序打印dp数组 01背包:n种物品,有一个,二维数组遍历顺序可以颠倒,(滚动数组)一维数组遍历顺序不可颠倒…...

Java | CompletableFuture详解
关注:CodingTechWork CompletableFuture 概述 介绍 CompletableFuture是 Java 8 引入的一个非常强大的类,属于 java.util.concurrent 包。它是用于异步编程的一个工具,可以帮助我们更方便地处理并发任务。与传统的线程池或 Future 对比&…...

【背包问题】二维费用的背包问题
目录 二维费用的背包问题详解 总结: 空间优化: 1. 状态定义 2. 状态转移方程 3. 初始化 4. 遍历顺序 5. 时间复杂度 例题 1,一和零 2,盈利计划 二维费用的背包问题详解 前面讲到的01背包中,对物品的限定条件…...

Golang 并发机制-5:详解syn包同步原语
并发性是现代软件开发的一个基本方面,Go(也称为Golang)为并发编程提供了一组健壮的工具。Go语言中用于管理并发性的重要包之一是“sync”包。在本文中,我们将概述“sync”包,并深入研究其最重要的同步原语之一…...

实验六 项目二 简易信号发生器的设计与实现 (HEU)
声明:代码部分使用了AI工具 实验六 综合考核 Quartus 18.0 FPGA 5CSXFC6D6F31C6N 1. 实验项目 要求利用硬件描述语言Verilog(或VHDL)、图形描述方式、IP核,结合数字系统设计方法,在Quartus开发环境下ÿ…...

如何用微信小程序写春联
生活没有模板,只需心灯一盏。 如果笑能让你释然,那就开怀一笑;如果哭能让你减压,那就让泪水流下来。如果沉默是金,那就不用解释;如果放下能更好地前行,就别再扛着。 一、引入 Vant UI 1、通过 npm 安装 npm i @vant/weapp -S --production 2、修改 app.json …...

LabVIEW无人机航线控制系统
介绍了一种无人机航线控制系统,该系统利用LabVIEW软件与MPU6050九轴传感器相结合,实现无人机飞行高度、速度、俯仰角和滚动角的实时监控。系统通过虚拟仪器技术,有效实现了数据的采集、处理及回放,极大提高了无人机航线的控制精度…...

C++哈希表深度解析:从原理到实现,全面掌握高效键值对存储
目录 一、核心组件与原理 1. 哈希函数(Hash Function) 2. 冲突解决(Collision Resolution) 3. 负载因子(Load Factor)与扩容 二、C实现:std::unordered_map 1. 模板参数 2. 关键操作与复…...

Vue.js组件开发-实现字母向上浮动
使用Vue实现字母向上浮动的效果 实现步骤 创建Vue项目:使用Vue CLI来创建一个新的Vue项目。定义组件结构:在组件的模板中,定义包含字母的元素。添加样式:使用CSS动画来实现字母向上浮动的效果。绑定动画类:在Vue组件…...

自研有限元软件与ANSYS精度对比-Bar2D2Node二维杆单元模型-四连杆实例
目录 1、四连杆工程实例以及手算求解 2、四连杆的自研有限元软件求解 2.1、选择单元类型 2.2、导入四连杆工程 2.3、节点坐标定义 2.4、单元连接关系、材料定义 2.5、约束定义 2.6、外载定义 2.7、矩阵求解 2.8、变形云图展示 2.9、节点位移 2.10、单元应力 2.11、…...

04树 + 堆 + 优先队列 + 图(D1_树(D11_伸展树))
目录 一、基本介绍 二、伸展操作 1. 左右情况的伸展 2. 左左情况的伸展 3. 右左情况的伸展 4. 右右情况的伸展 三、其它操作 1. 插入 2. 删除 四、代码实现 一、基本介绍 伸展树是一种二叉搜索树,伸展树也是一种平衡树,不过伸展树并不像AVL树那…...

c语言练习题【数据类型、递归、双向链表快速排序】
练习1:数据类型 请写出以下几个数据的数据类型 整数 a a 的地址 存放a的数组 b 存放a的地址的数组 b的地址 c的地址 指向 printf 函数的指针 d 存放 d的数组 整数 a 的类型 数据类型是 int a 的地址 数据类型是 int*(指向 int 类型的指针) …...

SliverAppBar的功能和用法
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容,本章回中将介绍SliverAppBar组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似,它们的…...

五、定时器实现呼吸灯
5.1 定时器与计数器简介 定时器是一种通过对内部时钟脉冲计数来测量时间间隔的模块。它的核心是一个递增或递减的寄存器(计数器值)。如果系统时钟为 1 MHz,定时器每 1 μs 计数一次。 计数器是一种对外部事件(如脉冲信号ÿ…...

Elasticsearch的索引生命周期管理
目录 说明零、参考一、ILM的基本概念二、ILM的实践步骤Elasticsearch ILM策略中的“最小年龄”是如何计算的?如何监控和调整Elasticsearch ILM策略的性能? 1. **监控性能**使用/_cat/thread_pool API基本请求格式请求特定线程池的信息响应内容 2. **调整…...

【大模型理论篇】最近大火的DeepSeek-R1初探系列1
1. 背景介绍 这一整个春节,被DeepSeek-R1刷屏。各种铺天盖地的新闻以及老板发的相关信息,着实感受到DeepSeek-R1在国外出圈的震撼。 DeepSeek推出了新的推理模型:DeepSeek-R1-Zero 和 DeepSeek-R1。DeepSeek-R1-Zero 是一个在没有经过监督微调…...

【数据结构】(4) 线性表 List
一、什么是线性表 线性表就是 n 个相同类型元素的有限序列,每一个元素只有一个前驱和后继(除了第一个和最后一个元素)。 数据结构中,常见的线性表有:顺序表、链表、栈、队列。 二、什么是 List List 是 Java 中的线性…...

【C++ STL】vector容器详解:从入门到精通
【C STL】vector容器详解:从入门到精通 摘要:本文深入讲解C STL中vector容器的使用方法,涵盖常用函数、代码示例及注意事项,助你快速掌握动态数组的核心操作! 一、vector概述 vector是C标准模板库(STL&am…...

OpenAI推出Deep Research带给我们怎样的启示
OpenAI 又发新产品了,这次是面向深度研究领域的智能体产品 ——「Deep Research」,貌似被逼无奈的节奏… 在技术方面,Deep Research搭载了优化后o3模型并通过端到端强化学习在多个领域的复杂浏览和推理任务上进行了训练。因没有更多的技术暴露…...

洛谷[USACO08DEC] Patting Heads S
题目传送门 题目难度:普及/提高一 题面翻译 今天是贝茜的生日,为了庆祝自己的生日,贝茜邀你来玩一个游戏。 贝茜让 N N N ( 1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1≤N≤105) 头奶牛坐成一个圈。除了 1 1 1 号与 N N N 号奶牛外࿰…...

CSS 溢出内容处理:从基础到实战
CSS 溢出内容处理:从基础到实战 1. 什么是溢出?示例代码:默认溢出行为 2. 使用 overflow 属性控制溢出2.1 使用 overflow: hidden 裁剪内容示例代码:裁剪溢出内容 2.2 使用 overflow: scroll 显示滚动条示例代码:显示滚…...

Spring Boot项目如何使用MyBatis实现分页查询
写在前面:大家好!我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭&#x…...

飞行汽车中的无刷外转子电机、人形机器人中的无框力矩电机技术解析与应用
重点:无刷外转子电机与无框力矩电机:技术解析与应用对比 在现代工业自动化和精密机械领域,无刷电机因其高效、低噪音和高可靠性而备受青睐。其中,无刷外转子电机和无框力矩电机更是以其独特的结构和性能特点,成为众多应用场景中的…...

FreeRTOS学习 --- 队列集
队列集简介 一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集 ! 作用:用于对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让…...

【R语言】R语言安装包的相关操作
一、管理R语言安装包 1、安装R包 install.packages() 2、查看已安装的R包 installed.packages() 3、更新R包 update.packages() 4、卸载R包 remove.packages() 二、加载R语言安装包 打开R语言时,基础包(base包)会自动被加载到内存中…...

15.[前端开发]Day15-HTML+CSS阶段练习(网易云音乐四)
完整代码 01_网易云-header <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…...