☰
《C++ 工具库实战》gflags 命令行参数解析库详解
2026/10/27 1:51:02 网站建设 项目流程

🔥小叶-duck:个人主页

❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
《Qt 方寸极境》 《MySQL》

✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、gflags 介绍

1.1 gflags 核心特性

1.2 gflags 安装

二、gflags 使用

2.1 包含头文件与编译链接

4.2 定义参数

4.3 访问参数

4.4 跨文件访问参数

4.5 初始化解析所有参数

4.6 运行时命令行设置参数

4.6.1 string /int 类型参数

4.6.2 bool 类型参数

4.7 配置文件的使用

4.8 内置特殊参数标识

三、完整示例讲解

3.1 main.cc

3.2 Makefile

3.3 main.conf(配置文件)

3.4 编译与多种运行方式

3.5 跨文件访问参数演示

结束语


前言

在 C++ 服务端开发中,程序启动时经常需要传入端口、监听 IP、日志级别等各类配置参数。如果手动解析argc/argv,需要自己编写大量字符串判断、类型转换的逻辑,代码繁琐且容易出错。而 gflags 是谷歌开源的 C++ 命令行参数解析库,可以直接定义不同类型的参数,自动完成命令行解析,同时支持配置文件批量加载参数,大幅简化服务启动配置的开发工作。

本篇文章将从 gflags 基础特性、环境安装开始,一步步讲解参数定义、参数读取、跨文件访问参数、命令行传参、配置文件加载以及内置参数等核心用法。最后搭配完整工程示例,结合 Makefile 构建脚本,演示多种程序启动方式,帮助你掌握 gflags 在 C++ 项目中的落地使用。

一、gflags 介绍

gflags是谷歌推出的开源 C++ 命令行参数解析库,专门用来完成命令行配置项的声明、定义与解析工作。借助gflags,我们可以很方便地为程序增加各类运行时配置参数,程序启动时通过命令行传入不同 flag,就能动态调整程序行为,无需修改源码重新编译。

1.1 gflags 核心特性

gflags具备下面这些核心特性:

  • 使用门槛低:gflags 对外暴露简洁清晰的 API,新增命令行参数只需要一行宏定义,不需要手动解析argc/argv,新增配置项十分轻松。
  • 自动生成帮助文档:定义 flag 时可以附带说明文字,程序运行时支持自动输出帮助信息,使用者可以快速了解每个参数的含义与用法。
  • 类型安全:支持布尔、整型、字符串等多种基础类型的配置项,库内部自带类型校验与类型转换逻辑,减少手动类型转换带来的错误。
  • 跨平台兼容:可在 Linux、Windows、macOS 多操作系统下编译运行,跨平台项目可以统一使用这套命令行参数方案。
  • 良好的可扩展能力:支持开发者自行扩展参数注册、解析逻辑,可根据业务需求做定制化封装。

1.2 gflags 安装

Ubuntu 直接命令安装:

sudo apt-get install libgflags-dev

CentOS 直接命令安装:

CentOS 系统可以通过 yum 包管理器直接安装开发库:

# CentOS7 / CentOS8 sudo yum install gflags-devel

说明:CentOS 的包名后缀是-devel,和 Ubuntu 的-dev命名习惯不一样。

源码安装(全平台通用,Ubuntu / CentOS 都可以用):

如果软件源自带版本过低,或者需要自定义编译选项,可以源码编译安装:

# 下载源码 git clone https://github.com/gflags/gflags.git # 切换目录 cd gflags/ mkdir build cd build/ # 生成Makefile cmake .. # 编译代码 make # 安装 make install

补充提示:源码安装完成后,CentOS 如果出现链接找不到库的问题,需要更新动态链接库缓存:

sudo ldconfig

安装完成结果:

二、gflags 使用

2.1 包含头文件与编译链接

想要使用 gflags 库完成命令行参数定义与解析,首先要引入对应的头文件。

#include <gflags/gflags.h>

编译程序时,需要手动链接 gflags 库:

g++ main.cc -o main -lgflags

为什么编译程序时,需要手动链接 gflags 库?

头文件只提供函数与类型声明,没有具体实现。gflags 是第三方库,它的代码实现存放在libgflags.so库文件中,C++ 编译器不会默认加载第三方库,需要通过-lgflags告知链接器去加载该库,否则链接阶段会报未定义引用错误。

4.2 定义参数

gflags提供一系列宏用来声明命令行参数。这类宏一共接收三项参数:命令行参数名称、参数默认值、该参数的说明文字。

DEFINE_bool(reuse_addr, true, "是否开启网络地址重用选项"); DEFINE_int32(log_level, 1, "日志等级:1-DEBUG,2-WARN,3-ERROR"); DEFINE_string(log_file, "stdout", "日志输出位置设置,默认为标准输出");

gflags支持多种基础类型的参数定义,常用宏如下:

  • DEFINE_bool:布尔类型参数
  • DEFINE_int32:32 位整型参数
  • DEFINE_int64:64 位整型参数
  • DEFINE_uint64:无符号 64 位整型参数
  • DEFINE_double:浮点型参数
  • DEFINE_string:字符串类型参数

4.3 访问参数

参数定义完成后,代码中可以直接使用FLAGS_参数名当作普通全局变量读取参数。
以上面例子为例:
对于reuse_addr这个参数对应的普通全局变量就是FLAGS_reuse_addr;
log_level对应的就是FLAGS_log_level;
log_file对应的就是FLAGS_log_file,即可获取命令行传入的值。

4.4 跨文件访问参数

如果需要在其他源文件使用当前文件定义的 flag,不能重复 DEFINE,需要使用DECLARE_*系列宏做外部声明。
以reuse_addr为例:

DECLARE_bool(reuse_addr);

DECLARE 宏本质等价于extern声明(extern FLAGS_reuse_addr),告诉编译器这个全局变量在别的文件中定义,实现跨文件共享参数。

4.5 初始化解析所有参数

参数定义完毕后,需要在main 函数调用解析接口,读取argc/argv,给所有FLAGS_*变量赋值。

int main(int argc, char *argv[]) { google::ParseCommandLineFlags(&argc, &argv, true); return 0; }
  • argc、argv:就是 main 函数自带的命令行参数。
  • 第三个参数remove_flags:
    • 设置为true:解析完成后,会从 argv 中移除已经识别的 flag 参数,同步修改 argc。
    • 设置为false:保留 argv 全部内容,仅调整参数顺序,把识别到的 flag 放到前面。

所以,第三个参数不管是设置为 true 还是 false,都会对我们的 argv 数组进行修改,我们就根据实际需求进行设置即可。
如果我们没有特殊需求的话,这个参数设置哪个都可以,大多数情况只要我们能获取到所需要的参数即可。

4.6 运行时命令行设置参数

程序启动时,可以通过命令行传入 flag,支持长短横线两种写法。

4.6.1 string /int 类型参数

# log_file和"./main.log"用等号连接 ./exec --log_file="./main.log" ./exec -log_file="./main.log" # 也可以不使用等号连接 ./exec --log_file "./main.log" ./exec -log_file "./main.log"

4.6.2 bool 类型参数

布尔类型参数用法比较特殊,布尔类型参数不能像上面的string /int 类型参数那样不使用等号连接,而不选择等号连接的写法是:不带值代表开启,--no前缀代表关闭:

./exec --reuse_addr # 等价 true,开启地址重用 ./exec --noreuse_addr # 等价 false,关闭地址重用 ./exec --reuse_addr=true ./exec --reuse_addr=false

补充:--可以终止 flag 解析。例如./exec -f1 1 -- -f2 2,-f1会被识别为 flag,-f2不会被 gflags 解析,作为普通业务参数。

针对上面不管是 string /int 类型参数 还是 bool 类型参数,我们都推荐的写法是:

./exec --log_file="./main.log" ./exec --reuse_addr=true

因为这样我们就不需要对两种参数的写法进行区分了,统一都是这种形式,而这种形式我们也可以理解为是一种Key-Value 的形式,便于我们记忆使用。

4.7 配置文件的使用

当参数数量很多,每次启动手动输入命令行参数会很麻烦。gflags 支持从配置文件批量加载参数,一次编写配置文件,后续直接复用。

注意:配置文件内的参数名必须和代码 DEFINE 定义的名字完全一致,字符串值不需要加双引号。

示例 main.conf:

-reuse_addr=true -log_level=3 -log_file=./log/main.log

启动时通过--flagfile指定配置文件路径:

./exec --flagfile=main.conf

4.8 内置特殊参数标识

gflags自带内置 flag,无需自己定义,直接命令行使用:

  • --help:打印所有参数的简要帮助信息
  • --helpfull:输出完整详细的帮助文档
  • --helpshort:仅打印当前文件内定义的参数
  • --helpxml:以 xml 格式输出帮助信息,便于程序自动化处理
  • --version:打印程序版本信息
  • --flagfile:从指定文件读取命令行配置

举例:

./exec --help

说明:--help、--helpfull、--helpshort、--helpxml、--version都是查看类指令,执行后程序打印信息就直接退出,不会执行业务逻辑。

三、完整示例讲解

3.1 main.cc

/* gflags使用: 1、包含头文件 <gflags/gflags.h> 2、定义要捕获的参数信息,以及设置默认值 3、解析参数,初始化参数数据 4、获取数据 */ #include <iostream> #include <gflags/gflags.h> #include <string> //定义gflags参数 DEFINE_bool(reuse_addr, true, "是否启用地址复用选项"); DEFINE_int32(listen_port, 8080, "服务器监听端口"); DEFINE_string(listen_ip, "0.0.0.0", "服务器监听IP"); DEFINE_double(pi, 3.14, "圆周率"); int main(int argc, char *argv[]) { //解析命令行参数 google::ParseCommandLineFlags(&argc, &argv, true); //解析完参数,进行获取(注意:获取的是变量,变量名称是参数名称加上 FLAGS_ 前缀) std::cout << FLAGS_reuse_addr << std::endl; std::cout << FLAGS_listen_port << std::endl; std::cout << FLAGS_listen_ip << std::endl; std::cout << FLAGS_pi << std::endl; return 0; }

3.2 Makefile

main:main.cc g++ -o $@ $^ -lgflags .PHONY:clean clean: rm -rf main
  • $@:目标文件(main)
  • $^:所有依赖文件(main.cc)
  • -lgflags:链接 gflags 第三方库

3.3 main.conf(配置文件)

-reuse_addr=false -listen_port=8888 -listen_ip=127.0.0.1 -pi=3.1415926

3.4 编译与多种运行方式

  • 编译
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ make g++ -o main main.cc -lgflags
  • 使用默认参数运行(不传递任何 flag)
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main 1 8080 0.0.0.0 3.14
  • 命令行传参,覆盖默认值
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --reuse_addr=false --listen_port=8888 --listen_ip=127.0.0.1 --pi=3.1415 0 8888 127.0.0.1 3.1415

这里我们会发现如果直接通过命令行传参的形式修改默认值,写起来是非常繁琐的,我们需要对每个 flags 参数都进行重新赋值,这也是为什么有配置文件的存在:通过配置文件加载参数,我们只需要写一项就可以将所有我们需要访问的参数都进行设置。

  • 通过配置文件加载参数
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --flagfile=main.conf 0 8888 127.0.0.1 3.14159

不管我们定义了多少个 gflags 参数,只需要把这些参数都设置到配置文件中,通过--flagfile=main.conf,我们就不再需要一个一个在命令行中进行设置了。

  • 配置文件 + 命令行参数(命令行优先级更高,覆盖配置文件)
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --flagfile=main.conf --listen_port=9090 0 9090 127.0.0.1 3.14159
  • 查看帮助信息(内置 flag)
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --help main: Warning: SetUsageMessage() never called Flags from ./src/gflags.cc: -flagfile (load flags from file) type: string default: "" -fromenv (set flags from the environment [use 'export FLAGS_flag1=value']) type: string default: "" -tryfromenv (set flags from the environment if present) type: string default: "" -undefok (comma-separated list of flag names that it is okay to specify on the command line even if the program does not define a flag with that name. IMPORTANT: flags in this list that have arguments MUST use the flag=value format) type: string default: "" Flags from ./src/gflags_completions.cc: -tab_completion_columns (Number of columns to use in output for tab completion) type: int32 default: 80 -tab_completion_word (If non-empty, HandleCommandLineCompletions() will hijack the process and attempt to do bash-style command line flag completion on this value.) type: string default: "" Flags from ./src/gflags_reporting.cc: -help (show help on all flags [tip: all flags can have two dashes]) type: bool default: false currently: true -helpfull (show help on all flags -- same as -help) type: bool default: false -helpmatch (show help on modules whose name contains the specified substr) type: string default: "" -helpon (show help on the modules named by this flag value) type: string default: "" -helppackage (show help on all modules in the main package) type: bool default: false -helpshort (show help on only the main module for this program) type: bool default: false -helpxml (produce an xml version of help) type: bool default: false -version (show version and build info and exit) type: bool default: false Flags from main.cc: -listen_ip (服务器监听IP) type: string default: "0.0.0.0" -listen_port (服务器监听端口) type: int32 default: 8080 -pi (圆周率) type: double default: 3.1400000000000001 -reuse_addr (是否启用地址复用选项) type: bool default: true

代码要点小结:

  1. DEFINE_*在全局域定义参数,不能写在函数内部。
  2. google::ParseCommandLineFlags放在 main 开头,解析后才能读取FLAGS_*变量。
  3. 配置文件参数不需要引号,参数名和 DEFINE 保持一致。
  4. 优先级:命令行参数 > 配置文件参数 > 代码内默认值。

3.5 跨文件访问参数演示

child.h:

extern void Print();

child.cc(错误写法):

#include <iostream> #include <gflags/gflags.h> void Print() { //跨文件访问gflags参数 std::cout << FLAGS_listen_ip << std::endl; }

main.cc:

#include <iostream> #include <gflags/gflags.h> #include <string> #include "child.h" //定义gflags参数 DEFINE_bool(reuse_addr, true, "是否启用地址复用选项"); DEFINE_int32(listen_port, 8080, "服务器监听端口"); DEFINE_string(listen_ip, "0.0.0.0", "服务器监听IP"); DEFINE_double(pi, 3.14, "圆周率"); int main(int argc, char *argv[]) { //解析命令行参数 google::ParseCommandLineFlags(&argc, &argv, true); //解析完参数,进行获取(注意:获取的是变量,变量名称是参数名称加上 FLAGS_ 前缀) std::cout << FLAGS_reuse_addr << std::endl; std::cout << FLAGS_listen_port << std::endl; std::cout << FLAGS_listen_ip << std::endl; std::cout << FLAGS_pi << std::endl; Print(); return 0; }

Makefile:

main:main.cc child.cc g++ -o $@ $^ -lgflags .PHONY:clean clean: rm -rf main

运行错误结果演示:

child.cc(正确写法):

#include <iostream> #include <gflags/gflags.h> DECLARE_string(listen_ip); void Print() { //跨文件访问gflags参数 std::cout << FLAGS_listen_ip << std::endl; }

运行结果演示:

[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ make g++ -o main main.cc child.cc -lgflags [ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main 1 8080 0.0.0.0 3.14 0.0.0.0

最后我们再拿曾经基于UDP协议写的一个Echo回显服务器,对代码进行修改,加上 gflags 第三方库,我们就可以直观感受到 gflags 带来的效果:

UdpServer.cc:

#include "UdpServer.hpp" #include <iostream> #include <cstdio> #include <memory> #include <gflags/gflags.h> DEFINE_int32(port, 8080, "端口号"); // ./udpserver port int main(int argc, char *argv[]) { // if (argc != 2) // { // std::cerr << "Usage: " << argv[0] << " port" << std::endl; // return 1; // } // gflags 替代了手动检查 argc google::ParseCommandLineFlags(&argc, &argv, true); ENABLE_CONSOLE_LOG_STRATEGY(); // uint16_t port = std::stoi(argv[1]); // 字符串转成uint16_t,获取客户传入的端口号 // 无需手动解析 argv,无需处理 char* 到其他类型的转换 // 类型自动转换:它内部会根据你定义的宏(_int32, _double, _string 等), // 自动把命令行传进来的字符串(char*)转换成对应的 C++ 类型 uint16_t port = FLAGS_port; std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, calculate_handler); usvr->Init(); usvr->Start(); return 0; }

UdpClient.cc:

// 各自头文件包含... #include <gflags/gflags.h> DEFINE_string(ip, "127.0.0.1", "IP地址"); DEFINE_int32(port, 8080, "端口号"); // ./udpclient ip port int main(int argc, char *argv[]) { // if (argc != 3) // { // std::cerr << "Usage: " << argv[0] << " ip port" << std::endl; // return 1; // } google::ParseCommandLineFlags(&argc, &argv, true); // 1、创建socket套接字 int socketfd = socket(AF_INET, SOCK_DGRAM, 0); if (socketfd < 0) { std::cerr << "socket created error" << std::endl; return 2; } // 填写服务器信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // server.sin_port = htons(std::stoi(argv[2])); // server.sin_addr.s_addr = inet_addr(argv[1]); server.sin_port = htons(FLAGS_port); server.sin_addr.s_addr = inet_addr(FLAGS_ip.c_str()); // 读写IO处理... return 0; }

Makefile:

.PHONY:all all:udpserver udpclient udpserver:UdpServer.cc g++ -o $@ $^ -std=c++17 -lgflags udpclient:UdpClient.cc g++ -o $@ $^ -std=c++17 -lgflags .PHONY:clean clean: rm -rf udpserver udpclient

运行结果展示:

结束语

到这里,gflags 的基础用法与工程实战就全部讲解完毕。我们从库的特性、安装入手,依次学习了参数定义、参数访问、跨文件共享参数、命令行传参、配置文件加载以及内置特殊标识等核心能力,并通过一套完整示例工程,演示了编译、多种运行方式的实际效果。

gflags 凭借简洁的 API、多类型参数支持,成为 C++ 后端项目中非常常用的命令行配置工具。需要注意的是,gflags 更适合存放程序启动时的静态配置,不适合运行时动态频繁修改业务参数。

后续你可以在自己的网络服务项目中集成 gflags,把端口、IP、日志开关等配置交由它管理,替代硬编码,提升项目的灵活性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询