此文基于前篇,在阅读本文之前,建议先看一下本系列的第一篇(系统设计)。
在前篇文章中,我们介绍了如何把一个想法从零变成一个基础的有大致逻辑的框架,以及如何把框架与编程语言做一个基础的结合,由此得到一个"底座"。
现在,我们将在底座上按位填入合适的内容,从而得到一个不错的骨架。
声明:本系列文章的目的旨在讲述如何把想法变为一段好的代码,总体来说可能不太适合专业人士。
框架回顾
回顾一下我们的框架,大概是这样子的:
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();
}