note

注意参考==各类库的编译.md==

这篇文章对整体的库有一个整理,有需求时可以去参考一下。

01. fmt 让输出带格式的库

fmt是一个c++格式化的库,挺好用的,也比较简单,源码下载下来,轻松就能编译出来,然后在测试时,直接用cmake中的find_package没搞定,就直接添加的搜索路径: CMakeLists.txt:

cmake_minimum_required(VERSION 3.1)
project(demo)
set(CMAKE_BUILD_TYPE Release)

include_directories(/opt/fmt-9.0.0/include)
link_directories(/opt/fmt-9.0.0/lib64)

add_executable(demo main.cpp)
target_link_libraries(demo fmt)

demo.cpp:(这个在它的官网里也是有的)

#include <string>
#include <vector>
#include <fmt/core.h>
#include <fmt/chrono.h>
#include <fmt/ranges.h>
#include <fmt/color.h>

int main() {
	fmt::print("hello world\n");

	std::string s = fmt::format("The answer is {}.\n", 42);
	fmt::print(s);

	// 1.格式化时间
	using namespace std::literals::chrono_literals;
	fmt::print("Default format: {} {} \n", 42s, 100ms);
	fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);

	// 2.直接打印vector
	std::vector<int> v = { 1, 2, 3 };
	fmt::print("{}\n", v);
    
    // 
    fmt::print("Hello, {}!", "world");  // 类 Python 的语法风格
	fmt::printf("Hello, %s!", "world"); 

	// 3.带颜色、格式的输出
	fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
		"Hello, {}!\n", "world");
	fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
		fmt::emphasis::underline, "Hello, {}!\n", "мир");
	fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
		"Hello, {}!\n", "世界");

	return 0;
}

02. Eigen 矩阵运算

gitlab开源地址。到这个页面去下载一个版本。

使用方式:

git clone https://gitlab.com/libeigen/eigen –branch 3.4

mkdir eigen_build && cd eigen_build

cmake -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda/ ..

make && make install # 这是安装在系统默认位置,这个库就默认位置用吧,版本影响不会太大,就用3.4最新的。

注:cmake时这样指定使用cuda,才比较好,主要是在编译三维重建项目“openMVS”时,没这样指定,make编译出了很多问题,比如这样“no suitable constructor exists to convert from “float” to “Eigen::half””的错误,像上面指定了cuda路径的才不报错。

常用头文件:它一些矩阵常用性质和方法,看这

Module Header file Contents
Core #include <Eigen/Core> Matrix和Array类,基础的线性代数运算和数组操作
Geometry #include <Eigen/Geometry> 旋转、平移、缩放、2维和3维的各种变换
LU #include <Eigen/LU> 求逆,行列式,LU分解
Cholesky #include <Eigen/Cholesky> 豪斯霍尔德变换,用于线性代数运算
SVD #include <Eigen/SVD> SVD分解
QR #include <Eigen/QR> QR分解
Eigenvalues #include <Eigen/Eigenvalues> 特征值,特诊向量分解
Sparse #include <Eigen/Spare> 稀疏矩阵的存储和一些基本的线性运算
稠密矩阵 #include <Eigen/Dense> 包含了Core/Geometry/LU/Cholesky/SVDIQR/Eigenvalues模块
矩阵 #include <Eigen/Eigen> 包含了Dense和Sparse(整合库)

使用demo:

#include <iostream>
#include <cmath>
#include <Eigen/Core>
/*
官方的关于vector和matrix的文档:
https://eigen.tuxfamily.org/dox/group__TutorialMatrixArithmetic.html
*/
int main() {
	//std::cout << std::sin(30.0 / 180.0*acos(-1)) << std::endl;
	//Eigen::Vector3f v(1.0f, 2.0f, 3.0f);
	
	//Eigen::Matrix3f i, j;
	//i << 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0;   // 注意只能这种初始化方式
	//j << 2.0, 3.0, 1.0, 4.0, 6.0, 5.0, 9.0, 7.0, 8.0;
	// 或者 MatrixXf a(2,3); a << 1, 2, 3, 4, 5, 6;

	// 题:将点p逆时针旋转45°
	Eigen::Vector3f p(2.0f, 1.0f, 1.0f);  // 默认打印出来形式是列向量,但运算时还是看作1行2列

	// 逆时针旋转45°
	float angle = 45.f / 180.f * std::acos(-1);  // std::acos(-1)为π,这是三角函数必须给弧度制
	// 方式一:原理 https://blog.csdn.net/whocarea/article/details/85706464
	Eigen::Vector2f out;
	out[0] = p[0] * std::cos(angle) - p[1] * std::sin(angle);
	out[1] = p[0] * std::sin(angle) + p[1] * std::cos(angle);
	std::cout << out << std::endl;

	// 方式二:其实是一样的,就是将上面操作弄成了一个矩阵  (这个额外加了一点,使用了齐次坐标)
	Eigen::Matrix3f mat;
	mat << std::cos(angle), -std::sin(angle), 1.f, std::sin(angle), std::cos(angle), 2.f, 0.f, 0.f, 1.f;
	std::cout << mat * p << std::endl;  // 这顺序不能变,就是对上面列向量使用的的进一步说明
}

上面是直接按照固定的把旋转矩阵写出来,不好写,按照下面这样来:平移、缩放、旋转

#include <iostream>
#include <Eigen/Core>
#include <Eigen/Geometry>

int main() {
	// 1、定义一个4*4的单位矩阵
	Eigen::Matrix4f matrix = Eigen::Matrix4f::Identity();
	// 2、仿射变换,需要头文件<Eigen/Geometry>
	Eigen::Affine3f trans = Eigen::Affine3f::Identity();   
	// (2.1)在X轴上定义一个2.5米的平移
	trans.translation() << 2.5, 0.0, 0.0;   
    // (2.1)要缩放的话,可以
    trans.scale(0.5);  // 所有轴整体缩放,每个轴不同的值还不知道
	// (2.2)在Z轴上旋转45度;X轴的话就是Eigen::Vector3f::UnitX();
	trans.rotate(Eigen::AngleAxisf(45, Eigen::Vector3f::UnitZ()));
	// (2.3)得到旋转矩阵
	matrix = trans * matrix;

	std::cout << matrix << std::endl;
	system("pause");
	return 0;
}

注:2.2中,可以绕几个轴旋转,括号里就这么写: Eigen::AngleAxisf(45, Eigen::Vector3f::UnitZ()) * Eigen::AngleAxisf(60, Eigen::Vector3f::UnitX())


给元素开方:

#include <iostream>
#include <Eigen/Dense>
int main(int argc, char* argv[]) {
	Eigen::MatrixXd bigMat(1000, 1000);   // 注意这种Xd、Xf这种写法

	Eigen::Matrix3d mat;
	mat << 4, 9, 16, 25, 36, 49, 64, 81, 100;
    // 注意:使用array()函数将matrix对象转换为array对象,以便使用array的sqrt函数。
	Eigen::Matrix3d res_sqrt = mat.array().sqrt();
	std::cout << "Square root of the matrix:\n" << res_sqrt << std::endl;
	return 0;
}

03. json

c++的json库,有几个,用的时候看情况吧:

04. spdlog 日志库

spdlog。(源码练习吧)c++的日志库,非常建议上手,star也非常多;demo在其readme中写得非常明白了。

另外一个c编写的,放这吧:EasyLogger,一款超轻量级(ROM<1.6K, RAM<0.3k)、高性能的 C/C++ 日志库

下面说一下spdlog日志库,按照其默认的方式来,可以以头文件的形式,一般也默认编译成了静态库来使用。

它还支持每天保存一个独立的日志文件,也可以设置每个文件最大多少,最多存多少个。

以上是最简单快捷的使用方法,直接导入头文件,然后终端输出的方式。

下面这是将其存进日志文件,或是同时终端输出:

#include <spdlog/spdlog.h>
#include <spdlog/sinks/daily_file_sink.h>           // 每天一个日志文件需要
#include <spdlog/sinks/rotating_file_sink.h>        // 文件大小到一定程度切割成新文件需要
#include <spdlog/sinks/stdout_color_sinks.h>        // 彩色输出到终端需要

// 宏里面直接执行了两次日志输出,一次是默认的logger输出(下面函数中将到控制台的输出设为默认的)
// 一次是到日志文件中的输出
#define DEBUG(...) SPDLOG_LOGGER_DEBUG(spdlog::default_logger_raw(), __VA_ARGS__);SPDLOG_LOGGER_DEBUG(spdlog::get("daily_logger"), __VA_ARGS__)
#define LOG(...) SPDLOG_LOGGER_INFO(spdlog::default_logger_raw(), __VA_ARGS__);SPDLOG_LOGGER_INFO(spdlog::get("daily_logger"), __VA_ARGS__)
#define WARN(...) SPDLOG_LOGGER_WARN(spdlog::default_logger_raw(), __VA_ARGS__);SPDLOG_LOGGER_WARN(spdlog::get("daily_logger"), __VA_ARGS__)
#define ERROR(...) SPDLOG_LOGGER_ERROR(spdlog::default_logger_raw(), __VA_ARGS__);SPDLOG_LOGGER_ERROR(spdlog::get("daily_logger"), __VA_ARGS__)


void register_logger() {
    // 这里是为了说明类型,和其它的使用方式(“daily_logger”是我们自己定义的一个名字,后续好获取的)
    // std::shared_ptr<spdlog::logger> logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 0, 0);
    // spdlog::get("daily_logger")->info("hello");
    // logger->info("world");

    // 1、按文件大小的logger(以字节为单位,下面代表是单个文件最大100M,到了后创建新文件,最多创建3个,满了后会删除旧的日志文件)(我一般就用这个吧)
    //auto file_logger = spdlog::rotating_logger_mt("file_log", "log/log.log", 1024 * 1024 * 100, 3);

    // 2、每天2:30 am 新建一个日志文件的logger(会自己创建logs/V01/这个路径,如果不存在)
    // 注:这种不能指定多少天自己删除旧日志,只能外部脚本处理
    auto logger = spdlog::daily_logger_mt("daily_logger", "logs/V01/daily.txt", 2, 30);
    // 遇到warn flush日志,防止丢失
    logger->flush_on(spdlog::level::warn);
    //每三秒刷新一次(不是一直往硬盘中写,提高效率)
    spdlog::flush_every(std::chrono::seconds(3));
    
    // 3、设置终端输出的logger
    // 其它地方:spdlog::get("daily_logger")也是能获取到这个终端输出的对象的
    auto console = spdlog::stdout_color_mt("console");
    // 这是设置这么多生成的logger哪个为默认的,即不用指定名字就能调用的
    spdlog::set_default_logger(console);
    spdlog::set_level(spdlog::level::debug);  // Set global log level to debug,非必须

    // 感觉就用默认吧,只是知道可以这么改,这太冗余了
    spdlog::warn("这是更改格式前的输出");
    // change log pattern
    // %s:文件名
    // %#:行号
    // %!:函数名
    // %e:毫秒
    // spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e [%l] [%t] - <%s>|<%#>|<%!>,%v");

    // 这是宏的实现方式,前面几行也写了非宏的实现方式
    LOG("test info");
    ERROR("test error");
    spdlog::warn("这是更改格式后的输出");
}

int main(int argc, char *argv[]) {
    // 设置日志格式
    register_logger();
    
    LOG("这是:{}", "hello world");
    // 这是这样做的默认结果:
    // [2025-01-22 10:37:20.746] [file_log] [info] [main.cpp:55] 这是:hello world
	
    // 注意:上面这样自己创建了logger对象,并将其设置为默认的日志对象后,其它任何文件只要导入spdlogde的头文件,那默认的就是自己设置好的
    spdlog::info("hello");   // 因为这函数点进去也是调用的默认日志对象
    
    // Release and close all loggers
    spdlog::drop_all();

    return 0;
}

注:这个日志库使用了单例模式,在这里一次注册后,其它文件导入后就直接使用了。

05. indicators 进度条库

indicators:一个c++编写的,用于c++的进度条库,可以是单文件的使用,很方便。直接去看它的README,gif图片样例给的非常生动,以后尽可能都搞一下吧。

还有其它的例子(github网络不好不一定看得到,下下来就好),这里在写一个它README的例子吧:

06. taskflow 高效的并发

taskflow:一个 C++ 头文件库,让你以简单的几行代码就可以实现高效的并发。示例代码如下:

​ The following program (simple.cpp) creates four tasks A, B, C, and D, where A runs before B and C, and D runs after B and C. When A finishes, B and C can run in parallel.

#include <taskflow/taskflow.hpp>  // Taskflow is header-only

int main(){
  
  tf::Executor executor;
  tf::Taskflow taskflow;

  auto [A, B, C, D] = taskflow.emplace(  // create four tasks
    [] () { std::cout << "TaskA\n"; },
    [] () { std::cout << "TaskB\n"; },
    [] () { std::cout << "TaskC\n"; },
    [] () { std::cout << "TaskD\n"; } 
  );                                  
                                      
  A.precede(B, C);  // A runs before B and C
  D.succeed(B, C);  // D runs after  B and C
                                      
  executor.run(taskflow).wait(); 

  return 0;
}

07. dbg-macro 宏debug

dbg-macro:这个项目里就一个头文件,拿来就能用,debug时打日志、变量非常好用,除基本信息外,还输出变量名和类型。以后运行要看某处数据可以试试。

下面的是官方README中的示例:(注释我是用了上面操作,官方的是dbg)

#include <gdb.h>

// You can use "gdb(..)" in expressions:
int my_func(int n) {
    if (gdb(n <= 1)) {
        return gdb(1);
    }
    else {
        return gdb(n * my_func(n - 1));
    }
}

int main() {
    // 1、
    const int a = 2;
    const int b = gdb(3 * a) + 1;  // [example.cpp:18 (main)] 3 * a = 6 (int32_t)

    // 2、
    std::vector<int> numbers{ b, 13, 42 };
    gdb(numbers);  // [example.cpp:21 (main)] numbers = {7, 13, 42} (std::vector<int32_t>)

    // 3、在一个表达式中
    my_func(4);  

    // 4、获取当前时间(比较直接简单,获取时间戳和用时还是用笔记里另外的)
    gdb(gdb::time());

    // 5、多个目标(像中间中记得用括号括起来)
    gdb(42, (std::vector<int>{2, 3, 4}), "hello", false);

    return 0;
}

进阶:Printing type names

dbg(…) already prints the type for each value in parenthesis (see screenshot above). But sometimes you just want to print a type (maybe because you don’t have a value for that type). In this case, you can use the dbg::type<T>() helper to pretty-print a given type T. For example:

template <typename T>
void my_function_template() {
  using MyDependentType = typename std::remove_reference<T>::type&&;
  dbg(dbg::type<MyDependentType>());
}

08. ThreadPool 线程池

ThreadPool:一个简单的 C++11 线程池实现,就两个文件,非常简单易用。

09. 读取 CSV 文件库

fast-cpp-csv-parser:cvs解析的库,就一个头文件非常简单。

10. args 参数解析库

args:一个简单的header-only文件(args.hxx)的c++参数解析器库。应该是灵活和强大的,并试图与Python标准argparse库的功能兼容,简单看demo,用法和python那个很相似,但不完全相同这种传参,一般不给–input这样的前缀。

​ 就英伟达的instant-ngp项目中就是用的这个库做的参数的处理,可以学习。

C++小知识中,19点,格式化接受的参数中还讲到了用 opencv来格式化接收的参数。

先放这里,如果没有用到opencv,就使用这种方式,如果用到opencv,就采用上一行c++小知识中的opencv接受参数的形式。

#include <args.hxx>

int parse_args(int argc, char* argv[], std::string &source_name, std::string &config_path) {
    args::ArgumentParser parser("这是传递受电弓参数文件");
    args::HelpFlag help(parser, "help", "DIsplay this help menu", {'h', "help"});

    args::Positional<std::string> _source_name(parser, "source_name", "项点名称");
    args::Positional<std::string> _config_path(parser, "config_path", "配置文件路径");

    try {
        parser.ParseCLI(argc, argv);
    }
    catch (args::Help) {
        // 这可以直接打印是因为 args::ArgumentParser这个类去重载了 << 
        // 想要用 spdlog::info()打印的话,可借助 std::ostringstream
        std::cout << parser;
        return 0;
    }
    catch (args::ParseError e) {
        std::cerr << e.what() << std::endl;
        std::cerr << parser;
        return -1;
    }
    catch (args::ValidationError e) {
        std::cout << e.what() << std::endl;;
        std::cerr << parser;
        return -1;
    }
    
    if (!_source_name) {
        std::cout << "必须指定项点名称" <<std::endl;
        return -1;
    }
    if (!_config_path) {
        std::cout << "必须指定配置的yaml文件路径" <<std::endl;
        return -1;
    }

    source_name = args::get(_source_name);
    config_path = args::get(_config_path);
    // 参数传递后,特别是给的文件的,一定要判断一下文件在不在,c++小知识19点里写到有
    return 1;
}

int main(int argc, char* argv[]) {
    std::string source_name;
    std::string config_path;
    if (parse_args(argc, argv, source_name, config_path) != 1) {
        return 0;
    }
    return 0;
}
/*
	- 使用方法:
		查看帮助:./main -h  或是 ./main --help    # 上面{'h', "help"}决定的可以这么写
		位置传参:./main  V02  ./config/default_config.yaml       # 一定要按照这个位置顺序去写,位置决定的(暂时只能这种)
    
    - 使用opencv的类cv::CommandLineParser,可以实现:
    	./main  --source_name=V02  --yaml_path=./config/default_config.yaml
*/

是否启用这种bool值可以这么传递(结合上面一起看)

11. cpp-httplib

cpp-httplib:一个文件的 C++ HTTP/HTTPS 库。这是一个用 C++11 写的仅头文件、跨平台的 HTTP/HTTPS 服务器端和客户端库,使用时十分方便,只需在代码中引入 httplib.h 文件。快速使用还不错,搜狗的workflow也还可以。

最简单的Demo:(有需求时,先去看它的README,写得比较详细)

CppNet

CppNet:这个国人学习中写的,感觉还不错,文档很全,中文支持不错,先放这里。

crow

crowCrow:这是受flask启发开发的库,用起来跟flak比较相近,后面有需要,先试试看看这个库。

12. Catch2 用例测试库

测试库还有googletest,暂时还没用过,或者doctest这个单文件测试项目。

Catch2:这是一个用例测试库,使用起来非常方便,是在学习inifile-cpp这个ini文件解析库时看到的,要使用的话,就按照ini解析库中的用例去使用,也会知道该怎么写catch2的相应的CmakeLists.txt.

下面是inifile-cpp中的简单测试示例:全是用TEST_CASE宏包裹起来的测试case,主函数是另一个文件定义起来的,具体看ini这个项目。

#include "inicpp.h"
#include <catch2/catch.hpp>
#include <cstring>
#include <sstream>

TEST_CASE("parse ini file", "IniFile") {
    std::istringstream ss(("[Foo]\nbar=hello world\n[Test]"));
    ini::IniFile inif(ss);

    REQUIRE(inif.size() == 2);
    REQUIRE(inif["Foo"]["bar"].as<std::string>() == "hello world");
    REQUIRE(inif["Test"].size() == 0);
}

TEST_CASE("test the error", "the func()") {
	/*
	假设外部有一个函数要测试,里面有throw的代码,就要测试看是否会触发
	#include <stdexcept>
	int func(int a, int b) {
    if (b == 0)
        throw std::logic_error("divided is zero!");  # 需要这个头文件
    return a / b;
	}
	*/    
    REQUIRE_THROWS(func(5, 0));  // 这就会测试通过
    REQUIRE_THROWS(func(5, 1));  // 这不会触发func中的异常,测试就不会通过
}

13. 队列

concurrentqueue:无锁队列,看起来还可以,好像是多线程的队列这种,可带锁不带锁,单文件使用。

readerwriterqueue:也是上面作者写的,单生产者,单消费者的无锁队列。

14. yaml-cpp

yaml-cpp这是用来处理yaml文件的。

注:linux下,当静态库libyaml-cpp.a与动态库libyaml-cpp.so同时存在于同一路径中,使用target_link_libraries(main yaml-cpp),会优先选择动态库,动态库不满足时,就会再选用静态库,如果想优先选择静态库,可在链接时把库名写完整为yaml-cpp.a

对应的CMakeLists.txt