前言

这个矩阵是当时大一下学期暑假的时候为简单的平差而写的,虽然效率不高,但是对面向对象的编程来说,还是大有裨益的。

工程文件请见:C++矩阵类实现

主要功能

基本的矩阵运算(包括运算符重载)
  • 矩阵加减法
  • 矩阵数乘
  • 矩阵乘法
  • 矩阵“除法”
  • 矩阵赋值(包括复合赋值)
  • 矩阵前后置自增自减
  • 矩阵判等
  • 矩阵广播(与数字的)
矩阵处理
  • 矩阵合并
  • 矩阵分割
  • 矩阵打印输出
矩阵构造
  • 零矩阵
  • 单位矩阵
  • 元素全为1的矩阵
  • 构造对角矩阵
  • 构造元素随机的矩阵
矩阵操作
  • 矩阵转置
  • 矩阵求逆
  • 高斯消元法、求秩
  • 行列式、伴随矩阵
  • 矩阵元素下标取值
  • 矩阵取对角线元素
  • 化为 hessenberg 矩阵
  • QR 分解
  • 实对称矩阵求特征值

矩阵类技术细节

矩阵类成员变量

矩阵类的成员变量很少,主要是记录矩阵行列数的变量,以及一个存储矩阵数据的指向动态内存的指针。

矩阵类还包括静态成员变量,用于控制矩阵打印输入的格式。

class Matrix
{
// ---- 成员变量 ----
private:
int miRow = 0; //行数
int miCol = 0; //列数
double* mpBuf = nullptr; //存储矩阵
// ---- 静态成员变量 ----
static int iPrecise; // 控制流输出的显示精度
}

矩阵的构造函数

矩阵类的构造函数有多个,下面进行部分介绍

  • 无参数的构造函数生成未分配内存的0*0矩阵,这个空矩阵的意义,相信大家在数据结构课的学习中能感受到的。
  • std::initializer_list 为参数的构造函数,是为了实现如同 Matlab 的矩阵构造方法设计的,这里涉及到 std::initializer_list 的使用方法
class Matrix
{
public:
// ---- 构造函数 ----

/**
* @brief 无参数构造函数
*
* 获取一个0*0的未分配内存的空矩阵
* 不合法的计算一般返回本矩阵
*/
explicit Matrix();

/**
* @brief 构造函数
* 将double类型转化为1*1矩阵
*
* @param num 数字
*/
Matrix(double num);

/**
* @brief 构造函数
* 使用初始化列表构造矩阵
*
* @param lst 初始化列表
*/
Matrix(const MatIniLst& lst);


/**
* @brief 构造函数
* 使用初始化列表构造矩阵,列数为最多的一行的元素个数,可以以0补足其它行的元素
*
* @param lst 初始化列表(两层)
*/
Matrix(const std::initializer_list<MatIniLst>& lst);

/**
* @brief 构造函数
* 使用初始化列表构造矩阵,根据设定的列数,可以以0补足初始化列表中没有的元素
*
* @param lst 初始化列表(两层)
* @param col 矩阵列数
*/
explicit Matrix(const std::initializer_list<MatIniLst>& lst, int col);

/**
* @brief 构造函数
* 使用指针所指的数据构造矩阵,若数组长度小于需要的长度row*col,则会报错
*
* @param p double型指针,是要输入的数据
* @param row 矩阵行数
* @param col 矩阵列数
* @param count 数组长度
*/
explicit Matrix(double* p, int row, int col, int count);
}

矩阵的拷贝构造(赋值)函数

拷贝构造(赋值)函数需要注意以下几点:

  • 由于矩阵类中有指针和动态分配的内存,拷贝时需要用深拷贝。
  • 拷贝的时候需要注意加上判断条件,拷贝的对象如果是自身,则不进行其他操作
  • 在重载拷贝赋值符的时候,要保持 = 原始的用法,即 operator= 应当返回赋值的结果
class Matrix
{
public:
/**
* @brief 拷贝构造函数
*
*/
Matrix(const Matrix& tmp);

/**
* @brief 运算符重载,拷贝赋值函数
* 将矩阵赋值
*
* @param tmp 待赋值的矩阵
* @return 返回赋值之后的该矩阵的引用
*/
Matrix& operator=(const Matrix& tmp);
}

矩阵的移动构造(赋值)函数

关于对象移动,在 C++ primer 中有详细的介绍,下面给出我的觉得重要的几点:

  • 移动主要是为了从“将亡值”中取出需要的数据(这里主要是动态内存),减少了拷贝的成本。
  • 移动构造(赋值)函数需要加上 noexcept 以支持与标准库交互
  • 在为类的移动构造和移动赋值函数指定不抛出异常时,必须在类的头文件声明中和定义中(如果定义在类外的话)都指定 noexcept
  • 移动的时候需要注意加上判断条件,移动的对象如果是自身,则不应当进行其他操作
  • 在重载移动赋值符的时候,要保持 = 原始的用法,即 operator= 应当返回赋值的结果
class Matrix
{
public:
/**
* @brief 移动构造函数
*
*/
Matrix(Matrix&& tmp)noexcept;

/**
* @brief 运算符重载,移动赋值函数
* 将矩阵赋值
*
* @param tmp 待赋值的矩阵
* @return 返回赋值之后的该矩阵的引用
*/
Matrix& operator=(Matrix&& tmp)noexcept;
}

矩阵的析构函数

析构函数没有什么特别的技术点,注意内存回收就好了

class Matrix
{
public:
/**
* @brief 析构函数
*/
~Matrix();
}

矩阵的运算符重载

关于运算符重载,在 C++ primer 中也有详细的介绍。下面是我觉得需要注意的地方。

  • 运算符重载应当与其内置版本意义相似

  • 运算符要注意“对称”运算符与“非对称”运算符的处理。“对称”运算符应当为普通非成员函数,尤其是左侧运算对象不一定为类的对象时(这里使用的是友元函数);而“非对称”的运算符需要是成员函数,尤其是当其会改变类对象的状态时。

    class Matrix
    {
    public:

    /**
    * @brief 运算符重载,友元函数
    * 将本矩阵与所给矩阵相加求和
    *
    * @param lMat 左加矩阵
    * @param rMat 右加矩阵
    * @return 返回求和之后的该矩阵的引用
    */
    friend Matrix operator+(const Matrix& lMat, const Matrix& rMat);

    /**
    * @brief 运算符重载
    * 复合赋值+=
    *
    * @param tmp 要加上的矩阵
    * @return 返回结果矩阵的引用
    */
    Matrix& operator+=(const Matrix& tmp);
    }
  • 下标运算符通常定义两个版本:一个返回普通的引用,另一个是类的常量成员并返回常量引用。本类为了实现类的按行列的下标访问,返回类型略有不同。

    class Matrix
    {
    public:
    /**
    * @brief 运算符重载
    * 取下标
    * 返回下标对应的矩阵的某一行
    * 一般连续使用两个[]以获取某个指针
    * 不要在类的方法中使用取下标运算符!!!
    * 有两个版本,此为非常量版本
    *
    * @param num 下标
    * @return 返回下标对应的矩阵的某一行的第一个元素的指针
    */
    double* operator[](int num);
    /**
    * @brief 运算符重载
    * 取下标
    * 返回下标对应的矩阵的某一行
    * 一般连续使用两个[]以获取某个指针
    * 不要在类的方法中使用取下标运算符!!!
    * 有两个版本,此为常量版本
    *
    * @param num 下标
    * @return 返回下标对应的矩阵的某一行的第一个元素的指针
    */
    const double* operator[](int num)const;
    }
  • 递增和递减运算符有两种版本,分别是前置和后置版本。

    • 前置递增递减运算符应当与内置版本意义相近,返回递增递减后对象的引用
    • 前置递增递减运算符应当与内置版本意义相近,返回递增递减前对象的原值
    • 区分前置和后置运算符。后置版本接受一个额外的、并不使用的 int 类型形参
    class Matrix
    {
    public:
    /**
    * @brief 运算符重载
    * 前置递增
    *
    * @return 递增后的矩阵引用
    */
    Matrix& operator++();

    /**
    * @brief 运算符重载
    * 后置递增
    *
    * @param 区分前后置的参数
    * @return 递增前的矩阵
    */
    Matrix operator++(int);
    }