【Eigen教程02】深入Eigen矩阵引擎:模板参数、内存布局与基础操作指南

【Eigen教程02】深入Eigen矩阵引擎:模板参数、内存布局与基础操作指南

原创作者:郑同学的笔记

原文链接:https://zhengjunxue.blog.csdn.net/article/details/148478757

一、eigen核心模板类

所有类型均基于 Matrix 模板类:

cpp 复制代码
Matrix<Scalar, RowsAtCompileTime, ColsAtCompileTime, Options>
  • Scalar:元素类型(float, double, int, complex等)
  • Rows/Cols:行/列数(Dynamic 表示动态大小)
  • Options:位字段组合(存储顺序+对齐方式)

Options 详解

    1. 存储顺序 (Storage Order)
标志值 说明 内存布局示例 (2x3 矩阵)
ColMajor (默认) 列优先 [a11, a21, a12, a22, a13, a23]
RowMajor = 0x1 行优先 [a11, a12, a13, a21, a22, a23]

选择建议:

默认用 ColMajor(兼容BLAS/LAPACK)

与C/C++数组交互时可用 RowMajor

bash 复制代码
Eigen 与 C/C++ 数组交互时为何推荐使用 RowMajor?
核心原因:内存布局匹配
C/C++ 多维数组采用 行优先存储(Row-Major Order):

cpp
int c_array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};
内存实际存储顺序:
1, 2, 3, 4, 5, 6(先存第一行,再存第二行)
    1. 内存对齐 (Alignment)
标志值 说明 适用场景
AutoAlign (默认) 自动对齐 固定尺寸矩阵自动启用SIMD优化
DontAlign = 0x80 禁用对齐 特殊内存需求或嵌入式系统
  • Options 组合示例
cpp 复制代码
#include <iostream>
#include <Eigen/Eigen>

using namespace Eigen;
using namespace std;

int main() {
    // 默认选项:列优先 + 自动对齐 (等价于 Options=0)
    Matrix<float, 3, 3> mat1;

    // 显式指定行优先 + 自动对齐
    Matrix<double, Dynamic, Dynamic, RowMajor> mat2(100, 100);

    // 列优先 + 禁用对齐 (0x0 | 0x80 = 0x80)
    Matrix<int, 4, 4, ColMajor | DontAlign> mat3;

    // 行优先 + 禁用对齐 (0x1 | 0x80 = 0x81)
    Matrix<float, 1, Dynamic, RowMajor | DontAlign> vec;

    cout << "Output signal length: " << endl;
    return 0;
}
  • 错误:static_assert failed: 'INVALID_MATRIX_TEMPLATE_PARAMETERS'

错误的代码

cpp 复制代码
 Matrix<float, Dynamic, 1, RowMajor | DontAlign> vec;

这个错误表明问题出在 Matrix<float, Dynamic, 1, RowMajor | DontAlign> vec; 的定义上。在 Eigen 库中,对于列向量(列数为1),指定 RowMajor 存储顺序是无效的,因为列向量的存储顺序只能是列优先(ColMajor)。

应该更正为

cpp 复制代码
// 行优先 + 禁用对齐 (0x1 | 0x80 = 0x81)
Matrix<float, 1, Dynamic, RowMajor | DontAlign> vec;
  • 下面是一个演示Eigen中行优先(Row-major)和列优先(Column-major)存储的完整示例代码
cpp 复制代码
#include <iostream>
#include <Eigen/Eigen>

using namespace Eigen;
using namespace std;


int main() {
    // 创建3x2矩阵
    Eigen::MatrixXd matrix(3, 2);
    matrix << 1, 2,
        3, 4,
        5, 6;

    std::cout << "原始矩阵:\n" << matrix << "\n\n";

    // 默认列优先存储 (内存布局: 1,3,5,2,4,6)
    Eigen::MatrixXd col_major = matrix;
    std::cout << "列优先存储 (默认):\n";
    std::cout << "内存顺序: ";
    for (int i = 0; i < col_major.size(); ++i) {
        std::cout << *(col_major.data() + i) << " ";
    }
    std::cout << "\n\n";

    // 行优先存储 (显式指定模板参数)
    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> row_major = matrix;
    std::cout << "行优先存储:\n";
    std::cout << "内存顺序: ";
    for (int i = 0; i < row_major.size(); ++i) {
        std::cout << *(row_major.data() + i) << " ";
    }
    std::cout << "\n";

    return 0;
}

输出

bash 复制代码
原始矩阵:
1 2
3 4
5 6

列优先存储 (默认):
内存顺序: 1 3 5 2 4 6

行优先存储:
内存顺序: 1 2 3 4 5 6

二、完整类型体系

类型定义 等效模板 元素类型 大小 典型用途
MatrixXd Matrix<double,Dynamic,Dynamic> double 动态 通用矩阵
Matrix4f Matrix<float,4,4> float 4x4固定 3D变换矩阵
Vector3d Matrix<double,3,1> double 固定列向量 3D坐标
RowVectorXi Matrix<int,1,Dynamic> int 行动态 行向量
ArrayXXcf Array<complex,Dynamic,Dynamic> 复数float 动态数组 信号处理
Quaternionf 专用四元数类型 float 4元素 旋转表示

三、固定大小 vs 动态大小

特性 固定大小 (e.g., Matrix4f) 动态大小 (e.g., MatrixXf)
内存分配 栈空间 堆空间(默认16字节对齐)
大小检查 编译时 运行时
性能 无分配开销,编译器完全优化 有分配开销,循环边界需运行时检查
最大尺寸 受栈大小限制 受系统内存限制
典型用途 小矩阵(<16x16),变换矩阵 大矩阵,用户定义尺寸

四、基础操作

a) 创建和初始化

cpp 复制代码
#include <iostream>
#include <Eigen/Eigen>

using namespace Eigen;
using namespace std;

int main() 
{
    // 1. 默认构造函数 (元素未初始化!)
    Vector3f v1; // 3x1 float 列向量 (元素是垃圾值)
    Matrix2d m1; // 2x2 double 矩阵 (元素是垃圾值)

    // 2. 逗号初始化 (非常重要且常用)
    Vector3f v2; v2 << 1.0f, 2.0f, 3.0f; // 初始化列向量 [1, 2, 3]^T
    RowVector3d rv; rv << 4.0, 5.0, 6.0; // 初始化行向量 [4, 5, 6]
    Matrix3f m2; m2 << 1, 2, 3,
        4, 5, 6,
        7, 8, 9; // 初始化矩阵

    // 3. 固定大小类型的构造函数初始化
    Vector2d v3(1.5, 2.5); // 直接构造 [1.5, 2.5]^T
    Vector4i v4(1, 2, 3, 4); // [1, 2, 3, 4]^T

    // 4. 动态大小对象的构造 (必须指定大小)
    VectorXd v5(5); // 创建大小为 5 的 double 列向量 (元素未初始化)
    MatrixXf m3(2, 3); // 创建 2x3 的 float 矩阵 (元素未初始化)

    // 5. 特殊值初始化
    Matrix3d m4 = Matrix3d::Zero();    // 全零矩阵
    MatrixXd m5 = MatrixXd::Zero(4, 4); // 动态大小全零矩阵
    Vector3f v6 = Vector3f::Ones();     // 全一向量 [1,1,1]^T
    Matrix3f m6 = Matrix3f::Identity(); // 单位矩阵
    MatrixXf m7 = MatrixXf::Random(3, 3); // 3x3 随机矩阵 (元素在 [-1,1])
    Vector4i v7 = Vector4i::Constant(10); // [10,10,10,10]^T
    //Matrix2f m8 = Matrix2f::LinSpaced(4, 0.0f, 1.0f).reshaped(2, 2); // 创建线性间隔向量并重塑 (更复杂)

    // 修正:创建线性间隔向量并手动重塑为矩阵
    VectorXf linSpacedVec = Eigen::VectorXf::LinSpaced(4, 0.0f, 1.0f); // 创建线性间隔向量
    Matrix2f m8;
    m8 << linSpacedVec(0), linSpacedVec(1), // 将线性间隔向量重塑为矩阵
        linSpacedVec(2), linSpacedVec(3);

    return 0;
}

b) 访问元素 (operator(), operator[])

  • 矩阵: 使用 operator(row, col)。索引从 0 开始。

  • 向量: 使用 operator 或 operator(index) (对于列向量 index 是行索引,对于行向量 index 是列索引)。推荐 operator[] 用于向量元素访问。

  • 数组: 访问方式与矩阵/向量相同。

cpp 复制代码
Vector3f v(1.0f, 2.0f, 3.0f);
float x = v[0];    // x = 1.0f (推荐方式)
float y = v(1);    // y = 2.0f

Matrix2d m;
m(0, 0) = 3.0; m(0, 1) = 2.5;
m(1, 0) = -1.0; m(1, 1) = m(0, 0) + m(0, 1); // m(1,1) = 5.5
double elem = m(1, 0); // elem = -1.0

c) 基本算术运算

  • +, -: 矩阵/向量/数组的加减法(要求维度匹配)。

  • *: 含义取决于操作数:

    • matrix * matrix: 矩阵乘法。
    • matrix * vector: 矩阵-向量乘法。
    • scalar * matrix/vector/array: 标量乘法。
    • array * array: 逐元素乘法 (Hadamard product)。
  • /: 标量除法 (matrix/scalar, vector/scalar, array/scalar), 或数组的逐元素除法 (array / array)。

  • =: 赋值。 Eigen 使用表达式模板,赋值操作会触发实际计算并复制结果。

  • **+=, -=, =, /=: **复合赋值运算符。对于矩阵/向量, = 仅用于标量乘法 (matrix = scalar)。对于数组,= 和 /= 可用于逐元素操作 (array1 *= array2)。

cpp 复制代码
// 向量运算
Vector3f a(1, 2, 3), b(4, 5, 6);
Vector3f c = a + b; // [5, 7, 9]^T
Vector3f d = a - b; // [-3, -3, -3]^T
Vector3f e = 2 * a; // [2, 4, 6]^T
Vector3f f = b / 2; // [2, 2.5, 3]^T
float dot = a.dot(b); // 点积 (1*4 + 2*5 + 3*6 = 32)
Vector3f cross = a.cross(b); // 叉积 (计算略)//a×b=((2×6−3×5),−(1×6−3×4),(1×5−2×4))

// 矩阵运算
Matrix2d A, B;
A << 1, 2,
     3, 4;
B << 5, 6,
     7, 8;
Matrix2d C = A + B; // [ [6, 8], [10, 12] ]
Matrix2d D = A - B; // [ [-4, -4], [-4, -4] ]
Matrix2d E = 3 * A; // [ [3,6], [9,12] ]
Matrix2d F = A * B; // 标准矩阵乘法 [ [1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8] ] = [ [19, 22], [43, 50] ]
Matrix2d G = A.array() * B.array(); // 逐元素乘法 [ [1*5, 2*6], [3*7, 4*8] ] = [ [5, 12], [21, 32] ] (需要.array()转换)

// 数组逐元素运算
Array3f arr1, arr2;
arr1 << 1, 2, 3;
arr2 << 4, 5, 6;
Array3f arr3 = arr1 * arr2; // [4, 10, 18] (逐元素乘)
Array3f arr4 = arr1 / arr2; // [0.25, 0.4, 0.5] (逐元素除)
Array3f arr5 = arr1.sqrt(); // [1, sqrt(2), sqrt(3)] (逐元素平方根)
Array3f arr6 = arr1.exp(); // [exp(1), exp(2), exp(3)] (逐元素指数)

d) 大小和类型信息

  • .rows(), .cols(): 获取矩阵/向量/数组的行数和列数。

  • .size(): 获取向量/数组中元素的个数。对于矩阵,返回 .rows() * .cols()。

  • .resize(rows, cols): 改变动态大小矩阵/向量/数组的维度(固定大小对象调用此方法会导致断言错误)。这会丢弃原有数据!如果需要保留数据改变大小,请使用 conservativeResize()(更昂贵)。

  • .setZero(), .setOnes(), .setIdentity(), .setRandom(): 将对象设置为对应特殊值(原地修改)。

  • static 方法如 MatrixXd::Identity(rows, cols) 用于创建动态大小的特殊矩阵。

cpp 复制代码
MatrixXd dynMat(2, 3); // 2x3 动态矩阵
int rows = dynMat.rows(); // 2
int cols = dynMat.cols(); // 3
int numElems = dynMat.size(); // 6

dynMat.resize(3, 2); // 现在变成 3x2, 内容未定义
dynMat.setZero(); // 设为 3x2 零矩阵

VectorXf dynVec(4);
dynVec.setOnes(); // [1,1,1,1]^T

e) 转置、共轭、伴随

  • .transpose(): 返回矩阵/向量的转置(对于向量,行向量转置成列向量,反之亦然)。注意: 原地转置使用 .transposeInPlace()。避免在复杂表达式中使用 A = A.transpose(),使用 A.transposeInPlace() 或 A = A.adjoint()(如果涉及复数)。

  • .conjugate(): 返回逐元素共轭复数(对实数无影响)。

  • .adjoint(): 返回伴随矩阵(即共轭转置 conjugate().transpose())。对于实数矩阵等同于 .transpose()。

cpp 复制代码
Matrix2cd matC; // 2x2 复数 double 矩阵
matC << std::complex<double>(1,2), std::complex<double>(3,4),
        std::complex<double>(5,6), std::complex<double>(7,8);
Matrix2cd trans = matC.transpose(); // [[1+2i, 5+6i], [3+4i, 7+8i]]
Matrix2cd conj = matC.conjugate(); // [[1-2i, 3-4i], [5-6i, 7-8i]]
Matrix2cd adj = matC.adjoint();   // [[1-2i, 5-6i], [3-4i, 7-8i]] (共轭转置)

RowVector3d r(1, 2, 3);
Vector3d c = r.transpose(); // 行向量转置成列向量 [1,2,3]^T

f) 归约操作

  • .sum(): 所有元素的和。
  • .prod(): 所有元素的乘积。
  • .mean(): 所有元素的平均值。
  • .minCoeff(): 最小元素的值。
  • .maxCoeff(): 最大元素的值。
  • .trace(): 矩阵的迹 (主对角线元素之和)。
  • .norm(): 向量的 L2 范数(欧几里得长度),或矩阵的 Frobenius 范数(所有元素平方和的平方根)。
  • .squaredNorm(): L2 范数的平方(或 Frobenius 范数的平方)。
  • .lpNorm<p>(): 计算 Lp 范数 (例如 .lpNorm<1>() 是 L1 范数, .lpNorm<Infinity>() 是 L∞ 范数)。
  • .all(): 是否所有元素都为 true (非零)。
  • .any(): 是否有任何一个元素为 true (非零)。
  • .count(): 等于 true (非零) 的元素个数。
cpp 复制代码
Vector3f v(1, -2, 3);
float s = v.sum();       // 1 + (-2) + 3 = 2
float p = v.prod();      // 1 * (-2) * 3 = -6
float m = v.mean();      // 2 / 3 ≈ 0.666...
float minVal = v.minCoeff(); // -2
float maxVal = v.maxCoeff(); // 3
float l2 = v.norm();     // sqrt(1^2 + (-2)^2 + 3^2) = sqrt(14) ≈ 3.74
float l2sq = v.squaredNorm(); // 14
float l1 = v.lpNorm<1>(); // |1| + |-2| + |3| = 6

Matrix2f mat;
mat << 1, 2,
       3, 4;
float trace = mat.trace(); // 1 + 4 = 5
float frob = mat.norm();   // sqrt(1^2 + 2^2 + 3^2 + 4^2) = sqrt(30) ≈ 5.477

g) 分块操作

  • Eigen 提供了强大的方法来提取或操作矩阵/向量的子块。

  • .block(i, j, p, q): 返回一个从 (i, j) 开始的 p x q 子块的可读写视图。

  • .topLeftCorner(p, q), .topRightCorner(p, q), .bottomLeftCorner(p, q), - .bottomRightCorner(p, q): 返回对应角落的 p x q 子块视图。

  • .topRows(q), .bottomRows(q), .leftCols(p), .rightCols(p): 返回顶部/底部 q 行或左侧/右侧 p 列的视图。

  • .head(n), .tail(n): 对于向量,返回前 n 个或后 n 个元素的视图。

  • .segment(i, n): 对于向量,返回从索引 i 开始的 n 个元素的视图。

  • InPlace 变体: 许多块操作有对应的 ...InPlace() 版本,可以直接在原始对象上操作该块(例如 mat1.topRows(2) = mat2; 是赋值给视图)。

  • .col(0) 直接引用首列

  • middleCols(1,2) 引用中间两列(索引1开始,长度2)

cpp 复制代码
Matrix4f M = Matrix4f::Random();
// 提取 2x2 子块,从位置 (1,1) 开始
Matrix2f block = M.block<2,2>(1,1); // 固定大小版本 (如果大小在编译时已知更高效)
MatrixXf dynBlock = M.block(1,1,2,2); // 动态大小版本

// 提取第一行
RowVector4f firstRow = M.row(0);
// 提取第三列
Vector4f thirdCol = M.col(2);

// 操作左上角 3x3 子块
M.topLeftCorner(3,3) *= 2; // 原地将左上角 3x3 块乘以 2

VectorXf longVec(10);
VectorXf first3 = longVec.head(3); // 前3个元素
VectorXf last4 = longVec.tail(4);  // 后4个元素
VectorXf middle5 = longVec.segment(2,5); // 从索引2开始的5个元素
cpp 复制代码
Eigen::VectorXi v(3);
v << 1, 2, 3;

Eigen::MatrixXi m1(3, 2);
m1 << 4, 5, 
      6, 7, 
      8, 9;

Eigen::MatrixXi m2(3, 2);
m2 << 10, 11,
      12, 13,
      14, 15;

Eigen::MatrixXi result(3, 5);

// 使用块操作直接赋值,避免额外拷贝
result.col(0) = v;              // 第0列 = VectorXi
result.middleCols(1, 2) = m1;   // 第1-2列 = 第一个MatrixXi
result.middleCols(3, 2) = m2;   // 第3-4列 = 第二个MatrixXi

五、 重要注意事项

  • 表达式模板 (Expression Templates): Eigen 大量使用表达式模板来优化性能。像 C = A * B + D 这样的表达式通常会被优化成一个循环,避免创建临时矩阵。理解这点有助于避免性能陷阱(如不必要的临时对象)和正确使用 .eval()(强制表达式求值)。

  • 混叠 (Aliasing): 在赋值操作 A = A * B 中,如果 A 和 B 是同一个矩阵(或共享数据),计算结果会出错。Eigen 默认会检测简单混叠(如 mat = mat.transpose()),但对于更复杂的情况(如 A = A * A),需要使用 A = A * A.eval() 或更好的 A = A.square()(如果存在)来避免。使用 .noalias() 可以明确告诉 Eigen 没有混叠(如 C.noalias() = A * B)。

  • 固定大小 vs. 动态大小: 固定大小的矩阵/向量(在编译时知道维度)允许 Eigen 进行更积极的优化(如循环展开、使用栈内存)。尽可能使用固定大小。动态大小的对象使用堆内存分配。

  • 内存对齐 (Memory Alignment): 为了使用 SIMD 指令获得最佳性能,Eigen 对象(特别是固定大小向量和矩阵)需要内存对齐。使用 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 宏重载 new 运算符或使用 std::aligned_allocator 在 STL 容器中存储 Eigen 对象来确保对齐。

  • 编译时间: Eigen 是一个纯头文件库,大量使用模板。复杂的表达式可能导致编译时间较长。合理组织代码和使用前向声明有时有帮助。

相关推荐
wadesir20 小时前
C++基本数据类型详解(零基础掌握C++核心数据类型)
java·开发语言·c++
leiming621 小时前
c++ map容器
开发语言·c++·算法
杨校1 天前
杨校老师课堂备赛C++信奥之模拟算法习题专项训练
开发语言·c++·算法
hd51cc1 天前
MFC 文档/视图 二
c++·mfc
wzfj123451 天前
认识lambda
c++
老王熬夜敲代码1 天前
C++万能类:any
开发语言·c++·笔记
智者知已应修善业1 天前
【数组删除重复数据灵活算法可修改保留重复数量】2024-3-4
c语言·c++·经验分享·笔记·算法
Cappi卡比1 天前
【无标题】
c++
汉克老师1 天前
GESP2025年12月认证C++五级真题与解析(编程题2 (相等序列))
c++·算法·贪心算法·中位数·质数分解