1. 项目概述:为什么选择Eigen?
如果你正在用C++做数值计算、机器人学、图形学或者机器学习,大概率绕不开线性代数运算。从简单的矩阵乘法到复杂的特征值分解,自己手写这些算法不仅容易出错,而且性能往往惨不忍睹。这时候,一个成熟、高效、易用的线性代数库就成了刚需。市面上选择不少,比如老牌的BLAS/LAPACK、功能强大的Armadillo,还有各种商业库。但今天要聊的Eigen,在我看来,是C++开源线性代数库中一个近乎完美的选择。
我第一次接触Eigen是在做一个机器人运动学仿真的项目里。当时需要频繁地进行3D坐标变换,涉及到大量的4x4齐次变换矩阵的乘法和求逆。一开始图省事,自己用二维数组和循环硬写,代码又长又慢,调试起来更是噩梦。后来同事推荐了Eigen,尝试之后,那种“声明即计算”的简洁和媲美手写汇编的性能,让我彻底被圈粉。它不是一个简单的函数集合,而是一个深度利用C++模板元编程特性的现代库,其设计哲学是“在编译期做尽可能多的事”,从而在保证极高抽象层次的同时,榨干硬件的每一分性能。无论是学术研究还是工业级产品,Eigen都足以胜任。
2. Eigen核心设计哲学与优势解析
2.1 模板元编程:性能的基石
Eigen性能卓越的秘密,很大程度上源于其对C++模板元编程(Template Metaprogramming)的极致运用。这与许多其他库(如早期版本的OpenCV的cv::Mat)在运行时进行类型检查和分支选择截然不同。
举个例子,当你写下MatrixXd C = A * B;(假设A和B都是动态大小的双精度矩阵)时,Eigen并不会立即计算。它首先会生成一个特殊的“乘积表达式模板”对象,这个对象仅仅记录了操作A * B以及操作数的类型、尺寸等信息。这个表达式对象就像一个计算蓝图。只有当这个表达式被赋值给C时(或者在其他需要求值的上下文中),Eigen的模板元编程引擎才会启动。
编译器会根据这个“蓝图”,为A * B这个特定的操作(双精度矩阵乘法)生成高度优化的、几乎无任何冗余判断的机器码。这个过程发生在编译期,它消除了所有运行时的动态分派、尺寸检查和临时对象创建的开销。对于小尺寸固定矩阵(如3x3, 4x4),Eigen甚至能直接将计算展开为一系列寄存器操作,性能堪比手写汇编。
注意:这种“表达式模板”技术也意味着你需要避免一些常见的陷阱。例如,
auto关键字会推导出表达式模板类型,而不是计算结果类型。auto D = A * B;这里的D是一个表达式对象,如果后续A或B的生命周期结束,再使用D就会导致未定义行为。安全的做法是直接赋值给MatrixXd或使用.eval()方法强制求值。
2.2 统一的API与丰富的功能
Eigen的API设计非常优雅和一致。无论是固定大小的矩阵(Matrix3f)还是动态大小的矩阵(MatrixXd),无论是稠密矩阵还是稀疏矩阵,其核心操作(访问元素、加减乘除、切片、转置等)的用法都高度统一。这极大地降低了学习成本和代码维护难度。
在功能层面,Eigen覆盖了绝大多数线性代数需求:
- 稠密矩阵核心运算:加减乘除、标量运算、点乘、叉乘、转置、共轭、求逆等。
- 分解与求解:LU分解、QR分解、特征值分解(EigenSolver)、奇异值分解(SVD)、Cholesky分解(用于正定矩阵)。这些分解是求解线性方程组、最小二乘问题、主成分分析(PCA)等的基础。
- 几何模块:对机器人、图形学极其友好。提供了各种变换的表示(
AngleAxis,Quaternion,Transform)和便捷的交互操作,能轻松处理旋转、平移、缩放。 - 稀疏矩阵模块:高效处理大规模但元素大部分为零的矩阵,支持多种压缩存储格式(如CSR)和迭代法求解器(如共轭梯度法)。
- 插件与兼容性:可以轻松地与STL容器、Boost库交互,也支持导入导出为MATLAB/NumPy格式的文件,方便跨平台、跨语言协作。
3. 从零开始:Eigen的安装与基础使用
3.1 极简安装与环境配置
Eigen是一个纯头文件库(Header-only Library),这是它最令人称道的优点之一。这意味着你不需要编译.so或.dll文件,也不需要复杂的链接步骤。
安装步骤:
- 下载:从Eigen官网下载最新稳定版本(如3.4.0)。
- 解压:将压缩包解压到你喜欢的目录,例如
/usr/local/include/eigen3或C:\Libs\eigen3。 - 配置编译器:告诉你的编译器去哪里找Eigen的头文件。
- GCC/Clang: 使用
-I选项,如-I /usr/local/include/eigen3。 - Visual Studio: 在项目属性 -> C/C++ -> 常规 -> 附加包含目录中,添加Eigen的根目录路径。
- CMake(推荐): 在你的
CMakeLists.txt中,使用find_package(Eigen3 REQUIRED)和target_include_directories(your_target PUBLIC ${EIGEN3_INCLUDE_DIRS})。Eigen提供了官方的CMake支持,这是最规范的方式。
- GCC/Clang: 使用
实操心得:我强烈建议使用CMake进行管理。即使你的项目很小,养成使用CMake的习惯也利于后续依赖管理和跨平台编译。将Eigen源码作为git子模块(git submodule)放在项目
third_party目录下,是另一种在团队项目中保持依赖版本一致性的好方法。
3.2 第一个Eigen程序:矩阵与向量的声明与操作
让我们从一个简单的例子开始,直观感受Eigen的语法。
#include <iostream> #include <Eigen/Dense> // 包含稠密矩阵和数组相关的所有功能 int main() { // 1. 声明与初始化 Eigen::Matrix3f mat_a; // 3x3的float矩阵,未初始化 mat_a << 1, 2, 3, 4, 5, 6, 7, 8, 9; // 逗号初始化,非常直观 Eigen::Vector3d vec_b(1.0, 0.0, 2.0); // 3x1的动态大小double向量,直接构造 Eigen::MatrixXd mat_dyn = Eigen::MatrixXd::Random(5, 3); // 5x3动态矩阵,元素为随机值 // 2. 基础运算 Eigen::Matrix3f mat_c = mat_a * 2.0f; // 标量乘法 Eigen::Vector3f vec_sum = mat_a.col(0) + vec_b.cast<float>(); // 矩阵列与向量相加,注意类型转换 // 3. 访问元素 std::cout << "mat_a(1,2) = " << mat_a(1,2) << std::endl; // 输出 6 (行、列从0开始) std::cout << "vec_b[0] = " << vec_b[0] << std::endl; // 输出 1 // 4. 输出整个矩阵 std::cout << "Matrix mat_a:\n" << mat_a << std::endl; std::cout << "Vector vec_b:\n" << vec_b.transpose() << std::endl; // transpose()使其行输出 return 0; }代码解析与注意事项:
- 命名空间:所有Eigen类和方法都在
Eigen命名空间下。 - 类型命名:
Matrix是核心类模板。Matrix3f是Matrix<float, 3, 3>的别名,代表3x3的float矩阵。Vector3d是Matrix<double, 3, 1>的别名。MatrixXd中的X表示动态大小(Dynamic)。 - 逗号初始化:
<<操作符用于对小矩阵/向量进行顺序初始化,非常方便。 - 元素访问:使用圆括号
(i, j)访问矩阵元素,使用方括号[i]或圆括号(i)访问向量元素。索引从0开始。 - 类型安全:Eigen有严格的类型检查。直接对
Matrix3f和Vector3d进行运算是错误的,需要显式使用.cast<float>()或.cast<double>()进行类型转换。
4. 核心操作详解:超越基础运算
4.1 矩阵分解与线性方程组求解
求解线性方程组Ax = b是科学计算中最常见的问题之一。Eigen提供了多种分解方式,对应不同的矩阵特性和精度/速度需求。
#include <Eigen/Dense> #include <iostream> int main() { // 构造一个正定矩阵A和向量b Eigen::Matrix3d A; A << 4, -1, 0, -1, 4, -1, 0, -1, 4; Eigen::Vector3d b(1, 2, 3); // 方法1:直接求逆 (不推荐,效率低且数值不稳定) // Eigen::Vector3d x = A.inverse() * b; // 方法2:LU分解 (适用于一般方阵) Eigen::Vector3d x_lu = A.lu().solve(b); // PartialPivLU std::cout << "Solution via LU: " << x_lu.transpose() << std::endl; // 方法3:QR分解 (适用于超定或欠定方程组,最小二乘解) Eigen::Vector3d x_qr = A.householderQr().solve(b); std::cout << "Solution via QR: " << x_qr.transpose() << std::endl; // 方法4:LLT分解 (适用于对称正定矩阵,速度最快) Eigen::Vector3d x_llt = A.llt().solve(b); std::cout << "Solution via LLT: " << x_llt.transpose() << std::endl; // 验证结果 std::cout << "Residual (A*x - b) norm: " << (A * x_llt - b).norm() << std::endl; return 0; }选择分解方式的指南:
| 分解类型 | 适用矩阵 | 特点 | 速度 |
|---|---|---|---|
| PartialPivLU | 一般可逆方阵 | 最通用,稳定性较好 | 快 |
| FullPivLU | 任意矩阵(可判断秩) | 数值最稳定,但速度慢 | 慢 |
| HouseholderQR | 任意矩阵(行>=列) | 求最小二乘解 | 中等 |
| ColPivHouseholderQR | 任意矩阵 | 更好的数值稳定性 | 比HouseholderQR稍慢 |
| LLT | 对称正定矩阵 | 速度最快 | 非常快 |
| LDLT | 对称半正定或不定矩阵 | 避免开方,更稳定 | 快 |
重要提示:对于小矩阵(如4x4及以下),直接使用
A.inverse() * b有时可能因为编译器优化而和分解法速度相差无几,但对于大矩阵或需要重复求解不同b的情况(分解只需一次),分解法的优势是压倒性的。永远优先考虑分解法,而不是直接求逆。
4.2 几何模块:处理旋转与变换
在机器人、SLAM、三维视觉中,几何变换无处不在。Eigen的Geometry模块让这些操作变得异常简洁。
#include <Eigen/Dense> #include <Eigen/Geometry> // 必须包含此头文件 #include <iostream> int main() { // 1. 旋转的多种表示与转换 // 旋转向量(轴角):绕Z轴旋转45度 Eigen::AngleAxisd rotation_vector(M_PI / 4, Eigen::Vector3d::UnitZ()); std::cout << "Rotation matrix from angle-axis:\n" << rotation_vector.matrix() << std::endl; // 旋转矩阵 Eigen::Matrix3d rotation_matrix = rotation_vector.toRotationMatrix(); // 四元数 (推荐!无万向节锁,插值方便) Eigen::Quaterniond quat(rotation_vector); // 或者直接从旋转矩阵构造 // Eigen::Quaterniond quat(rotation_matrix); std::cout << "Quaternion (x,y,z,w): " << quat.coeffs().transpose() << std::endl; // coeffs顺序是(x,y,z,w) // 2. 使用四元数旋转一个点 Eigen::Vector3d point(1, 0, 0); Eigen::Vector3d rotated_point = quat * point; // 重载了乘法运算符,非常直观 std::cout << "Point (1,0,0) rotated by 45 deg around Z: " << rotated_point.transpose() << std::endl; // 3. 齐次变换矩阵 (用于刚体变换:旋转+平移) Eigen::Isometry3d T = Eigen::Isometry3d::Identity(); // 4x4齐次变换矩阵 T.rotate(quat); // 设置旋转部分 T.pretranslate(Eigen::Vector3d(1, 2, 3)); // 设置平移部分 (注意是pretranslate) std::cout << "Homogeneous transformation matrix T:\n" << T.matrix() << std::endl; // 4. 使用变换矩阵变换点(包括平移) Eigen::Vector3d transformed_point = T * point; // 自动进行齐次坐标计算 std::cout << "Transformed point: " << transformed_point.transpose() << std::endl; // 5. 变换的逆与复合 Eigen::Isometry3d T_inv = T.inverse(); Eigen::Isometry3d T_composite = T * T_inv; // 应该得到单位矩阵 return 0; }几何模块使用心得:
- 首选四元数:在存储和表示旋转时,我几乎总是使用
Eigen::Quaterniond。它紧凑、无奇异性、组合和插值方便。注意Eigen内部四元数存储顺序为(x, y, z, w),而有些库(如某些ROS消息)是(w, x, y, z),转换时需要小心。 - 理解
Isometry3d:Eigen::Isometry3d是表示刚体变换(旋转+平移)的最佳选择。它底层是一个4x4矩阵,但Eigen通过模板特化,保证了当你进行T * point运算时,得到的是正确的三维点结果,而不是一个四维向量。pretranslate()和translate()的区别在于,前者是左乘平移,后者是右乘,通常使用pretranslate()来设置变换。 - 避免欧拉角:虽然Eigen也提供了欧拉角(
eulerAngles)方法,但由于万向节锁的存在,除非与特定硬件或协议交互,否则建议在内部计算中避免使用。
5. 性能优化与高级特性
5.1 内存对齐与向量化
为了充分发挥SIMD指令(如SSE, AVX)的威力,Eigen的固定大小对象(如Vector4f,Matrix4d)在内存上需要是对齐的。现代编译器在开启优化后通常会处理好这一点,但如果你使用自定义的包含Eigen对象的结构体或类,并对其进行动态内存分配(new),就需要特别注意。
#include <Eigen/Dense> class MyClass { private: // 如果包含固定大小的Eigen对象,类需要特殊处理 Eigen::Vector4f vec_; // 16字节对齐 Eigen::Matrix3d mat_; // 可能需要32字节对齐(取决于架构和类型) public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 这个宏是关键! // ... 其他成员函数 ... }; int main() { // 动态创建对象时,new操作符会保证内存对齐 MyClass* obj = new MyClass(); // 使用STL容器存储固定大小Eigen对象时,必须使用Eigen提供的对齐分配器 #include <Eigen/StdVector> std::vector<Eigen::Vector4f, Eigen::aligned_allocator<Eigen::Vector4f>> vec_of_vecs; vec_of_vecs.push_back(Eigen::Vector4f::Zero()); delete obj; return 0; }对齐问题排查技巧:如果程序在涉及Eigen固定大小对象运算时,出现神秘的段错误(Segmentation Fault)或总线错误(Bus Error),尤其是在使用自定义容器或类时,首先应该怀疑内存对齐问题。确保:
- 类中使用了
EIGEN_MAKE_ALIGNED_OPERATOR_NEW宏。 - STL容器使用了
Eigen::aligned_allocator。 - 可以考虑在gcc/clang中使用
-fsanitize=undefined编译选项来检测未对齐的内存访问。
5.2 映射外部数据与避免不必要的拷贝
Eigen的Map类允许你将现有的内存块(如C数组、std::vector的数据指针)解释为Eigen的矩阵或向量,而无需拷贝数据。这对于与现有代码库或数据流集成至关重要。
#include <Eigen/Dense> #include <vector> #include <iostream> int main() { // 1. 将C数组映射为Eigen向量/矩阵 double raw_array[6] = {1, 2, 3, 4, 5, 6}; // 映射为6x1的向量 Eigen::Map<Eigen::VectorXd> vec_map(raw_array, 6); vec_map(0) = 100; // 直接修改原始数组 std::cout << "raw_array[0] is now: " << raw_array[0] << std::endl; // 输出 100 // 映射为2x3的矩阵 (按列优先存储!) Eigen::Map<Eigen::Matrix<double, 2, 3, Eigen::RowMajor>> mat_map(raw_array); std::cout << "Mapped matrix (RowMajor):\n" << mat_map << std::endl; // 2. 将std::vector的数据映射为Eigen对象 std::vector<float> vec_data = {1.1f, 2.2f, 3.3f, 4.4f}; Eigen::Map<Eigen::VectorXf> vec_from_std(vec_data.data(), vec_data.size()); // 注意:确保vec_data在Map对象生命周期内有效! // 3. 利用Map进行原地操作,避免临时对象 Eigen::MatrixXd big_mat = Eigen::MatrixXd::Random(100, 100); // 错误的做法:会产生临时矩阵 // big_mat = big_mat * 2 + big_mat; // (1) big_mat*2生成临时对象T1, (2) T1+big_mat生成T2, (3) big_mat = T2 // 正确的做法:使用原地操作或表达式模板 big_mat = 2 * big_mat + big_mat; // 更好的写法,Eigen的表达式模板会优化 // 或者显式使用.noalias()(在某些复杂表达式中有用) big_mat.noalias() = big_mat * 2 + big_mat; return 0; }关于存储顺序的要点:Eigen默认使用列优先(Column-major)存储,这与MATLAB、Fortran相同,但与C/C++原生数组的行优先习惯不同。使用Map时,必须通过模板参数Eigen::RowMajor或Eigen::ColMajor明确指出数据的存储方式,否则计算会出错。在性能敏感且需要与行优先数据交互的场景,可以考虑在全局使用typedef Eigen::Matrix<double, Dynamic, Dynamic, RowMajor> MatrixXdRowMajor;。
6. 常见问题排查与调试技巧实录
在实际项目中使用Eigen,难免会遇到各种问题。下面是我踩过的一些坑和解决方法。
6.1 编译错误:YOU_MIXED_MATRICES_OF_DIFFERENT_SIZES或YOU_MIXED_DIFFERENT_NUMERIC_TYPES
这是最常见的错误,属于Eigen的静态断言(static assertion)错误。
- 原因:你在一个表达式中混用了尺寸不匹配或数据类型不匹配的矩阵/向量。
- 排查:仔细检查出错行附近的矩阵声明和运算。使用IDE的调试功能或打印出矩阵的
.rows()和.cols()来确认尺寸。对于类型不匹配,使用.cast<T>()进行显式转换。 - 示例:
Eigen::Matrix3f m3; Eigen::Matrix4f m4; Eigen::MatrixXf m_dyn = Eigen::MatrixXf::Random(3, 4); // auto err1 = m3 + m4; // 编译错误:尺寸不匹配 (3x3 vs 4x4) // auto err2 = m3 + m_dyn; // 可能编译错误或运行时断言失败,因为m_dyn是动态尺寸,但可能不是3x3 auto ok = m3 + m_dyn.topLeftCorner<3, 3>(); // 正确:使用block操作获取固定尺寸块
6.2 运行时错误:段错误或断言失败
- 可能原因1:内存对齐问题。如前所述,固定大小Eigen对象需要对齐内存。解决方案见5.1节。
- 可能原因2:动态矩阵尺寸未初始化或错误。动态矩阵在参与运算前必须有正确的尺寸。
Eigen::MatrixXd A, B, C; // A和B未分配空间 // C = A * B; // 运行时灾难! int rows = 10, cols = 5; A.resize(rows, cols); B.resize(cols, rows); // 注意矩阵乘法的尺寸要求:A.cols() == B.rows() C = A * B; // 正确 - 可能原因3:
auto导致的表达式模板悬垂引用。Eigen::MatrixXd getTemporaryMatrix() { return Eigen::MatrixXd::Random(3,3); } void someFunction() { auto dangerous = getTemporaryMatrix() * 2; // dangerous是一个表达式模板,持有对临时矩阵的引用 // 临时矩阵在这里已经被销毁! std::cout << dangerous(0,0); // 未定义行为! // 正确做法:立即赋值或求值 Eigen::MatrixXd safe = getTemporaryMatrix() * 2; // 触发求值,存储结果 }
6.3 性能未达预期
- 检查编译优化选项:Eigen的性能严重依赖编译器优化。确保在Release模式下编译,并开启优化标志(如GCC/Clang的
-O2或-O3,MSVC的/O2)。-march=native可以生成针对本地CPU特定指令集(如AVX2)的代码,能大幅提升性能。 - 避免在循环中创建临时对象:将循环内不变的矩阵声明移到循环外。使用
.noalias()避免不必要的临时对象(但在简单表达式中,现代Eigen通常能自动优化)。 - 使用固定尺寸矩阵:如果矩阵尺寸在编译期已知(如3x3, 4x4),务必使用固定尺寸类型(
Matrix3d,Matrix4f)。这能让Eigen进行最激进的优化,包括循环展开和避免动态内存分配。 - 注意表达式模板的求值时机:复杂的复合表达式会被Eigen优化为一个循环。但如果你将其拆分成多行赋值,可能会阻碍优化。尽量写出完整的复合表达式。
6.4 与STL及其他库的交互
- 输出格式化:Eigen对象可以直接用
std::cout输出,但格式可能不理想。可以使用Eigen::IOFormat进行自定义:Eigen::IOFormat fmt(4, 0, ", ", "\n", "[", "]"); std::cout << "Formatted: " << mat.format(fmt) << std::endl; - 将Eigen向量存入
std::vector:如前所述,对于固定大小类型,需要使用对齐分配器。对于动态类型VectorXd,由于其数据本身在堆上,std::vector<Eigen::VectorXd>是安全的。 - 序列化:Eigen本身不提供序列化。可以使用二进制写入或文本格式(如保存为CSV)。对于简单场景,可以直接写入数据指针:
std::ofstream file("matrix.bin", std::ios::binary); Eigen::MatrixXd mat = Eigen::MatrixXd::Random(100, 100); file.write(reinterpret_cast<const char*>(mat.data()), mat.size() * sizeof(double));
7. 实战案例:一个简单的线性回归求解器
最后,我们用一个完整的例子来串联所学知识:实现一个使用正规方程(Normal Equation)求解多元线性回归的类。这涉及到矩阵转置、乘法、求逆(或更优的求解)等操作。
#include <Eigen/Dense> #include <vector> #include <iostream> #include <cassert> class LinearRegression { private: bool trained_ = false; Eigen::VectorXd coefficients_; // 存储学到的参数 theta public: LinearRegression() = default; // 使用正规方程 (X^T * X)^-1 * X^T * y 进行训练 void fit(const Eigen::MatrixXd& X, const Eigen::VectorXd& y) { assert(X.rows() == y.rows() && "X and y must have the same number of samples"); assert(X.cols() > 0 && "X must have at least one feature"); // 添加偏置项(截距): 在X左侧添加一列1 Eigen::MatrixXd X_with_bias = Eigen::MatrixXd::Ones(X.rows(), X.cols() + 1); X_with_bias.rightCols(X.cols()) = X; // 计算正规方程的解 // 使用更稳定、更高效的QR分解来代替直接求逆 (X^T * X)^-1 Eigen::MatrixXd Xt = X_with_bias.transpose(); // 求解 (X^T * X) * theta = X^T * y // 等价于求解 X * theta = y 的最小二乘解 coefficients_ = X_with_bias.colPivHouseholderQr().solve(y); trained_ = true; std::cout << "Training completed. Number of coefficients (including bias): " << coefficients_.size() << std::endl; } // 预测新样本 Eigen::VectorXd predict(const Eigen::MatrixXd& X_new) const { assert(trained_ && "Model must be trained before prediction"); assert(X_new.cols() == (coefficients_.size() - 1) && "Feature dimension mismatch"); Eigen::MatrixXd X_new_with_bias = Eigen::MatrixXd::Ones(X_new.rows(), X_new.cols() + 1); X_new_with_bias.rightCols(X_new.cols()) = X_new; return X_new_with_bias * coefficients_; } const Eigen::VectorXd& get_coefficients() const { return coefficients_; } }; int main() { // 生成模拟数据: y = 2.5 + 1.5*x1 - 0.8*x2 + noise int n_samples = 100; int n_features = 2; Eigen::MatrixXd X = Eigen::MatrixXd::Random(n_samples, n_features); Eigen::VectorXd true_theta(3); true_theta << 2.5, 1.5, -0.8; // [bias, coeff1, coeff2] Eigen::MatrixXd X_with_bias = Eigen::MatrixXd::Ones(n_samples, n_features + 1); X_with_bias.rightCols(n_features) = X; Eigen::VectorXd y = X_with_bias * true_theta; y += 0.1 * Eigen::VectorXd::Random(n_samples); // 添加噪声 // 创建并训练模型 LinearRegression lr; lr.fit(X, y); // 查看学到的参数 std::cout << "True coefficients (bias, feat1, feat2): " << true_theta.transpose() << std::endl; std::cout << "Learned coefficients: " << lr.get_coefficients().transpose() << std::endl; // 进行预测 Eigen::MatrixXd X_test(3, 2); X_test << 0.5, 0.2, -0.3, 0.8, 1.0, -1.0; Eigen::VectorXd predictions = lr.predict(X_test); std::cout << "Predictions:\n" << predictions.transpose() << std::endl; return 0; }这个案例展示了如何用Eigen清晰地实现一个机器学习算法。关键点在于:
- 使用
colPivHouseholderQr().solve()而非直接计算(X^T * X).inverse() * X^T * y,这在数值稳定性和计算效率上都是最佳实践。 - 通过
.rightCols()和.Ones()优雅地添加偏置项列。 - 整个实现过程简洁、高效,且与数学公式几乎一一对应,体现了Eigen在科学计算表达上的强大优势。
Eigen库的深度远不止于此,还有稀疏矩阵求解、非线性优化、矩阵函数等高级模块。但掌握了上述核心内容,你已足以应对90%以上的C++线性代数编程任务。记住,多查官方文档,多动手写代码,遇到性能问题时善用分析工具,你就能越来越熟练地驾驭这个强大的工具。