news 2026/5/16 1:02:15

面向对象_昂瑞微_作者观点仅供参考

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
面向对象_昂瑞微_作者观点仅供参考

C 语言面向对象编程实例解析

选自 OnMicro OM6626 BLE SDK 中的 DFU(Device Firmware Upgrade)模块。
适合有一定 C 基础、想理解"如何在 C 中实现面向对象"的初级工程师。


一、先看最终效果:调用方完全不关心底层实现

在 onmicro_dfu.c 中,固件升级的核心逻辑是这样写存储操作的:

// 获取某个存储后端的接口指针constdfu_nvds_itf_t*flash_itf=&dfu_nvds_itf[DFU_NVDS_ITF_TYPE_FLASH];// 像调用对象方法一样调用flash_itf->enable();flash_itf->get(addr,&len,buf);flash_itf->put(addr,len,buf);flash_itf->del(addr,len);flash_itf->disable();

不同之处只是数组索引不同——DFU_NVDS_ITF_TYPE_MBRDFU_NVDS_ITF_TYPE_DUMMYDFU_NVDS_ITF_TYPE_FLASH_EXT。同一个.get()调用,底层可以是读内部 Flash、读 MBR 分区表、甚至返回全0xFF的假数据。这就是多态。


二、第一步:定义"接口"(函数指针表)

文件:onmicro_dfu_nvds.h

// 这就是 C 语言中的"接口"——一个全是函数指针的结构体typedefstruct{uint8_t(*enable)(void);// 初始化uint8_t(*get)(uint32_tid,uint32_t*lengthPtr,void*buf);// 读取uint8_t(*put)(uint32_tid,uint32_tlength,void*buf);// 写入uint8_t(*del)(uint32_tid,uint32_tlength);// 擦除/删除uint8_t(*disable)(void);// 反初始化}dfu_nvds_itf_t;

对应关系:

C(函数指针表)C++(类)
dfu_nvds_itf_t抽象基类 / 接口
结构体内的 5 个函数指针5 个纯虚函数
实现文件里创建的具体实例派生类 + 虚表

为什么每个函数参数里没有"this"指针?

因为这个项目里每个"实现"的后端是全局唯一的(只有一个内部 Flash、一个 MBR 分区表),不需要区分实例。更完善的写法会把第一个参数设计为void *self


三、第二步:实现"类"(提供具体函数,填表注册)

文件:onmicro_dfu_nvds.c

3.1 Flash 后端的实现

// 每个函数对标接口中的一个函数指针staticuint8_tonmicro_dfu_nvds_enable_flash(void){drv_sfs_enable();// 使能内部 Flash 控制器returnONMICRO_DFU_NVDS_ST_SUCCESS;}staticuint8_tonmicro_dfu_nvds_get_flash(uint32_taddr,uint32_t*lengthPtr,void*buf){drv_sfs_read(addr,buf,*lengthPtr);// 调用 Flash 驱动读returnONMICRO_DFU_NVDS_ST_SUCCESS;}staticuint8_tonmicro_dfu_nvds_put_flash(uint32_taddr,uint32_tlength,void*buf){drv_sfs_write(addr,buf,length);// 调用 Flash 驱动写returnONMICRO_DFU_NVDS_ST_SUCCESS;}staticuint8_tonmicro_dfu_nvds_erase_flash(uint32_taddr,uint32_tlength){drv_sfs_erase(addr,length);// 调用 Flash 驱动擦除returnONMICRO_DFU_NVDS_ST_SUCCESS;}staticuint8_tonmicro_dfu_nvds_disable_flash(void){returnONMICRO_DFU_NVDS_ST_SUCCESS;}

3.2 MBR(分区表)后端的实现

staticuint8_tonmicro_dfu_nvds_get_mbr(uint32_tid,uint32_t*lengthPtr,void*buf){dfu_image_mbr_info*info=(dfu_image_mbr_info*)buf;// 调用 MBR 库读取分区信息if(id<=sizeof(mbr_types)/sizeof(mbr_types[0])&&mbr_read_part(mbr_types[id],&info->address,&info->length,&info->crc16)==0){returnONMICRO_DFU_NVDS_ST_SUCCESS;}else{returnONMICRO_DFU_NVDS_ST_FAILED;}}// ... 其余 4 个函数同理

3.3 Dummy 后端的实现(占位 / 测试用)

staticuint8_tonmicro_dfu_nvds_get_dummy(uint32_tid,uint32_t*lengthPtr,void*buf){memset(buf,0xFF,*lengthPtr);// 全部填 FF,模拟空 FlashreturnONMICRO_DFU_NVDS_ST_SUCCESS;}// put/del/enable/disable 全部直接返回成功——这是个"空实现"

3.4 组装:把函数指针填入虚表数组

constdfu_nvds_itf_tdfu_nvds_itf[]={[DFU_NVDS_ITF_TYPE_MBR]={// 索引 0:分区表后端onmicro_dfu_nvds_enable_mbr,onmicro_dfu_nvds_get_mbr,onmicro_dfu_nvds_put_mbr,onmicro_dfu_nvds_del_mbr,onmicro_dfu_nvds_disable_mbr},[DFU_NVDS_ITF_TYPE_FLASH]={// 索引 1:内部 Flash 后端onmicro_dfu_nvds_enable_flash,onmicro_dfu_nvds_get_flash,onmicro_dfu_nvds_put_flash,onmicro_dfu_nvds_erase_flash,onmicro_dfu_nvds_disable_flash},// ... CFG、EXT_FLASH 等后端[DFU_NVDS_ITF_TYPE_DUMMY]={// 索引 4:空后端onmicro_dfu_nvds_enable_dummy,onmicro_dfu_nvds_get_dummy,onmicro_dfu_nvds_put_dummy,onmicro_dfu_nvds_del_dummy,onmicro_dfu_nvds_disable_dummy},};

四、第三步:配置层注入依赖

文件:onmicro_dfu_config.h

每个固件镜像的存储操作接口和元信息存储接口是可独立配置的:

typedefstruct{uint16_ttype;// 镜像类型(APP / PATCH / CONFIG ...)uint32_tbase_address1;// 基地址 1uint32_tbase_address2;// 基地址 2(双区备份用)uint32_tmax_length;// 最大长度constchar*describe;// 描述(仅调试用)constdfu_nvds_itf_t*image_ops_itf;// ← 镜像数据读写的接口constdfu_nvds_itf_t*info_ops_itf;// ← 镜像元信息读写的接口(可为空)uint16_tinfo_id;}dfu_image_t;constdfu_image_tdfu_image_types[]={{IMAGE_TYPE_APP,0x00044000,0x00044000,0x00040000,"Application",&dfu_nvds_itf[DFU_NVDS_ITF_TYPE_FLASH],// 镜像存在内部 FlashNULL,// 元信息不需要单独存储0x00},{IMAGE_TYPE_DUMMY,0x00000000,0x00000000,0xFFFFFFFF,"Dummy",&dfu_nvds_itf[DFU_NVDS_ITF_TYPE_DUMMY],// 镜像用空实现NULL,0x00},};

关键在于image_ops_itfinfo_ops_itfdfu_nvds_itf_t类型指针,可以指向数组中任意一个实现。新增一种存储后端,只需:

  1. 写 5 个static函数
  2. dfu_nvds_itf[]数组中加一项
  3. dfu_image_types[]中把对应的镜像指向它

DFU 核心逻辑 onmicro_dfu.c不需要改一行代码


五、执行时的调用流

以写入镜像数据为例(onmicro_dfu.c 第 281-297 行):

// 1. 从当前镜像配置中取出操作接口constdfu_nvds_itf_t*write_itf=p_env->image_ops_itf;// 2. 统一调用——不需要知道底层是 Flash / MBR / Dummywrite_itf->enable();write_itf->put(write_addr,p_env->cache_recv_len,m_cache);write_itf->disable();

对于获取新镜像地址的逻辑(第 170-210 行),同样使用接口切换:

if(img->type<IMAGE_TYPE_MBR_MAX){// 系统镜像 → 用 MBR 接口查分区表constdfu_nvds_itf_t*nvds_itf=&dfu_nvds_itf[DFU_NVDS_ITF_TYPE_MBR];nvds_itf->enable();nvds_itf->get(img->type,&len,&info);nvds_itf->disable();}elseif(img->type<IMAGE_TYPE_RAW){// 自定义镜像 → 用配置中的 info_ops_itfconstdfu_nvds_itf_t*nvds_itf=cmd_img_info->info_ops_itf;// ... same pattern ...}

六、总结:这个模式的核心思想

┌─────────────────────────┐ │ dfu_nvds_itf_t │ ← "接口"(struct of function pointers) │ ┌───────────────────┐ │ │ │ enable() │ │ │ │ get() │ │ │ │ put() │ │ │ │ del() │ │ │ │ disable() │ │ │ └───────────────────┘ │ └───────┬───────┬─────────┘ │ │ ┌─────────────┘ └─────────────┐ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ Flash 实现 │ │ MBR 实现 │ │ drv_sfs_read() │ │ mbr_read_part() │ │ drv_sfs_write() │ │ mbr_write_part() │ └──────────────────┘ └──────────────────┘ │ │ └───────────────┬───────────────────┘ ▼ ┌─────────────────────┐ │ dfu_nvds_itf[] 数组 │ ← "注册表" │ [0] = MBR 实现 │ │ [1] = Flash 实现 │ │ [2] = CFG 实现 │ │ [3] = Ext Flash 实现 │ │ [4] = Dummy 实现 │ └─────────────────────┘

三个关键技巧:

技巧C 中的实现对应的 OOP 概念
1. 结构体里放函数指针typedef struct { uint8_t (*get)(...); } dfu_nvds_itf_t;虚表 / 接口
2.static函数隐藏实现所有后端实现函数都声明为static封装(private 方法)
3. 运行时选择实现通过数组索引或指针切换不同表项多态 / 依赖注入

为什么嵌入式开发要这么写?

  • 零额外开销:函数指针调用在 ARM 上是BLX Rn,和直接函数调用开销几乎一样。没有 C++ 虚函数的多级间接跳转。
  • 无堆分配:虚表是const全局数组,编译时就确定,不占 RAM。
  • 可测试:可以插入 Dummy 后端(返回全0xFF),不操作真实硬件即可测试升级流程。
  • 易扩展:加一种新存储后端(比如 SPI NOR Flash),只需要新增一个文件写 5 个static函数,在数组中注册一行。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 1:00:03

基于RAG与代码专用嵌入模型构建本地智能代码库问答系统

1. 项目概述与核心价值最近在GitHub上看到一个挺有意思的项目&#xff0c;叫“smart-codebase”。光看名字&#xff0c;你可能觉得这又是一个关于代码智能化的工具&#xff0c;但仔细研究其设计和实现思路&#xff0c;你会发现它瞄准的是一个非常具体且高频的痛点&#xff1a;如…

作者头像 李华
网站建设 2026/5/16 1:00:00

ToyKind-World:基于Python的ECS架构多智能体模拟框架构建指南

1. 项目概述与核心价值最近在GitHub上看到一个挺有意思的项目&#xff0c;叫“ToyKind-World”。光看这个名字&#xff0c;你可能会觉得有点抽象&#xff0c;是玩具世界&#xff1f;还是某种模拟器&#xff1f;点进去一看&#xff0c;发现它其实是一个用Python构建的、高度可配…

作者头像 李华
网站建设 2026/5/16 0:57:04

快速上手在控制台创建与管理多个APIKey

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 快速上手在控制台创建与管理多个APIKey 对于任何使用大模型API的开发者或团队而言&#xff0c;API Key是访问服务的核心凭证。妥善…

作者头像 李华
网站建设 2026/5/16 0:52:25

3步高效掌握LizzieYzy:围棋AI分析工具的完整实战指南

3步高效掌握LizzieYzy&#xff1a;围棋AI分析工具的完整实战指南 【免费下载链接】lizzieyzy LizzieYzy - GUI for Game of Go 项目地址: https://gitcode.com/gh_mirrors/li/lizzieyzy LizzieYzy是一款强大的围棋AI分析工具&#xff0c;它基于著名的Lizzie项目进行了深…

作者头像 李华
网站建设 2026/5/16 0:45:19

告别龟速!手把手教你用Motrix+Chrome插件免费提速下载百度网盘文件

突破限速封锁&#xff1a;高效下载百度网盘资源的终极方案 每次面对百度网盘那令人抓狂的下载速度&#xff0c;你是否感到无比沮丧&#xff1f;当急需获取重要文件时&#xff0c;看着进度条像蜗牛一样缓慢移动&#xff0c;那种无力感简直让人崩溃。但今天&#xff0c;我要分享的…

作者头像 李华