
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
[一、gflags 介绍](#一、gflags 介绍)
[1.1 gflags 核心特性](#1.1 gflags 核心特性)
[1.2 gflags 安装](#1.2 gflags 安装)
[二、gflags 使用](#二、gflags 使用)
[2.1 包含头文件与编译链接](#2.1 包含头文件与编译链接)
[4.2 定义参数](#4.2 定义参数)
[4.3 访问参数](#4.3 访问参数)
[4.4 跨文件访问参数](#4.4 跨文件访问参数)
[4.5 初始化解析所有参数](#4.5 初始化解析所有参数)
[4.6 运行时命令行设置参数](#4.6 运行时命令行设置参数)
[4.6.1 string /int 类型参数](#4.6.1 string /int 类型参数)
[4.6.2 bool 类型参数](#4.6.2 bool 类型参数)
[4.7 配置文件的使用](#4.7 配置文件的使用)
[4.8 内置特殊参数标识](#4.8 内置特殊参数标识)
[3.2 Makefile](#3.2 Makefile)
[3.3 main.conf(配置文件)](#3.3 main.conf(配置文件))
[3.4 编译与多种运行方式](#3.4 编译与多种运行方式)
[3.5 跨文件访问参数演示](#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 直接命令安装:
bash
sudo apt-get install libgflags-dev
CentOS 直接命令安装:
CentOS 系统可以通过 yum 包管理器直接安装开发库:
bash
# CentOS7 / CentOS8
sudo yum install gflags-devel
说明: CentOS 的包名后缀是
-devel,和 Ubuntu 的-dev命名习惯不一样。
源码安装(全平台通用,Ubuntu / CentOS 都可以用):
如果软件源自带版本过低,或者需要自定义编译选项,可以源码编译安装:
bash
# 下载源码
git clone https://github.com/gflags/gflags.git
# 切换目录
cd gflags/
mkdir build
cd build/
# 生成Makefile
cmake ..
# 编译代码
make
# 安装
make install
补充提示:源码安装完成后,CentOS 如果出现链接找不到库的问题,需要更新动态链接库缓存:
bash
sudo ldconfig
安装完成结果:


二、gflags 使用
2.1 包含头文件与编译链接
想要使用 gflags 库完成命令行参数定义与解析,首先要引入对应的头文件。
cpp
#include <gflags/gflags.h>
编译程序时,需要手动链接 gflags 库:
bash
g++ main.cc -o main -lgflags
为什么编译程序时,需要手动链接 gflags 库?
头文件只提供函数与类型声明 ,没有具体实现。gflags 是第三方库 ,它的代码实现存放在libgflags.so库文件中,C++ 编译器不会默认加载第三方库 ,需要通过-lgflags告知链接器去加载该库,否则链接阶段会报未定义引用错误。
4.2 定义参数
gflags 提供一系列宏 用来声明命令行参数 。这类宏一共接收三项参数 :命令行参数名称、参数默认值、该参数的说明文字。
cpp
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为例:
cpp
DECLARE_bool(reuse_addr);
DECLARE 宏 本质等价于extern声明(extern FLAGS_reuse_addr),告诉编译器这个全局变量在别的文件中定义,实现跨文件共享参数。
4.5 初始化解析所有参数
参数定义完毕后,需要在main 函数 调用解析接口 ,读取argc/argv,给所有FLAGS_*变量赋值。
cpp
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 类型参数
bash
# 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前缀代表关闭:
bash
./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 类型参数,我们都推荐的写法是:
bash
./exec --log_file="./main.log"
./exec --reuse_addr=true
因为这样我们就不需要对两种参数的写法进行区分了,统一都是这种形式,而这种形式我们也可以理解为是一种Key-Value 的形式,便于我们记忆使用。
4.7 配置文件的使用
当参数数量很多,每次启动手动输入命令行参数会很麻烦。gflags 支持从配置文件批量加载参数,一次编写配置文件,后续直接复用。
注意:配置文件内的参数名 必须和代码 DEFINE 定义的名字完全一致 ,字符串 值不需要加双引号。
示例 main.conf:
cpp
-reuse_addr=true
-log_level=3
-log_file=./log/main.log
启动时通过--flagfile指定配置文件路径:
bash
./exec --flagfile=main.conf
4.8 内置特殊参数标识
gflags 自带内置 flag,无需自己定义,直接命令行使用:
--help:打印所有参数的简要帮助信息--helpfull:输出完整详细的帮助文档--helpshort:仅打印当前文件内定义的参数--helpxml:以 xml 格式输出帮助信息,便于程序自动化处理--version:打印程序版本信息--flagfile:从指定文件读取命令行配置
举例:
bash
./exec --help
说明:
--help、--helpfull、--helpshort、--helpxml、--version都是查看类指令 ,执行后程序打印信息就直接退出,不会执行业务逻辑。
三、完整示例讲解
3.1 main.cc
cpp
/*
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
cpp
main:main.cc
g++ -o $@ $^ -lgflags
.PHONY:clean
clean:
rm -rf main
$@:目标文件(main)$^:所有依赖文件(main.cc)-lgflags:链接 gflags 第三方库
3.3 main.conf(配置文件)
cpp
-reuse_addr=false
-listen_port=8888
-listen_ip=127.0.0.1
-pi=3.1415926
3.4 编译与多种运行方式
- 编译
bash
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ make
g++ -o main main.cc -lgflags
- 使用默认参数运行(不传递任何 flag)
bash
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main
1
8080
0.0.0.0
3.14
- 命令行传参,覆盖默认值
bash
[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 参数都进行重新赋值,这也是为什么有配置文件的存在:通过配置文件加载参数,我们只需要写一项就可以将所有我们需要访问的参数都进行设置。
- 通过配置文件加载参数
bash
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --flagfile=main.conf
0
8888
127.0.0.1
3.14159
不管我们定义了多少个 gflags 参数,只需要把这些参数都设置到配置文件中,通过 --flagfile=main.conf,我们就不再需要一个一个在命令行中进行设置了。
- 配置文件 + 命令行参数(命令行优先级更高,覆盖配置文件)
bash
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --flagfile=main.conf --listen_port=9090
0
9090
127.0.0.1
3.14159
- 查看帮助信息(内置 flag)
bash
[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
代码要点小结:
DEFINE_*在全局域定义参数,不能写在函数内部。google::ParseCommandLineFlags放在 main 开头,解析后才能读取FLAGS_*变量。- 配置文件参数不需要引号,参数名和 DEFINE 保持一致。
- 优先级:命令行参数 > 配置文件参数 > 代码内默认值。
3.5 跨文件访问参数演示
child.h:
cpp
extern void Print();
child.cc(错误写法):
cpp
#include <iostream>
#include <gflags/gflags.h>
void Print()
{
//跨文件访问gflags参数
std::cout << FLAGS_listen_ip << std::endl;
}
cpp
#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:
cpp
main:main.cc child.cc
g++ -o $@ $^ -lgflags
.PHONY:clean
clean:
rm -rf main
运行错误结果演示:

child.cc(正确写法):
cpp
#include <iostream>
#include <gflags/gflags.h>
DECLARE_string(listen_ip);
void Print()
{
//跨文件访问gflags参数
std::cout << FLAGS_listen_ip << std::endl;
}
运行结果演示:
bash
[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 带来的效果:
cpp
#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;
}
cpp
// 各自头文件包含...
#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:
cpp
.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、日志开关等配置交由它管理,替代硬编码,提升项目的灵活性。