一、前言
对于简单的C/C++应用程序,一般只需要根据 main 函数的 argc 和 argv 参数列表进行传值。但是这种方式较为固化,每次都需要传入所有参数,并且参数间的相对关系也不能更改。
虽然可以使用 shell 脚本对以上方式进行优化,不过在应用程序中进行集成可变的参数解析,对后续的开发也能起到很好的铺垫作用。
本文使用 getopt 库进行参数解析,实现 Linux 常见应用中的命令行解析方式,即 -h 和 --help 形式。样例我分为 C 的版本和 C++ 的版本,C++ 使用面向对象进行了优化,整体的逻辑是一样的,都是使用 getopt 方式进行解析。C++ 好像使用 Boost.Program_options
的较多,读者可以自行设计,我这里是先做了 C 的版本,然后更迭出 C++ 的版本。
二、参考样例
1、C版本
这里的源文件来自我 oled 小屏幕的应用程序,实现了引脚,页码,间隔时间等参数的解析,带有帮助显示,支持长命令和短命令混合使用。
parse_config.c:
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include "parse_config.h"
/**
* @Description: 显示帮助信息
* @param {char} *program_name: 程序名称
* @return {*}
*/
void print_help(const char *program_name) {
printf("Usage: %s [options]\n", program_name);
printf("Options:\n");
printf(" -o, --oled_pins <scl,mosi,res,dc> Set oled pin number\n");
printf(" -p, --page <number> Set display page (1, 2, or 3)\n");
printf(" -i, --interval <seconds> Set update interval (default: 1)\n");
printf(" -t, --text <string> Set display text\n");
printf(" -v, --verbose Enable verbose output\n");
printf(" -h, --help Show this help message\n");
}
/**
* @Description: 打印配置信息
* @param {AppConfig} config: 配置信息
* @return {*}
*/
void print_config(AppConfig config) {
printf(" **************************\n");
printf("Parse Information:\n");
printf(" Display page: %d\n", config.page);
printf(" Update Interval: %d millisecond(ms)\n", config.interval);
printf(" Display Text: %s\n", config.text);
printf(" GPIOs: %s\n", config.oled_pins);
}
/*
* @description : 解析命令行参数
* @param : 无
* @return : 无
*/
AppConfig parse_arguments(int argc, char *argv[]) {
AppConfig config = {
.oled_pins = NULL, // 默认为空字符串,提示用户传入
.page = 1, // 默认显示风格
.interval = 1000, // 默认更新间隔
.text = "SPI OLED", // 默认显示文本
.verbose = 0 // 默认关闭详细信息
};
/* 定义长选项 */
struct option long_options[] = {
{"oled_pins", required_argument, 0, 'o'},
{"page", required_argument, 0, 'p'},
{"interval", required_argument, 0, 'i'},
{"text", required_argument, 0, 't'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
/* 解析参数 */
int opt;
// 支持短选项和长选项
// : 表示该选项需要一个参数,v 和 h 不需要
// 如果解析到长选项,返回 val 字段的值(即第四列)
while ((opt = getopt_long(argc, argv, "o:p:i:t:vh", long_options, NULL)) != -1) {
switch (opt) {
case 'o':
config.oled_pins = optarg;
break;
case 'p':
config.page = atoi(optarg);
if (config.page < 1 || config.page > 3) {
fprintf(stderr, "Invalid page number. Use 1, 2, or 3.\n");
exit(EXIT_FAILURE);
}
break;
case 'i':
config.interval = atoi(optarg);
if (config.interval <= 0) {
fprintf(stderr, "Interval must be a positive number.\n");
exit(EXIT_FAILURE);
}
break;
case 't':
config.text = optarg;
break;
case 'v':
config.verbose = 1;
break;
case 'h':
print_help(argv[0]);
exit(EXIT_SUCCESS);
default:
print_help(argv[0]);
exit(EXIT_FAILURE);
}
}
return config;
}
parse_config.h:
#ifndef _PARSE_CONFIG_H_
#define _PARSE_CONFIG_H_
/* 定义命令行参数结构体 */
typedef struct {
char *oled_pins; // 控制引脚
int page; // 显示主页
int interval; // 更新间隔(ms毫秒)
char *text; // 显示文本
int verbose; // 是否显示详细信息
} AppConfig;
void print_help(const char *program_name);
AppConfig parse_arguments(int argc, char *argv[]);
void print_config(AppConfig config);
#endif
2、C++ 版本
这里的源文件来自另一个 YOLO 加速的项目,主要实现模型路径,输入数据,线程数等参数的解析。这里也同样带有帮助显示,支持长命令和短命令混合使用。
parse_config.cpp:
#include <iostream>
#include <cstdlib>
#include <getopt.h>
#include "parse_config.hpp"
/**
* @Description: 显示帮助信息
* @param {char} *program_name: 程序名称
* @return {*}
*/
void ConfigParser::print_help(const string &program_name) const { // 常量成员函数承诺不会修改调用它的对象的任何成员变量(函数内只有只读操作)
cout << "Usage: " << program_name << " [options]" << endl;
cout << "Options:" << endl;
cout << " -m, --model_path <string, require> || Set rknn model path. need to be set" << endl;
cout << " -i, --input <int or string, require> || Set input source. int: Camera index, like 0; String: video path. need to be set" << endl;
cout << " -a, --hwaccels <int> || Configure the hardware acceleration mode. 1:letterbox, 2:RGA. default: 2" << endl;
cout << " -t, --threads <string> || Set threads number. default: 1" << endl;
cout << " -c, --opencl <bool or int> || Configure the opencl mode. true(1):use opencl, fals(0):use cpu. default: True(1)" << endl;
cout << " -v, --verbose || Enable verbose output" << endl;
cout << " -h, --help || Show this help message" << endl;
}
/**
* @Description: 打印配置信息
* @param {AppConfig} config: 配置信息
* @return {*}
*/
// 打印配置信息
void ConfigParser::printConfig(const AppConfig &config) const {
cout << "*************************" << endl;
cout << "Parse Information:" << endl;
cout << " Model path: " << config.model_path << endl;
cout << " Input source: " << config.input << endl;
cout << " Hwaccels: " << config.hwaccels << endl;
cout << " Threads: " << config.threads << endl;
cout << " Opencl: " << boolalpha << config.opencl << endl; // boolalpha: 将 bool 类型以 true/false 形式输出
}
/**
* @Description: 解析命令行参数
* @param {int} argc: 命令行参数个数
* @param {char} *argv: 命令行参数数组
* @return {*}
*/
AppConfig ConfigParser::parse_arguments(int argc, char *argv[]) const {
if (argc < 2) {
this->print_help(argv[0]);
exit(EXIT_FAILURE);
}
AppConfig config;
/* 定义长选项 */
struct option long_options[] = {
{"model_path", required_argument, nullptr, 'm'},
{"input", required_argument, nullptr, 'i'},
{"hwaccels", required_argument, nullptr, 'a'},
{"threads", required_argument, nullptr, 't'},
{"opencl", required_argument, nullptr, 'c'},
{"verbose", no_argument, nullptr, 'v'},
{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0}
};
/* 解析参数 */
int opt;
// 支持短选项和长选项
// : 表示该选项需要一个参数,v 和 h 不需要
// 如果解析到长选项,返回 val 字段的值(即第四列)
while ((opt = getopt_long(argc, argv, "m:i:a:t:c:vh", long_options, nullptr)) != -1) {
switch (opt) {
case 'm':
config.model_path = optarg;
break;
case 'i':
config.input = optarg;
break;
case 'a':{
try {
config.hwaccels = std::stoi(optarg);
if (config.hwaccels != LETTERBOX && config.hwaccels != RGA)
throw std::invalid_argument("Unsupported hwaccel type.");
} catch (const std::exception &e) {
exit(EXIT_FAILURE);
}
break;
}
case 't':
config.threads = std::stoi(optarg);
break;
case 'c': {
if (optarg == "true" || optarg == "1")
config.opencl = true;
else if (optarg == "false" || optarg == "0")
config.opencl = false;
}
case 'v':
config.verbose = true;
break;
case 'h':
this->print_help(argv[0]);
exit(EXIT_SUCCESS);
default:
this->print_help(argv[0]);
exit(EXIT_FAILURE);
}
}
if (config.verbose)
this->printConfig(config);
return config;
}
parse_config.hpp:
#ifndef _PARSE_CONFIG_HPP_
#define _PARSE_CONFIG_HPP_
#include <iostream>
#include <string>
using namespace std;
enum hwaccels {
LETTERBOX = 1,
RGA = 2,
};
/* 定义命令行参数结构体 */
struct AppConfig {
std::string model_path = ""; // rknn 模型路径
std::string input = ""; // 输入源
int hwaccels = RGA; // 硬件加速,默认为 RGA
int threads = 1; // 线程数,默认为1
bool opencl = true; // 是否使用opencl
bool verbose = false; // 是否打印命令行参数
};
/* 定义配置解析类 */
class ConfigParser {
public:
// 显示帮助信息
void print_help(const string &program_name) const;
// 打印配置信息
void printConfig(const AppConfig &config) const;
// 解析命令行参数
AppConfig parse_arguments(int argc, char *argv[]) const;
private:
// 私有成员(如果有需要可以添加)
};
#endif
三、总结
实现效果:
源码我会放至 GitHub 和 Gitee,方便克隆使用。
1125962926/argument_parsing: Command line argument parsinghttps://github.com/1125962926/argument_parsing
argument_parsing: Command line argument parsinghttps://gitee.com/lrf1125962926/argument_parsing