目录
基础工具的安装
框架安装
基础工具的安装
sudo apt-get install gcc g++ // 编译器安装 sudo apt-get install gdb // 调试器安装 sudo apt-get install make cmake // 项目构建工具安装 sudo apt-get install lrzsz // 传输工具安装 sudo apt-get install git // 版本管理工具安装 //g++ --version(安装好后,可以查看版本)框架安装
2.1 gflags 框架安装
sudo apt-get install libgflags-dev //查找系统路径下的 gflags 文件夹 ls /usr/include/gflags2.2 gflags 的使用
2.2.1 包含头文件
#include <gflags/gflags.h>2.2.2 定义参数
利用 gflag 提供的宏定义来定义参数。该宏的 3 个参数分别为命令行参数名,参数默认值,参数的帮助信息。
DEFINE_bool(reuse_addr, true, "是否开始网络地址重用选项"); DEFINE_int32(log_level, 1, "日志等级:1-DEBUG, 2-WARN, 3-ERROR"); DEFINE_string(log_file, "stdout", "日志输出位置设置,默认为标准输出");2.2.3 访问参数
我们可以在程序中通过 FLAGS_name 像正常变量一样访问标志参数。比如在上面的例 子中,我们可以通过 FLAGS_big_menu 和 FLAGS_languages 变量来访问命令行参数。
2.2.4 初始化所有参数
当我们定义好参数后,需要告诉可执行程序去处理解析命令行传入的参数,使得 FLAGS_*变量能得到正确赋值。我们需要在main函数中,调用下面的函数来解决命令行传入的所有参数。
google::ParseCommandLineFlags(&argc, &argv, true);argc和argv就是 main 的入口参数 • 第三个参数被称为remove_flags。如果它为true, 表示 ParseCommandLineFlags 会从 argv 中移除标识和它们的参数,相应减少 argc 的值。 如果它为false,ParseCommandLineFlags 会保留argc 不变,但将会重新调整它们的顺序,使得标识再前面。
2.2.5 简单的示例
// 引入 gflags 库的主头文件 #include <gflags/gflags.h> // #include <iostream> // 1. 定义命令行参数 (宏定义) // 定义一个 string 类型的参数,名为 ip,默认值是 "127.0.0.1" // gflags 会在底层自动创建一个名为 FLAGS_ip 的全局字符串变量 DEFINE_string(ip, "127.0.0.1", "这是服务器的监听IP地址,格式:127.0.0.1"); // 定义一个 int32 (32位整数) 类型的参数,名为 port,默认值是 "8080" // 注意:这里的默认值写成了字符串形式 "8080",gflags 底层会自动将其转换为整数 // gflags 会在底层自动创建一个名为 FLAGS_port 的全局整型变量 DEFINE_int32(port, "8080", "这是服务器的监听端口,格式:8080"); // 定义一个 bool 类型的参数,名为 debug_enable,默认值是 true // gflags 会在底层自动创建一个名为 FLAGS_debug_enable 的全局布尔变量 DEFINE_bool(debug_enable, true, "是否启用调试模式,格式:true/false"); int main(int argc, char *argv[]) { // 【核心步骤】:解析命令行参数 // 参数1: &argc (命令行参数个数) // 参数2: &argv (命令行参数数组) // 参数3: true (表示解析完成后,从 argv 中移除已识别的 gflags 参数, // 避免影响程序后续可能需要的其他非 gflags 参数。通常填 true) google::ParseCommandLineFlags(&argc, &argv, true); // ========================================== // 3. 使用解析后的结果 // 只要经过了 ParseCommandLineFlags 解析, // 之前定义的参数就可以直接通过 FLAGS_参数名 直接访问,且类型匹配。 // ========================================== std::cout << FLAGS_ip << std::endl; std::cout << FLAGS_port << std::endl; std::cout << FLAGS_debug_enable << std::endl; return 0; }2.2.6 运行参数设置
exec --log_file="./main.log" exec -log_file="./main.log" exec --log_file "./main.log" exec -log_file "./main.log2.2.7 配置文件的使用
配置文件的使用,其实就是为了让程序的运行参数配置更加标准化,不需要每次运行 的时候都手动收入每个参数的数值,而是通过配置文件,一次编写,永久使用。 需要注意的是,配置文件中选项名称必须与代码中定义的选项名称一致。
3.1 gtest 框架安装
sudo apt-get install libgtest-dev3.2 gtest 的使用样例
#include <gtest/gtest.h> // 引入 GTest 头文件 // 1. 待测试的业务函数 int Add(int a, int b) { return a + b; } // 2. 编写测试用例 // 格式:TEST(测试套件名, 测试用例名) TEST(MathTest, AddTest) { // EXPECT_EQ:非致命断言,失败继续跑 EXPECT_EQ(Add(1, 2), 3); // ASSERT_EQ:致命断言,失败直接退出当前 TEST 函数 ASSERT_EQ(Add(10, 20), 30); } // 3. 框架入口(对应你截图里的 main 函数) int main(int argc, char *argv[]) { // 初始化 GTest,解析命令行参数(如 --gtest_filter) testing::InitGoogleTest(&argc, argv); // 运行所有被 TEST 宏注册的用例,全过返回 0,有失败返回 1 return RUN_ALL_TESTS(); }4.1 Spdlog 日志组件的安装
sudo apt-get install libspdlog-dev4.2 Spdlog 日志组件的使用
#include <iostream> #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" // 必须包含这个才能用 stdout_color_mt int main() { //设置全局的刷新策略 //每秒刷新 spdlog::flush_every(std::chrono::seconds(1)); //遇到debug以上等级的日志立即刷新 spdlog::flush_on(spdlog::level::level_enum::debug); //设置全局的日志输出等级 -- 无所谓 --每个日志器可以独立进行设置 spdlog::set_level(spdlog::level::level_enum::debug); //创建同步日志器(标准输出/文件) -- 工厂接口默认创建的就是同步日志器 auto logger = spdlog::stdout_color_mt("default-logger"); //设置日志器的刷新策略,以及设置日志器的输出等级 // logger->flush_on(spdlog::level::level_enum::debug); // logger->set_level(spdlog::level::level_enum::debug); //设置日志输出格式 logger->set_pattern("[%H:%M:%S][%t][%-8l] %v"); //进行简单的日志输出 logger->trace("你好! {}", "小明"); logger->debug("你好! {}", "小明"); logger->info("你好! {}", "小明"); logger->warn("你好! {}", "小明"); logger->error("你好! {}", "小明"); logger->critical("你好! {}", "小明"); std::cout << "日志输出演示完毕!\n"; } /* 编译提示 * 本代码依赖 spdlog 和 fmt 库,编译时请务必添加链接参数: * g++ your_file.cpp -o your_app -lspdlog -lfmt * 注意:链接顺序不能反,-lspdlog 必须写在 -lfmt 前面。 -lspdlog:编译器会去寻找 libspdlog.so 或 libspdlog.a(这就是你写日志用的库)。 -lfmt:编译器会去寻找 libfmt.so 或 libfmt.a(这是格式化字符串的库,spdlog 底层强依赖它)。/*#include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/async.h> #include <iostream> int main() { //设置全局的刷新策略 //每秒刷新 spdlog::flush_every(std::chrono::seconds(1)); spdlog::flush_on(spdlog::level::level_enum::debug); spdlog::set_level(spdlog::level::level_enum::debug); //初始化异步日志输出线程配置 //void init_thread_pool(size_t q_size, size_t thread_count) spdlog::init_thread_pool(3072, 1); //创建异步日志器(标准输出/文件) -- 工厂接口默认创建的就是同步日志器 auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("async-logger"); //设置日志输出格式 logger->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v"); //进行简单的日志输出 logger->trace("你好!{}", "小明"); logger->debug("你好!{}", "小明"); logger->info("你好!{}", "小明"); logger->warn("你好!{}", "小明"); logger->error("你好!{}", "小明"); logger->critical("你好!{}", "小明"); std::cout << "日志输出演示完毕!\n"; return 0; }4.3 Spdlog 日志组件的二次封装
为什么要二次封装?
避免单例的锁冲突,因此直接创建全局的线程安全的日志器进行使用
因为日志输出没有文件名行号,因此使用宏进行二次封装输出日志的文件名和行号
封装出一个初始化接口,便于使用:调试模式则输出到标准输出,否则输出到文件中
封装思想:
封装出一个全局接口,用户进行日志器的创建与初始化:
初始化接口接收一个参数:运行模式-bool
初始化接口接收一个参数:输出文件名 -- 用于发布模式
初始化接口接收一个参数:输出日志等级 -- 用于发布模式
对日志输出的接口,进行宏的封装,加入文件名行号的输出
#include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/async.h> #include <iostream> // mode - 运行模式: true-发布模式; false调试模式 std::shared_ptr<spdlog::logger> g_default_logger; void init_logger(bool mode, const std::string &file, int32_t level) { if (mode == false) { //如果是调试模式,则创建标准输出日志器,输出等级为最低 g_default_logger = spdlog::stdout_color_mt("default-logger"); g_default_logger->set_level(spdlog::level::level_enum::trace); g_default_logger->flush_on(spdlog::level::level_enum::trace); }else { //否则是发布模式,则创建文件输出日志器,输出等级根据参数而定 g_default_logger = spdlog::basic_logger_mt("default-logger", file); g_default_logger->set_level((spdlog::level::level_enum)level); g_default_logger->flush_on((spdlog::level::level_enum)level); } g_default_logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v"); } #define LOG_TRACE(format, ...) g_default_logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_DEBUG(format, ...) g_default_logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_INFO(format, ...) g_default_logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_WARN(format, ...) g_default_logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_ERROR(format, ...) g_default_logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_FATAL(format, ...) g_default_logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)#include "logger.hpp" #include <gflags/gflags.h> DEFINE_bool(run_mode, false, "程序的运行模式,false-调试; true-发布;"); DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件"); DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级"); int main(int argc, char *argv[]) { google::ParseCommandLineFlags(&argc, &argv, true); init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level); LOG_DEBUG("你好:{}", "比特"); LOG_INFO("你好:{}", "比特"); LOG_WARN("你好:{}", "比特"); LOG_ERROR("你好:{}", "比特"); LOG_FATAL("你好:{}", "比特"); LOG_DEBUG("这是一个测试"); return -1; }