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
参考来源: