工具开发-ESP32程序管理系统 | 具体实现(一)

此文基于前篇,在阅读本文之前,建议先看一下本系列的第一篇(系统设计)。

在前篇文章中,我们介绍了如何把一个想法从零变成一个基础的有大致逻辑的框架,以及如何把框架与编程语言做一个基础的结合,由此得到一个"底座"。

现在,我们将在底座上按位填入合适的内容,从而得到一个不错的骨架。

声明:本系列文章的目的旨在讲述如何把想法变为一段好的代码,总体来说可能不太适合专业人士。

框架回顾

回顾一下我们的框架,大概是这样子的:

yaml 复制代码
        程序入口
            |
        程序注册 ------>>> 
            |             |
            |          管理器
            |    创建新的程序结构体
            |       加入程序数组
            |
    进入菜单选择启动项 <<<-------------
(前文未提到,但应为常规逻辑)            |
            |                          |
       每轮检测按键                    |
            |                          |
        选中执行 ------>>>             |
                          |            |
                        管理器         |
                    得到启动项信息     |
                 调用结构体run方法指针 |
                          |            |
                       程序执行        |
                          |            |
                     等待执行结束      |
                        (阻塞)         |
                          |            |
                           ------------ 
                            (循环执行)

本图中部分内容在上一篇文章中并未明确提到,但这些多为基础逻辑,即一个可用程序最好一定拥有的逻辑,我想只要关键部分理解,这份逻辑图并不难看懂。

我们的任务就是在这个图的基础上给关键部分写出代码,并给不算太关键的部分写出伪代码,这些任务将在接下来的两篇文章中解决。本篇,我们将完成管理器有关的逻辑。

功能实现

接下来,我们将先为主要部分管理器写出代码,并为后续布局,预留接口。

我们将逐步添加内容,完善主干,让核心逻辑在底座上被撑起来。

最终代码可以在附录查看

管理器 - 添加程序

看管理器,我们逐步思考。

上篇我们讲过,管理器要能添加和执行程序,那么我们先来完成这些基础内容。

创建一个manager.h,开始写入核心逻辑。

首先,我们提到,管理器可以以一个对象的形式出现,同时具有方法。

cpp 复制代码
// manager.h
#ifndef MANAGER_H
#define MANAGER_H

class ProgramManager{
    public:
        void add(
            const char* name,
            void (*run)(),
            const char* dsc
        );
};

#endif

此上,我们写出了一个管理器类的声明,并给予一个用于添加程序的方法add,传入名称、入口函数指针、描述,囊括它的基本信息。

当然,它需要有具体实现,所以需要创建一个新的文件manager.cpp:

cpp 复制代码
// manager.cpp
void ProgramManager::add(
    const char* name,
    void (*run)(),
    const char* dsc
){
    // 写入有关添加程序的代码
}

现在,我们完成了管理器类的基础声明与实现,当然实现方法我们还没有写出。这将是我们接下来的方向。

架构设计篇我们想到,结合C++语言,程序用一个数组保存是最好的。

那么就可以请出上篇写过的代码片段,写入manager.h

cpp 复制代码
// manager.h

// 创建一个程序结构体
struct Program{
    const char* name;
    const char* dsc;
    void (*run)();
};
// 管理器类
class ProgramManager{
    private:
        Program programs[16]; // 假设我们设置最多可以存储16个程序
    // 其余与前文一致
};

加上了我们上一篇设计好的数据类型,我们管理器添加方法的实现也有了方向,我们继续完善。同样可以用我们前文设计好的代码。

cpp 复制代码
// manager.cpp
void ProgramManager::add(
    const char* name,
    void (*run)(),
    const char* dsc
){
    programs[0] = {
        name,
        dsc,
        run
    };
}

这样看,当我们调用add方法添加程序时,程序内部会把我们传入的信息写入programs数组,索引为0的地址里。

但是,仔细看一看,当我们第一次调用时,程序信息会被正确录入索引为0的地址, 但第二次、第三次呢?

会发现,每次调用都写入同一个地址的话,后面写入的自然会把前面的覆盖掉,这当然不是我们想要的结果,我们希望无论第几个程序都应该被正常写入数组,这样我们后续才能正常使用。

所以,为了解决这个问题,我们需要程序能自己找到合适的位置放入我们要加入的程序,也就是说,这个索引需要可以变化。

实现并不难,我们可以让每次添加时,索引自动 +1 。 调整头文件,加入一个索引变量的声明。

cpp 复制代码
// manager.h
class ProgramManager{
    private:
        int _index;
        // 在嵌入式开发中,我们尽量选择占用内存尽量低的类型
        // 在arduino框架中,可以是uint8_t
        // #include<Arduino.h>
        // uint8_t index;
        // 分配8bit内存,无符号整形,范围0~255
        // 但这里我们以标准C++语言来讲解
    public:
        ProgramManager();
        // 构造函数,用于初始化index变量
};
// 其它内容不变

现在回到manager.cpp,补充逻辑:

cpp 复制代码
// manager.cpp
ProgramManager::ProgramManager()
    :_index(0){}

void ProgramManager::add(
    const char* name,
    void (*run)(),
    const char* dsc
){
    programs[_index] = {
        name,
        dsc,
        run
    };
    _index ++;
}

至此,不出意外的话每次添加程序使用的都不会再是同一个索引,因此可以保留多个程序在这个数组。

当然,后面将会有更优雅的实现方法,但对于我们现在只加不减的情况,已经足矣。

这段代码其实还有一个问题,但不需要我们立刻解决。那就是programs仅能存储16个元素,但我们的_index变量却会一直累加,直到程序尝试访问programs16,这时就会发生错误,但作为最小可用版本,我们可以暂时不考虑这个问题,但要记住这个问题,正式版本一定不能保留。

当然,最好的办法还是现在就立刻解决。

cpp 复制代码
// manager.cpp
ProgramManager::ProgramManager()
    :_index(0){}

void ProgramManager::add(
    const char* name,
    void (*run)(),
    const char* dsc
){
    if (_index >= 16){
        // 超出限制
        return;
    }
    programs[_index] = {
        name,
        dsc,
        run
    };
    _index ++;
}

好了,现在我们已经可以做到完成一个完整的程序添加逻辑。

管理器 - 运行程序

完成了添加逻辑,接下来就需要完成运行逻辑,不能只让程序躺在那里。

首先,当然要有一个start方法,负责调用运行:

cpp 复制代码
// manager.h
class ProgramManager{
    public:
        void start(const char* name);
        // 传入程序的名称,执行
};

接下来,在manager.cpp写出实现。

cpp 复制代码
// manager.cpp
void ProgramManager::start(const char* name){
    // 调用执行
}

这当然还不是实现,我们要想想怎么调用执行。

我们在程序结构体中写入了一个run函数指针,所以执行程序最简单的办法就是直接调用这个函数。

但是,我们传入的是名字,但是找到程序的结构体我们必须要直到它的索引,其实很简单,加一个遍历就可以了。

cpp 复制代码
int program_index;
for (int i=0;i<16;i++){
    if (strcmp(programs[i].name,name)==0){
        program_index = i;
    }
}

通过遍历数组的每一个元素,当名称一致时,得到它的索引。

当然,最好的办法时封装为函数,或者说是管理器的一个私有方法。

cpp 复制代码
// manager.h
class ProgramManager{
    private:
        int _get_index(const char* name);
}
cpp 复制代码
// manager.cpp
int ProgramManager::_get_index(const char* name){
    for (int i=0;i<16;i++){
        if (strcmp(programs[i].name,name)==0){
            return i;
        }
    }
}
void ProgramManager::start(const char* name){
    programs[_get_index(name)].run();
}

完美,现在我们已经可以通过传入名称完成调用了,这也可以是对外部暴露的一个接口,以后菜单就可以调用这个接口启动程序。

至此,这个基础的骨架已经出现了,我们可以以此向外继续拓展,当然,这是下一章的内容了。

结语

本文,我们完成了这个系统的核心,即管理器的设计,预留出的两个接口将是我们接下来完善外围功能的中心。

当前的代码一定还存在一系列没有避免的问题,但作为最小版本,暂时至此即可。

下一篇文章,我们将完成菜单的基础设计。到时,这个系统就会拥有运行所需的所有基础内容。

写给读者

作为我个人的一个小想法,本系列主要是为了记录这个工具的开发,同时给刚学完编程基础,却不知道如何把自己的想法写出来的人一个思路的指引,所以很多内容并没有写的很深,只给了大致的框架。我的目的不是让看到这篇文章的你也写出一个这样的工具,而是可以借着这篇文章,了解怎么把问题拆散,再逐步深入,让各个模块契合,让每个操作符合预期,最终完成一个自己满意的程序。

AI遍地开花时,我希望你,希望我,能继续拥有这份创造、思考的能力。

附录

以下是本文所有代码的整合,可做参考。

鼓励看到文章的你可以依照自己的想法继续补充。

cpp 复制代码
// manager.h
#ifndef MANAGER_H
#define MANAGER_H

struct Program{
    const char* name;
    const char* dsc;
    void (*run)();
};
class ProgramManager{
    private:
        Program programs[16];
        int _index;
        int _get_index(const char* name);
    public:
        ProgramManager();
        void add(
            const char* name,
            void (*run)(),
            const char* dsc
        );
        void start(const char* name);
};

#endif
cpp 复制代码
// manager.cpp
ProgramManager::ProgramManager()
    :_index(0){}

int ProgramManager::_get_index(const char* name){
    for (int i=0;i<16;i++){
        if (strcmp(programs[i].name,name)==0){
            return i;
        }
    }
}

void ProgramManager::add(
    const char* name,
    void (*run)(),
    const char* dsc
){
    if (_index >= 16){
        return;
    }
    programs[_index] = {
        name,
        dsc,
        run
    };
    _index ++;
}

void ProgramManager::start(const char* name){
    programs[_get_index(name)].run();
}
相关推荐
欧特克_Glodon2 小时前
OpenCV计算机视觉开发入门与实践<十六>:图像边框和图像轮廓
c++·人工智能·opencv·计算机视觉
wuminyu2 小时前
JDK21中FFM api的upcall回调机制解析
java·linux·c语言·jvm·c++
我变成萤火虫8 小时前
河南萌新联赛2026第(四)场:南阳理工学院
数据结构·c++·算法·贪心算法·stl·动态规划
余额瞒着我当琳13 小时前
C++--深拷贝三件套 + swap + 写时拷贝 + vector 扩容 + reserve
java·开发语言·c++
ShineWinsu14 小时前
对于C++:布隆过滤器(bloomfilter)的详细解析
c++·面试·位图·位运算·布隆过滤器·海量数据·比特位
牛油果子哥q18 小时前
C++内存模型与深浅拷贝万字详解:栈堆静态内存布局、深浅拷贝底层差异、内存泄漏根治、拷贝崩溃踩坑、手写深拷贝实战
java·开发语言·c++
大鹏的NLP博客18 小时前
通用 Linux 嵌入式板端 C/C++ ABI 与 Glibc 依赖治理规范
linux·c++·交叉编译
ysa05103018 小时前
c++常用自带函数用法与注意
c++·笔记·算法
qq_1508419920 小时前
从CVI(C)到Pyside(python)/Qt(C++)/VS(C#)的一点吐槽
c++·qt·c#