项目名:命令系统(Command System)
作者: SoliGhost
最后一次更新:2024.10.24 22:30
项目地址:https://github.com/SoliGhost/CmdSys
状态:开发中

父项目:(由于项目性质的特殊性,无法公开)

项目背景

在某一项目中,遇到了需要自制命令系统的需求,而这个模块的复用性很高,因此单独拉出来做一个子项目

更新日志

[2024.10.15 - 10:00]【增】命令系统基本的输入输出功能
[2024.10.24 - 22:30]【增】命令系统的核心函数执行功能if-else版
[2024.10.25 - 08:30]【增】命令系统的核心函数执行功能switch版


项目进度

[2024.10.15 10:00]【增】命令系统基本的输入输出功能

  • 首先实现最基础的输入输出功能,用std::getline读入行再分割成字符串数组

- main.cpp

#include <iostream>
#include <windows.h>
#include <vector>
#include <string>
#include <cstring>    //for std::strtok
#include "cmd_sys.h"

//using namespace std;    //byte冲突,在后续项目中要用到
//封装成模块时,应删掉所有下面的using,避免冲突
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;

void mainloop(void) {
    string full_cmd;
    string cmd;
    char* arg_;    //个人习惯使用"<变量名>_"作为临时变量名
    vector<string> args;
    while (1) {
        cout << "> ";
        std::getline(cin, full_cmd);
        if (full_cmd == "") {
            continue;
        }

        cmd = std::strtok(full_cmd.data(), " ");
        arg_ = std::strtok(NULL, " ");
        while (arg_ != NULL) {
            args.push_back(arg_);
            arg_ = strtok(NULL, " ");
        }
        cmd_fun(cmd, args);
        args.clear();
    }
}

int main(void) {
    SetConsoleOutputCP(65001);  //中文输出
    cout << "命令系统 - Command System" << endl << "By: SoliGhost" << endl << endl;
    mainloop();
    return 0;
}

坑点1:用C++的std::strtok实现python中的split功能

  • std::getline需要full_cmd为std::string类型
    但std::strtok要分割的字符串却必须是char*类型
    又不能直接强转,查了半天查到可以调用std::string对象的data方法获取char*类型的字符串

Soli评: string和char*要不你俩打一架吧,为啥string库内部的函数也不能统一成string啊

[2024.10.24 22:30]【增】命令系统的核心函数执行功能if-else版

  • 然后是命令系统的核心,根据输入的命令调用对应的函数,那么最简单的方法就是if-else分支语句了

- cmd_sys.h

#ifndef CMD_SYS_H
#define CMD_SYS_H

#include <string>
#include <vector>

void cmd_fun(std::string cmd, std::vector<std::string> args);

#endif

- cmd_sys.cpp

#include <iostream>
#include <map>
#include "cmd_sys.h"

using std::cout;
using std::cin;
using std::string;
using std::vector;
using std::endl;
using std::map;

void cmd_help_fun(vector<string> args) {
    if (args.size() > 1) {
        cout << "[ERROR] help只需要0或1个参数,输入了" << args.size() << "个参数。需要帮助请输入\"help help\"\n";
        return;
    } else if (args.size() == 0) {
        cout << "<Program General Help Document>" << endl;
        return;
    }

    if (args[0] == "help" || args[0] == "?") {
        cout << R"TAG(命令名: help 别称: ?
用途: 查看帮助
用法:
    generatesbox (<command_name>)
参数说明:
    command_name 要查看帮助的命令名,不写则输出总帮助文档
)TAG";
    } else if (args[0] == "fun1" || args[0] == "1") {
        cout << R"TAG(Document of fun1)TAG" << endl;
    } else if (args[0] == "fun2" || args[0] == "2") {
        cout << R"TAG(Document of fun2)TAG" << endl;
    } else {
        cout << "不存在命令: \"" << args[0] << "\",或该命令没有帮助文档。需要帮助请输入\"help help\"\n";
    }
}

void cmd_fun1_fun(vector<string> args) {
    cout << "fun1 called.\nArgs: ";
    for (string arg : args) {
        cout << arg << ", ";
    }
    cout << "\b\b \b\b\n";
}

void cmd_fun2_fun(vector<string> args) {
    cout << "fun2 called.\nArgs: ";
    for (string arg : args) {
        cout << arg << ", ";
    }
    cout << "\b\b  \b\b\n";
}

void cmd_fun(string cmd, vector<string> args) {
    if (cmd == "help" || cmd == "?") {
        cmd_help_fun(args);
    } else if (cmd == "fun1" || cmd == "1") {
        cmd_fun1_fun(args);
    } else if (cmd == "fun2" || cmd == "2") {
        cmd_fun2_fun(args);
    } else {
        cout << "不存在该命令: \"" << cmd << "\"" << endl;
    }
}

不过在这段程序里有一个小小的瑕疵,但是我懒得改了,有没有细心的读者能够发现呢:)。

[2024.10.30 13:00]【增】命令系统的核心函数执行功能switch版

昨天先用最简单的if-else把功能实现了,但是"传闻说"switch语句会更快,于是我们就试试用switch语句来实现命令名到函数的映射。

- cmd_sys.h

#ifndef CMD_SYS_H
#define CMD_SYS_H

#include <string>
#include <map>
#include <vector>

const std::map<std::string, int> cmd_id_map = {
    {"help", 0},
    {"?",    0},
    {"fun1", 1},
    {"1",    1},
    {"fun2", 2},
    {"2",    2}
};

void cmd_fun(std::string cmd, std::vector<std::string> args);

#endif

- cmd_sys.cpp

#include <iostream>
#include <map>
#include "cmd_sys_v1.h"

using std::cout;
using std::cin;
using std::string;
using std::vector;
using std::endl;
using std::map;

void cmd_help_fun(vector<string> args) {
    if (args.size() > 1) {
        cout << "[ERROR] help只需要0或1个参数,输入了" << args.size() << "个参数。需要帮助请输入\"help help\"\n";
        return;
    } else if (args.size() == 0) {
        cout << "<Program General Help Document>" << endl;
        return;
    }
    if (cmd_id_map.find(args[0]) == cmd_id_map.end()) {
        cout << "不存在命令: \"" << args[0] << "\"。需要帮助请输入\"help help\"\n";
        return;
    }
    int cmd_id = cmd_id_map.at(args[0]);
    switch (cmd_id) {
    case 0:
        cout << R"TAG(命令名: help 别称: ?
用途: 查看帮助
用法:
    generatesbox (<command_name>)
参数说明:
    command_name 要查看帮助的命令名,不写则输出总帮助文档
)TAG";
        break;
    case 1:
        cout << R"TAG(Document of fun1)TAG" << endl;
        break;
    case 2:
        cout << R"TAG(Document of fun2)TAG" << endl;
        break;
    default:
        cout << "命令\"" << args[0] << "\"没有帮助文档\n";
        break;
    }
}

void cmd_fun1_fun(vector<string> args) {
    cout << "fun1 called.\nArgs: ";
    for (string arg : args) {
        cout << arg << ", ";
    }
    cout << "\b \b";
}

void cmd_fun2_fun(vector<string> args) {
    cout << "fun2 called.\nArgs: ";
    for (string arg : args)  {
        cout << arg << ", ";
    }
    cout << "\b \b";
}

void cmd_fun_v1(string cmd, vector<string> args) {
    if (cmd_id_map.find(cmd) == cmd_id_map.end()) {
        cout << "不存在该命令: \"" << cmd << "\"" << endl;
        return;
    }
    int cmd_id = cmd_id_map.at(cmd);
    switch (cmd_id) {
    case 0:
        cmd_help_fun(args);
        break;
    case 1:
        cmd_fun1_fun(args);
        break;
    case 2:
        cmd_fun2_fun(args);
        break;
    default:
        cout << "[ERROR] ID为" << cmd_id << "的命令\"" << cmd << "\"没有对应的执行函数,请检查你的源代码" << endl;
        break;
    }
}

main.cpp并不需要修改(这就是模块化编程的好处啦)

坑点1: switch到字符串类型case

  • switch语句的case值必须是整型常量,不能是字符串,所以得先映射一遍,这里用map来映射。当然还有别的方案,比如用字符编码,但是整型不够大,这样只能用很短的字符串了;或者用摘要算法那你就得考虑碰撞的问题,这样一来可能比直接map还复杂。