通用环形缓冲区 LwRB 使用指南

2023-05-16

什么是 LwRB?

LwRB 是一个开源、通用环形缓冲区库,为嵌入式系统进行了优化。源码点击这里(Github)。

LwRB 特性

  • 使用 ANSI C99 编写
  • FIFO (先进先出)
  • 无动态内存分配,数据是静态数组
  • 只有单个任务写单个任务读时,线程是安全的
  • 只有单个中断写单个中断读时,中断是安全的
  • 支持内存间的 DMA 操作,实现缓冲区和应用程序内存之间零拷贝
  • 对于读数据,提供 peek(窥读,读数据但不改变读指针) 、skip (跳读,向前移动读指针,将指定长度的数据标记为已读)函数,对于写数据,提供 advance (跳写,向前移动写指针,比如 DMA 硬件向环形缓冲区写入了 10 字节,应用程序需要调用此函数更新写指针)函数。
  • 支持事件通知

临界条件

定义 LwRB读指针r ,在读写操作时使用 r ,但仅在操作时修改 r
定义 LwRB写指针w ,在读写操作时使用 w ,但仅在操作时修改 w
定义 LwRB 的 环形缓冲区大小为 s所有操作都会使用,决不会修改 s

  • 环形缓冲区可以容纳的最大字节数总是为 s - 1 。这要求在初始化时,环形缓冲区的大小要比实际存储数据多一个字节
  • wr 指针总是指向下一个可写、可读位置;
  • w == r 时,环形缓冲区为
  • w == r - 1 时,环形缓冲区为

常规 API 函数

初始化函数

uint8_t lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size)

用法举例:

#define QUEUE_MAX_SIZE         (1024)
lwrb_t format_rb;
uint8_t format_data[sizeof(int) * QUEUE_MAX_SIZE + 1];

void init_format(void)
{
    lwrb_init(&format_rb, format_data, sizeof(format_data));
}

这里需注意环形缓冲区数组 format_data 的定义格式:

uint8_t format_data[sizeof(int) * QUEUE_MAX_SIZE + 1];

规定写入环形缓冲区或者从环形缓冲区读出的最小数据单位是 数据项数据项可能为 1 个字节,也可能为多个字节。
这里的例子数据项int 类型数据。
首先用宏 QUEUE_MAX_SIZE 定义需要保存的最大数据项数目,这里定义 1024数据项
环形缓冲区定义为 uint8_t 类型的数组。
环形缓冲区数组大小可以用以下公式确定:
环形缓冲区数组大小 = 数据项大小 ∗ 数据项个数 + 1 环形缓冲区数组大小 = 数据项大小 * 数据项个数 + 1 环形缓冲区数组大小=数据项大小数据项个数+1
这里 + 1 是因为 LwRB 的实现特性决定的,详见 临界条件 一节。

从环形缓冲区读数据

size_t lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr)

该函数最多读取 btr 字节的数据(如果有的话),数据从环形缓冲区拷贝到 data 指向的数组。函数返回实际读取的字节数。
用法举例:

lwrb_read(&format_rb, data_buf, sizeof(int));

向环形缓冲区写数据

size_t lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw)

该函数最多写入 btw 字节的数据(如果可以的话),数据从 data 指向的数组拷贝到环形缓冲区。函数返回实际写入的字节数。
用法举例:

lwrb_write(&format_rb, data_buf, sizeof(int)); 

从环形缓冲区窥读数据

size_t lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp)

函数 lwrb_peek 也可以从环形缓冲区读取最多 btp 字节的数据,数据被拷贝到 data 指向的数组。但是它和 lwrb_read 函数有两点不同:

  • 第一,lwrb_read 函数读取数据后会移动读指针,也就是读取数据后,环形缓冲区将这些数据移除掉;而 lwrb_peek 函数不移动读指针,这就意味着读取数据后,环形缓冲区不会将这些数据移除掉,就好像只是到环形缓冲区中看看这些数据都是什么样子,并不把数据拿出来,所以称为窥读
  • 第二,相比 lwrb_read 函数, lwrb_peek 函数多了一个 skip_count 参数。这个参数允许用户先跳过 skip_count 指定的字节数,再开始读取。

lwrb_peek 对一些场景十分有用。举一个我用到的例子:
设备与上位机通讯故障后,会将本地采集的不定长数据缓存起来,缓存的格式是:
在这里插入图片描述
然后将这些数据存储到环形缓冲区。等到设备与上位机恢复通讯,再将这些数据去除长度字段后上传给上位机。为了数据的可靠传输,设备必须等到上位机确认数据已经收到,才能将这些数据删除掉。
所以在程序设计中,先窥读长度字段,确认长度字段合法后,再窥读剩余数据。
因为使用窥读,所以数据仍保存在环形缓冲区中,直到上位机确认数据已经收到后,再将这些数据从环形缓冲区中删除(会用到尚未介绍的 API 函数)。代码如下:

#define DATA_LEN_NUM        	2			//长度字段占用的字节数
#define DATA_SEND_BUF_NUM		100			//数据字段最大字节数

read_count = lwrb_peek(&resume_rb_s, 0, resume_read_buf, DATA_LEN_NUM);	//窥读长度字段
/*环形缓冲区空处理*/
if(read_count != DATA_LEN_NUM)
{
	//处理
	return 0;
}
len = to_uint16_low_first(resume_read_buf);	//长度字段
/*长度字段合法性检查*/
if(len == 0 || len > DATA_SEND_BUF_NUM)
{        
	ASSERT(0);
	//错误处理
	return 0;
}
lwrb_peek(&resume_nvrb_s, DATA_LEN_NUM, resume_read_buf, len);		//跳过长度字段窥读数据部分
//其它处理

从环形缓冲区跳读数据

size_t lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len)

该函数最多读取 len 字节的数据(如果有的话),数据并不会被保存到用户层,而是直接丢弃掉(环形缓冲区会删除这些数据),就像跳过了这些数据,所以称为跳读
跳读一般有两个用处:

  • 第一,和窥读(lwrb_peek)函数配合使用,就如窥读举例使用的场景:先窥读出数据,传送给上位机;上位机确认接收后,用跳读将这些数据丢弃掉。
  • 第二,使用硬件(比如 DMA )直接读取环形缓冲区数组后,需要用跳读将这些数据丢弃掉。这会在零拷贝一节中讲解。

零拷贝

这是 LwRB 的关键特性之一。可以结合 DMA 控制器实现环形缓冲区和用户内存之间的零拷贝

从环形缓冲区零拷贝读取

需要3个函数配合:

/*获取环形缓冲区的线性读地址*/
void* lwrb_get_linear_block_read_address(LWRB_VOLATILE lwrb_t* buff)
/*获取读操作用到的线性数据块长度*/
size_t lwrb_get_linear_block_read_length(LWRB_VOLATILE lwrb_t* buff)
/*跳读*/
size_t lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len)

DMA 读操作需要源地址长度,函数 lwrb_get_linear_block_read_address 用于获取 DMA 需要的源地址,函数 lwrb_get_linear_block_read_length 用于获取 DMA 需要的长度 。DMA 读取成功后,需要调用函数 lwrb_skip 修改 r 指针,将 DMA 已经读取的数据从环形缓冲区中删除掉。
DMA 只能操作线性地址,而环形缓冲区会有地址回环,因此,可能需要读取 2 次才能将环形缓冲区数据读取完。下面对这句话举例分析。
假设环形缓冲区大小 s = 8uint8_t buff_data[8]),目前处于满状态( w == r - 1 ),保存 7 个数据,读指针 r == 5 ,写指针 w == 4 。如下图所示:

在这里插入图片描述
那么函数 lwrb_get_linear_block_read_address 返回的线性地址为读指针 r 所在的物理内存地址,这里为 &buff_data[5] ,函数 lwrb_get_linear_block_read_length 返回的线性数据块 长度为 3 字节(buff_data[5]~buff_data[7])。 要特别注意,虽然现在环形缓冲区有 7 字节可读,但是第一个线性连续的数据块只有 3 个字节,另外 4 字节(buff_data[0]~buff_data[3])虽然也是线性连续的,但两个线性连续数据块之间发生了地址回环(buff_data[7] -> buff_data[0])。
因此,第 1 次使用 DMA ,可以一次性读取 3 个字节数据,DMA读取成功后,调用函数 lwrb_skip 修改读( r )指针,修改后,读指针 r == 0 ,环形缓冲区变为:
在这里插入图片描述
这里需要再次重复一次上面的操作,函数 lwrb_get_linear_block_read_address 返回的线性地址为读指针 r 所在的物理内存地址,这里为 &buff_data[0] ,函数 lwrb_get_linear_block_read_length 返回的线性数据块 长度为 4 字节(buff_data[0]~buff_data[3])。
第 2 次使用 DMA ,可以一次性读取 4 个字节数据,DMA读取成功后,调用函数 lwrb_skip 修改 r 指针,修改后,读指针 r == 4 ,环形缓冲区为空:
在这里插入图片描述
从环形缓冲区零拷贝读取的一般使用方法:

/* Initialization part skipped */

/* Get length of linear memory at read pointer */
/* When function returns 0, there is no memory
   available in the buffer for read anymore */
   while ((len = lwrb_get_linear_block_read_length(&buff)) > 0) {
    /* Get pointer to first element in linear block at read address */
    data = lwrb_get_linear_block_read_address(&buff);

    /* If max length needs to be considered */
    /* simply decrease it and use smaller len on skip function */
    if (len > max_len) {
        len = max_len;
    }

    /* Send data via DMA and wait to finish (for sake of example) */
    send_data(data, len);

    /* Now skip sent bytes from buffer = move read pointer */
    lwrb_skip(&buff, len);
}

向环形缓冲区零拷贝写入

需要3个函数配合:

/*获取环形缓冲区的线性写地址*/
void* lwrb_get_linear_block_write_address(LWRB_VOLATILE lwrb_t* buff)
/*获取写操作用到的线性数据块长度*/
size_t lwrb_get_linear_block_write_length(LWRB_VOLATILE lwrb_t* buff)
/*跳写*/
size_t lwrb_advance(LWRB_VOLATILE lwrb_t* buff, size_t len)

DMA 写操作需要目的地址长度,函数 lwrb_get_linear_block_write_address 用于获取 DMA 需要的目的地址,函数 lwrb_get_linear_block_write_length 用于获取 DMA 需要的长度 。DMA 写入成功后,需要调用函数 lwrb_advance 修改写( w )指针,将 DMA 已经写入的数据更新到环形缓冲区控制块中。
DMA 只能操作线性地址,而环形缓冲区会有地址回环,因此,可能需要写入 2 次才能将环形缓冲区写满(与从环形缓冲区零拷贝读取类似)。

线程安全

LwRB 的一个重要特性是支持边写边读或者边读边写操作,这个操作有前提条件,即只有存在单个写入入口点和单个读取出口点时才可以。换句话说,在此条件下,LwRB线程安全的、中断安全的。
只有单个任务写单个任务读时满足单个写入入口点和单个读取出口点条件;
只有单个中断写单个中断读时满足单个写入入口点和单个读取出口点条件;
多个任务写或者多个任务读、多个中断写或者多个中断读都不满足单个写入入口点和单个读取出口点条件,比如:

  1. 多个线程写
    在这里插入图片描述
  2. 多个线程读
    在这里插入图片描述
  3. 多个线程写多个线程读
    在这里插入图片描述

以上三种情况均违反了单个写入入口点和单个读取出口点条件。这时代码不属于线程安全代码,红色虚线框住的部分,需要应用程序进行额外的资源互斥操作。

lwrb_t rb;

/* 2 个互斥量, 一个用于写操作一个用于读操作 */
mutex_t m_w, m_r;

/* 以下 4 个线程, 2 个写, 2 个读 */
void thread_write_1(void* arg) {
    /* 使用写互斥 */
    while (1) {
        mutex_get(&m_w);
        lwrb_write(&rb, ...);
        mutex_give(&m_w);
    }
}

void thread_write_2(void* arg) {
    /* 使用写互斥 */
    while (1) {
        mutex_get(&m_w);
        lwrb_write(&rb, ...);
        mutex_give(&m_w);
    }
}

void thread_read_1(void* arg) {
    /* 使用读互斥 */
    while (1) {
        mutex_get(&m_r);
        lwrb_read(&rb, ...);
        mutex_give(&m_r);
    }
}

void thread_read_2(void* arg) {
    /* 使用读互斥 */
    while (1) {
        mutex_get(&m_r);
        lwrb_read(&rb, ...);
        mutex_give(&m_r);
    }
}

事件

事件是一个回调函数,函数原型为:

typedef void (*lwrb_evt_fn)(LWRB_VOLATILE struct lwrb* buff, lwrb_evt_type_t evt, size_t bp);

事件分为:读事件、写事件和复位事件。由枚举类型 lwrb_evt_type_t 定义:

typedef enum {
    LWRB_EVT_READ,                              /*!< Read event */
    LWRB_EVT_WRITE,                             /*!< Write event */
    LWRB_EVT_RESET,                             /*!< Reset event */
} lwrb_evt_type_t;

一个典型的事件函数实现为:

/**
 * \brief           Buffer event function
 */
void my_buff_evt_fn(lwrb_t* buff, lwrb_evt_type_t type, size_t len) {
    switch (type) {
        case LWRB_EVT_RESET:
            printf("[EVT] Buffer reset event!\r\n");
            break;
        case LWRB_EVT_READ:
            printf("[EVT] Buffer read event: %d byte(s)!\r\n", (int)len);
            break;
        case LWRB_EVT_WRITE:
            printf("[EVT] Buffer write event: %d byte(s)!\r\n", (int)len);
            break;
        default: break;
    }
}

事件函数通过注册的方式提交给环形缓冲区,注册函数为 lwrb_set_evt_fn ,一个注册事件函数的例子:

lwrb_set_evt_fn(&buff, my_buff_evt_fn);

事件函数注册成功后,LwRB 在每次修改读写指针时,都会调用这个事件函数,具体为:

  • 读事件:lwrb_read 函数、 lwrb_peek 函数
  • 写事件:lwrb_write 函数、lwrb_advance 函数
  • 复位事件:lwrb_reset 函数(将读写指针设置为 0 )






读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
千金难买知识,但可以买好多奶粉


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通用环形缓冲区 LwRB 使用指南 的相关文章

  • LINGO使用指南(转载)

    LINGO是用来求解线性和非线性优化问题的简易工具 LINGO内置了一种建立最优化模型的语言 xff0c 可以简便地表达大规模问题 xff0c 利用LINGO高效的求解器可快速求解并分析结果 1 LINGO快速入门 当你在windows下开
  • IntelliJ使用指南—— 导入Eclipse的Web项目

    通常一个团队中可能有人用eclipse xff0c 有人用intelliJ xff0c 那么经常会出现需要导入别人用eclipse建好的web项目 而IntelliJ提供了多种项目类型的导入方式 xff0c 其中就有eclipse 在初始窗
  • 【Linux】Ubuntu 使用指南

    content 1 换清华源2 更新三步走3 1 换清华源 备份 Ubuntu 的软件源配置文件 etc apt sources list span class token function sudo span span class tok
  • mysql使用指南

    目录 一 SQL简述1 SQL的概述2 SQL的优点3 SQL的分类 二 数据库的三大范式三 数据库的数据类型1 整数类型2 浮点数类型和定点数类型3 字符串类型4 字符串类型5 日期与时间类型 四 数据库 数据表的基本操作1 数据库的基本
  • 七牛云存储 CDN 使用指南

    七牛cdn 使用指南 更新于2016 3 13 分为两种情况 xff1a 1 使用七牛存储 2 直接使用七牛cdn 一 使用七牛存储 xff08 七牛的存储默认使用cdn加速 xff09 静态资源存储到七牛后 xff0c 可以使用七牛提供的
  • netperf使用指南

    netperf使用笔记 netperf 是一种非常常见的测量网络带宽的工具 xff0c 其安装非常简单 xff1a 1 下载netperf包 2 进入netperf目录 xff0c configure 3 make 4 make insta
  • python ftplib使用指南

    关于服务器 这个是个难弄的东西 一台就需要几十大洋 而服务器的类型有很多种 http https tcp ftp等等 而今天说的就是使用python连接和使用ftp服务器 1 开通ftp服务器 开通局域网ftp服务器的过程就不说了 不知道的
  • 使用Formik轻松开发更高质量的React表单(二)使用指南

    一个基本的例子 设想你要开发一个可以编辑用户数据的表单 不过 xff0c 你的用户API端使用了具有类似下面的嵌套对象表达 xff1a id string email string social facebook string twitte
  • Github使用指南(持续更新中)

    一 简介 Github的网页端是www github com GitHub是一个面向开源及私有软件项目的托管平台 xff0c 因为只支持Git作为唯一的版本库格式进行托管 xff0c 故名GitHub GitHub于2008年4月10日正式
  • mask rcnn使用指南

    做姿态估计的小伙伴们肯定经常用检测器 xff0c 为了方便大家 xff0c 这里给出一个很方便的教程 让大家快速上手 xff0c 不用再纠结配置环境 xff01 欢迎加入我们的姿态估计群 xff1a 970029323 xff08 xff1
  • fio使用指南

    这个文档是对fio 2 0 9 HOWTO文档的翻译 xff0c fio的参数太多了 xff0c 翻译这个文档时并没有测试每一个参数的功能和使用方法 xff0c 只有少量参数做了试验 xff0c 大部分的参数采用的是根据字面翻译或是个人理解
  • Idea使用指南

    安装 只选一个X64 xff0c 其余一路next 使用 xff1a
  • 【MATLAB UAV Toolbox】使用指南(一)

    开始使用UAV Toolbox 设计 xff0c 仿真和部署无人机应用程序 UAV Toolbox给设计 仿真 测试和部署无人机应用程序提供了工具和参考应用 你能够设计自动飞行算法 无人机任务和飞行控制器 飞行日志分析仪应用程序可以让您交互
  • 【MATLAB UAV Toolbox】使用指南(二)

    可视化和回放MAVLink飞行日志 这个例子将展示如何将包含MAVLink包的遥测日志 xff08 TLOG xff09 加载进MATLAB 提取的详细信息用来绘图 然后再次仿真飞行 xff0c 这些消息通过MAVLink通信接口重新发布
  • STC-ISP使用指南

    该软件无需安装 xff0c 下载后打开直接用 本软件是专门给STC系列单片机下载烧录程序的 xff0c 并不能适用于ARM系列的单片机 界面介绍 xff1a 打开后的界面如下 xff1a 左边的部分一般是用来下载程序的 xff0c 右面一般
  • xsemaphoretake返回_FreeRTOS 使用指南

    http xilinx eetrend com article 7845 2014 繁星电子开发团队制作 作为一个轻量级的操作系统 xff0c FreeRTOS 提供的功能包括 xff1a 任务管理 时间管理 信号量 消息队列 内存管理 记
  • Podman 使用指南

    原文链接 xff1a Podman 使用指南 Podman 原来是 CRI O 项目的一部分 xff0c 后来被分离成一个单独的项目叫 libpod Podman 的使用体验和 Docker 类似 xff0c 不同的是 Podman 没有
  • JavaParser使用指南

    抱歉这个博客是很久以前写的 xff0c 现在我已经不用javaparser了 xff0c 这个包也一直在更新所以这篇博客参考意义不大 大家可以根据官网的例子去试试 JavaParser使用指南 前言入门 StartJavaParser Cl
  • iperf3 使用指南

    iperf3的参数 xff1a s表示做服务器 c表示做客户端 t 表示表示发送时间 i 表示间隔多久报告一次 u 表示UDP xff0c 不加表示tcp R 表示服务器发客户端收 xff0c 不加默认客户端发 xff0c 服务器收 f 表
  • 【MATLAB UAV Toolbox】使用指南(三)

    可视化自定义飞行日志 通过配置flightLogSignalMapping可从自定义的飞行日志中可视化数据 加载自定义的飞行日志 在本例中 xff0c 假设飞行数据已经被解析到MATLAB 中 xff0c 并存储为M文件 本示例重点介绍如何

随机推荐

  • realsense内参、外参获取

    启动相机节点 roslaunch realsense2 camera rs camera launch 查看相机参数信息 rostopic echo camera depth camera info 内参矩阵格式 fx 焦距xfy 焦距yx
  • ROS控制crazyflie无人机(一)

    ROS控制crazyflie无人机 参考资料测试环境一 设置用户权限二 安装Crazyflie程序库和客户端三 安装crazyflie ros程序包四 遥控器控制crazyflie无人机 本篇博客记录了测试使用ros控制crazyflie无
  • echarts 黑色样式

    lt THIS EXAMPLE WAS DOWNLOADED FROM https echarts apache org examples zh editor html c 61 dataset simple1 gt lt DOCTYPE
  • 磁盘分区

    磁盘 xff1a 可以被分区成多个分区槽 xff08 partition xff09 以Windows来看 xff0c 磁盘可以被分区为 C xff1a xff0c D xff1a xff0c E xff1a 槽 C D E就是分区槽 而L
  • 【STM32】IAP下载程序分析

    IAP下载程序分析 IAP基本原理STM32启动流程程序跳转代码实现总结 IAP基本原理 要实现STM32的IAP xff08 在应用编程 xff09 xff0c 需要分别建立bootloader和app工程 这里的bootloader程序
  • vue el-date-picker 设置可选近30天且时间范围为7天

    对 el date picker 限制 xff1a 默认选择时间范围为最近一天 xff1b 设置快捷选项 xff1a 最近1天 最近3天 最近7天等 xff1b 限制只能选择今天以及今天以前30天 xff1b 选择一个时间之后 xff0c
  • MySQL索引分析

    1 索引是什么 xff1f 索引是一种能提高数据库查询效率的数据结构 它可以比作一本字典的目录 xff0c 可以帮你快速找到对应的记录 索引一般存储在磁盘的文件中 xff0c 它是占用物理空间的 正所谓水能载舟 xff0c 也能覆舟 适当的
  • 任务是如何调度(切换)的?

    学习任务的切换有助于自己理解操作系统的运行过程 并且任务的调度 xff08 切换 xff09 也是操作系统中的重要的部分 任务的切换中关键是任务控制块的控制 xff0c 将现在以及将来要运行的任务向堆栈中的存储与恢复 因为 C OS II总
  • 机器学习算法五:随机森林(Random Forest)

    集成学习 xff1a 通过构建并结合多个学习器来完成学习任务 xff1b 集成学习中主要包括boosting算法 和bagging算法 xff1b boosting算法 xff1a xff08 线性集成 xff09 关注于降低偏差 xff1
  • 怎样利用VNC远程连接LINUX桌面

    关于显示等问题都有 xff0c 比较详细 http blog csdn net zhouyunjie archive 2008 11 27 3396824 aspx 先要修改 vnc xstartup文件 bin sh Uncomment
  • VNC多用户

    1 为vnc建立用户 newuser 2 拷贝 root vnc 里的xstartup文件到用户目录 home newuser 3 修改xstartup文件 xff0c 在最后增加 xff1a gnome session amp 4 以ne
  • 【环境配置】初试使用mitmproxy搭建网络代理

    初试使用mitmproxy搭建网络代理 1 参考文章 老版本的很多功能已经不能用了 xff0c 用新的版本研究了一下 参考文章 https mitmproxy org http www freebuf com sectool 76361 h
  • 关于Keil 的快速注释功能,并为其添加快捷键

    原地址 xff1a http blog sina com cn s blog 6859cadf0101i3p4 html Keil版本uVision 4 03 1 在Keil gt Edit gt Advanced中有两项 Comment
  • 【OpenCV】ArUco Marker

    1 创建 span class token keyword import span cv2 span class token keyword as span cv span class token keyword import span n
  • 非常实用,华为、新华三、锐捷交换机的配置命令分享

    干弱电这一行难免会接触到交换机 xff0c 华为 新华三锐捷交换机又是最常见的交换机 xff0c 关于他们的命令配置很容易弄混 xff0c 而且在实际项目配置中也很容易出错 xff0c 因此 xff0c 本期我们将来介绍这三家交换机的基础配
  • 打飞机小游戏

    设计目标 xff1a 高质量的代码要有这些优点 复用性好 扩展性好 维护性好 可移植性好 健壮性好 效率好 可读性好 所以设计代码的时候不要只想到功能的实现 xff0c 还要考虑功能的扩展及代码复用等 设计规则 xff1a 需求分析抽取共性
  • windows 安装 Navicat Premiun

    Navicat Premiun 中文网站 Navicat Premiun 官网下载 简介 Navicat Premium 是一套数据库管理工具 xff0c 结合其它Navicat 成员 xff0c 支持单一程序同时连接到 MySQL Mar
  • 论文排版中MathType的使用(论文投稿必备)

    使用mathtype往论文中插入公式时 xff0c 若需要编号 xff0c 则点击 右编号 xff08 若只需插入文本行内 xff0c 则点 内联 xff09 由于论文是分栏格式 xff0c 单行公式太长导致编号串行 xff0c 因此需要将
  • python语言中变量的共享引用及原处修改

    代码段一 xff1a 代码段1 gt gt gt a 61 6 gt gt gt b 61 a gt gt gt a b 输出 xff1a 6 6 上述代码段一中 xff0c 第一行我们创建了对象6 xff0c 并将变量a 与之相关联 xf
  • 通用环形缓冲区 LwRB 使用指南

    什么是 LwRB xff1f LwRB 是一个开源 通用环形缓冲区库 xff0c 为嵌入式系统进行了优化 源码点击这里 Github LwRB 特性 使用 ANSI C99 编写FIFO xff08 先进先出 xff09 无动态内存分配 x