C++ 的Eigen 库全解析

Eigen 是 C++ 世界里线性代数领域的"瑞士军刀"------纯头文件、零依赖、高性能,从简单的向量运算到复杂的矩阵分解,它几乎覆盖了数值计算的全部日常需求。无论你是做量化金融、机器学习、计算机图形学还是科学仿真,Eigen 都是绕不开的利器。


一、Eigen 是什么?

Eigen 是一个开源的 C++ 模板库,专门用于线性代数运算,包括矩阵、向量、数值求解器以及相关算法。它的核心特点是:

  • 纯头文件 :不需要编译成 .so.lib#include 即用
  • 表达式模板(Expression Templates) :利用惰性求值(lazy evaluation)避免不必要的临时对象,性能极佳
  • 同时支持稠密矩阵与稀疏矩阵:覆盖绝大多数工程场景
  • 丰富的矩阵分解:LU、Cholesky、QR、SVD 等应有尽有
  • 行优先/列优先存储均可配置

相比自己手写矩阵类,Eigen 经过了大量"战场检验",bug 少、社区活跃、API 直观,是生产环境的不二选择。


二、安装与配置

Eigen 的安装是所有 C++ 库里最简单的之一------只需要下载头文件

方法一:直接下载

eigen.tuxfamily.org 下载源码包,解压后将 Eigen/ 目录放到你的项目中或系统 include 路径下。

方法二:包管理器

bash 复制代码
# Ubuntu/Debian
sudo apt install libeigen3-dev

# macOS (Homebrew)
brew install eigen

方法三:Git 子模块(推荐用于项目管理)

csharp 复制代码
git submodule add https://gitlab.com/libeigen/eigen.git
git submodule update --init --recursive

编译时只需加 -I 指定头文件路径:

css 复制代码
g++ -I /path/to/eigen/ my_program.cpp -o my_program

不需要链接任何库文件,这一点非常省心。


三、核心概念:Matrix 类型系统

Eigen 的一切都建立在 Matrix<Scalar, Rows, Cols> 这个模板类上。理解它的命名规则,是入门的第一步。

3.1 类型命名规则

Eigen 提供了大量预定义的便捷类型别名,规律如下:

类型名 含义
Matrix3f 3×3 的 float 矩阵
Matrix4d 4×4 的 double 矩阵
MatrixXd 动态大小的 double 矩阵(运行时确定)
Vector3f 3维 float 列向量
VectorXd 动态大小的 double 列向量
RowVector3d 3维 double 行向量

后缀规则:数字代表固定尺寸,X 代表动态,f/d/i/cf/cd 分别对应 float/double/int/复数float/复数double。

3.2 固定尺寸 vs 动态尺寸

arduino 复制代码
// 固定尺寸:编译期已知,分配在栈上,速度更快
Eigen::Matrix3f A;
Eigen::Vector4d v;

// 动态尺寸:运行时确定,分配在堆上,更灵活
Eigen::MatrixXd M(100, 100);
Eigen::VectorXd u(50);

// 自定义任意尺寸
Eigen::Matrix<float, 20, 75> M2;

一般建议:尺寸小且固定时用固定类型 (性能更好),尺寸大或不确定时用动态类型


四、矩阵的初始化

4.1 逗号初始化

css 复制代码
Eigen::Matrix3d A;
A << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;

这种语法非常直观,<< 运算符按行填充元素。

4.2 特殊矩阵

rust 复制代码
// 零矩阵
Eigen::MatrixXd Z = Eigen::MatrixXd::Zero(3, 3);

// 全1矩阵
Eigen::MatrixXd O = Eigen::MatrixXd::Ones(3, 3);

// 单位矩阵
Eigen::Matrix3d I = Eigen::Matrix3d::Identity();

// 随机矩阵(元素在 [-1, 1] 之间)
Eigen::MatrixXd R = Eigen::MatrixXd::Random(3, 3);

// 常数矩阵
Eigen::MatrixXd C = Eigen::MatrixXd::Constant(3, 3, 6.28);

4.3 元素访问

scss 复制代码
Eigen::MatrixXd m(2, 2);
m(0, 0) = 3.0;   // 用圆括号,注意是0-indexed
m(1, 0) = 2.5;
double val = m(0, 1);

// 向量可以用单下标
Eigen::VectorXd v(3);
v(0) = 1.0;
v(1) = 2.0;
v(2) = 3.0;

五、基本运算

5.1 加减法与标量运算

ini 复制代码
Eigen::Matrix2d a, b;
a << 1, 2, 3, 4;
b << 2, 3, 1, 4;

auto c = a + b;      // 矩阵加法
auto d = a - b;      // 矩阵减法
a += b;              // 原地加法

auto e = a * 2.5;    // 标量乘法
auto f = 0.1 * b;    // 标量乘法(交换律)
a *= 2;              // 原地标量乘法

加减法要求两个矩阵形状完全一致 ,且 Eigen 不做自动类型提升float 矩阵和 double 矩阵不能直接相加。

5.2 矩阵乘法

ini 复制代码
Eigen::MatrixXd A(3, 3), B(3, 3);
// ... 初始化 ...

auto C = A * B;      // 矩阵乘法(不是逐元素!)
auto v_out = A * v;  // 矩阵-向量乘法

5.3 转置与共轭

ini 复制代码
Eigen::MatrixXd A(3, 3);
auto AT = A.transpose();         // 转置
auto AC = A.conjugate();         // 共轭(实数矩阵等同于自身)
auto AH = A.adjoint();           // 共轭转置(Hermitian)

// 注意!不能原地转置:A = A.transpose() 是错误的!
A.transposeInPlace();            // 正确的原地转置方式

5.4 点积与叉积

ini 复制代码
Eigen::Vector3d u(1, 2, 3), v(4, 5, 6);

double dot_product = u.dot(v);       // 点积:标量
Eigen::Vector3d cross = u.cross(v);  // 叉积:仅限3维向量
double norm = u.norm();              // L2范数
double sq_norm = u.squaredNorm();    // 范数的平方

六、实用操作:块操作与切片

Eigen 提供了强大的子矩阵访问能力,这在实际工程中极为常用。

scss 复制代码
Eigen::MatrixXd M(4, 4);
// ... 初始化 ...

// 提取子矩阵:block(起始行, 起始列, 行数, 列数)
auto sub = M.block(1, 1, 2, 2);   // 从(1,1)开始的2×2子矩阵

// 提取行/列
auto row1 = M.row(0);             // 第0行
auto col2 = M.col(2);             // 第2列

// 提取角落
auto top_left  = M.topLeftCorner(2, 2);
auto bot_right = M.bottomRightCorner(2, 2);

// 向量的头尾
Eigen::VectorXd v(6);
auto head = v.head(3);    // 前3个元素
auto tail = v.tail(3);    // 后3个元素
auto seg  = v.segment(1, 4); // 从index 1开始的4个元素

七、矩阵分解与线性方程求解

这是 Eigen 最强大的部分之一。求解线性方程组 Ax = b 有多种分解方式,精度和速度各有侧重。

7.1 常用分解方法对比

分解方法 适用场景 速度 精度
PartialPivLU 方阵,通用
FullPivLU 方阵,需要秩信息 较慢 极好
LLT (Cholesky) 对称正定矩阵 最快
LDLT 对称半正定矩阵
HouseholderQR 最小二乘,通用 中等
JacobiSVD 最小二乘,需奇异值 最好

7.2 代码示例

ini 复制代码
#include <Eigen/Dense>

Eigen::Matrix3d A;
Eigen::Vector3d b;
A << 2, 1, -1,
    -3, -1, 2,
    -2, 1, 2;
b << 8, -11, -3;

// 方法1:LU 分解(通用方阵)
Eigen::Vector3d x1 = A.lu().solve(b);

// 方法2:Cholesky(对称正定矩阵,最快)
// Eigen::Vector3d x2 = A.llt().solve(b);

// 方法3:QR 分解(最小二乘问题)
Eigen::Vector3d x3 = A.colPivHouseholderQr().solve(b);

// 方法4:直接求逆(小矩阵可用,大矩阵不推荐)
Eigen::Matrix3d A_inv = A.inverse();

// 行列式
double det = A.determinant();

八、Array 类:逐元素运算

Eigen 区分了 Matrix(线性代数语义)和 Array(逐元素运算语义),这个设计非常精妙。

ini 复制代码
Eigen::ArrayXXd a(3, 3), b(3, 3);
// ... 初始化 ...

auto c = a * b;          // 逐元素乘法(不是矩阵乘法!)
auto d = a / b;          // 逐元素除法
auto e = a.sqrt();       // 逐元素开方
auto f = a.exp();        // 逐元素指数
auto g = a.pow(2.0);     // 逐元素幂次
auto h = (a > 0.5);      // 逐元素比较,返回 bool Array

// Matrix 和 Array 之间的转换
Eigen::MatrixXd M = a.matrix();   // Array → Matrix
Eigen::ArrayXXd A2 = M.array();   // Matrix → Array

一个常见的混合用法:先做矩阵乘法,再对结果逐元素应用激活函数(比如 ReLU):

css 复制代码
Eigen::MatrixXd result = (W * x).array().max(0.0).matrix();

九、一个完整的入门示例

把前面所有知识串联起来,下面是一个完整可运行的程序:

c 复制代码
#include <iostream>
#include <Eigen/Dense>

int main() {
    // 1. 创建矩阵
    Eigen::MatrixXd A = Eigen::MatrixXd::Random(3, 3);
    A = A.transpose() * A;  // 构造对称正定矩阵

    Eigen::VectorXd b = Eigen::VectorXd::Random(3);

    std::cout << "矩阵 A:\n" << A << "\n\n";
    std::cout << "向量 b:\n" << b << "\n\n";

    // 2. 求解线性方程组 Ax = b
    Eigen::VectorXd x = A.llt().solve(b);
    std::cout << "解 x (Cholesky):\n" << x << "\n\n";

    // 3. 验证:计算残差
    double residual = (A * x - b).norm();
    std::cout << "残差 ||Ax - b|| = " << residual << "\n\n";

    // 4. 特征值分解
    Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver(A);
    std::cout << "特征值:\n" << solver.eigenvalues() << "\n\n";
    std::cout << "特征向量:\n" << solver.eigenvectors() << "\n";

    return 0;
}

编译运行:

css 复制代码
g++ -O2 -I /path/to/eigen/ main.cpp -o demo && ./demo

十、进阶方向

掌握了上面的基础之后,Eigen 还有几个值得深入的方向:

  • 稀疏矩阵模块Eigen/Sparse):适合有限元、图算法等大规模稀疏问题,提供 SparseMatrix<double> 类型和配套的稀疏求解器
  • 几何模块Eigen/Geometry):四元数、旋转矩阵、仿射变换,是机器人和图形学的标配
  • 与其他库集成:Eigen 可以与 OpenCV、PCL、ROS 无缝对接,很多库内部直接使用 Eigen 类型
  • 性能调优 :开启 -O2-O3 编译优化,配合 SIMD 指令(Eigen 会自动利用 SSE/AVX),性能可以媲美手写 BLAS

参考来源:

相关推荐
卷无止境2 小时前
现代 C++特性大盘点:一门脱胎换骨的老语言
c++·后端
郝学胜_神的一滴3 小时前
CMake 27:缓存变量的特性、语法、类型与实操全解
c++·cmake
博客18002 天前
酷宝的使用方法,超好用的免费界面库,C++、MFC可用
c++·mfc·界面库·库来帮·酷宝
郝学胜_神的一滴2 天前
CMake 026:属性体系精讲、四大作用域全解 & 实战代码落地
c++·cmake
众少成多积小致巨3 天前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
clint4567 天前
C++进阶(1)——前景提要
c++
夜悊7 天前
C++代码示例:进制数简单生成工具
c++
郝学胜_神的一滴7 天前
CMake 021: IF 条件判据详诠
c++·cmake
_wyt0018 天前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp