与我共同打造C++矩阵类

发表时间: 2024-03-07 10:36

06年用VC作图像处理软件时, 搜集的一篇文章.
文章虽不太长, 但原文还是分成了3部分, 我这里也保持原样.
大连理工大学软件学院WAKU(转载请保留署名)

和我一起写矩阵类(一)

最近Mr.Zeng的一个作业就是自己实现一个矩阵类,这是一个非常有实用价值的类。我花了几天完成了,期间也增长了很多知识,不敢独享,拿出来和大家共同学习。由于本人水平很非常有限,有错误不要给我面子,请尽快指出,我将不胜感激!!

开始吧。

矩阵也是一种数据结构,所以在设计之前一定要明确有哪些数据和哪些操作。首先,最容易想到的,矩阵要有一个一个二维数组,好这简单:

double array[3][3];

先等一下,为什么是double类型的?为什么是3x3的二维数组,如果图省事,这么定义当然可以,而且在相当一部分场合中,这个矩阵类也很实用。

我们要有更高的追求,要想办法使我们的类寿命更长。改进它:

类型用模板,可以花最少的时间最大程度上的提高代码的重用性。

template <class T>

那么二维数组应该几乘几的呢,答案是由用户确定。数组在矩阵使用的时候动态创建,可大可小,最具有灵活性。那么动态的二维数组应如何表示呢?大家都动态创建过一维数组,写过类似这样的语句:

int *p = new int[10];

new关键字动态的申请了10个int类型的连续空间,并把这段连续空间的首地址赋给指针p。然后就可以使用p[i]来引用数组元素,因为数组名就是指针。以此类推,动态二维数组也应该用指针来表示、来引用元素。在定义指针之前,还是回顾一下二维数组的一些概念,举例说明:

double array[3][3];

定义了3x3的double型数组,由于内存是线性的,所以这9个元素在内存中都是“一条直线”似的存放。array[0][0]是头一个元素,array[2][2]是最后一个元素,这很好理解。那么array[0]代表什么?它是什么东西?

它其实也是个指针,指向数组的第一行元素的行首。同理array[1]是第二行行首指针,array[2]是第三行。array呢?它是数组名,那肯定也是个指针,但是它可不是我们常用的那种指针,而是一个指向指针的指针(二级指针)。它指向的是“指向第一行行首的指针”。看着像一个绕口令,仔细琢磨一下就明白了。如果你感兴趣,输入以下代码并执行:

double array[3][3];cout<<&array[0]<<endl <<array<<endl;

你会发现输出的值是完全相同的,这就证明了array指向了array[0]。总结一下就是:

想表示或引用几维数组,就需要几级指针。

那么我们的类属性的声明就差不多了:

template <class T>class CMatrix{    int m;     //行数    int n;     //列数    T** p;     //二维数组指针};

由于是动态创建的数组,所以矩阵的行和列各需要用一个int型变量来记录。p就是用模板创建的二级指针,来表示二维数组。

下一次我们就开始类方法的设计。大家觉得我写的还可以,麻烦贵手轻抬回一下贴,让我知道有人在看,好有动力写下去,谢谢~~

和我一起写矩阵类(二)

上次说到了在构造函数和SetMatrix函数中用数组给矩阵赋值,两个函数的第三个形参都是T* array。用这种方式传递二维数组应该说有点无奈,我们以前学过的传递二维数组都是类似以下形式:

void function(int a[][10]);

数组的最高维一定要是一个常数,但是我们的矩阵的大小是不定的,不能确定各个维的大小,那怎么办?不要忘了,所有数组在内存中都是线性的,我们可以用一个一级指针来引用任意维的数组。看以下示例代码:

void main(){    int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};    int *p = (int*)a;    cout<<p[3]<<endl;}

程序工作良好,并如你所愿,输出了4。这就说明一级指针也是可以引用二维数组的。如果我想用指针p引用原数组里的元素a[1][1]就需要做一个简单的变换p[1*3 + 1]p[4],这就是为什么给p[i][j]赋值的时候用array[i*n + j]了。n即为二维数组的列数。

但是和上面示例的第二条语句一样,构造函数CMatrix(int m, int n, T* array)SetMatrix(int m, int n, T* array)函数在调用的时候都需要把二维数组的数组名强制转换成一级指针。调用形式如下:

double array[2][2] = {1, 2, 3, 4};CMatrix<double>   matrix(2, 2, (double*)array);

或者

double array[2][2] = {1, 2, 3, 4};CMatrix<double>   matrix;matrix.SetMatrix(2, 2, (double*)array);

这样就定义了一个2行2列名为matrix的矩阵对象,并且矩阵里面的值和二维数组array一样。

下面我们完成最后一个SetMatrix函数,在用构造函数CMatrix(int m, int n)创建对象后,如果只想用一个数组填充矩阵而不改变行列值,就可以用下面的函数:

template <class T>void CMatrix<T>::SetMatrix(T* array){    int i, j;    if (p)    {       for (i = 0; i < this->m; i++)       {           delete[] p[i];       }       delete[] p;    }    for (i = 0; i < m; i++)    {       for (j = 0; j < n; j++)       {           p[i][j] = array[i*n+j];       }    }}

没什么新东西就不详细讲解了。

至此,矩阵创建和初始化的相关函数基本上都已经完成了,如果你勤快,你的类声明应该是这个样子的:

template <class T>class CMatrix{    int m;     //行数    int n;     //列数    T** p;     //二维数组指针public:    CMatrix(void);    CMatrix(int m, int n);             CMatrix(int m, int n, T* array);    ~CMatrix(void);    void SetMatrix(int m, int n);    void SetMatrix(int m, int n, T* array);    void SetMatrix(T* array);};

怎么样?大家还能跟上吧?有什么问题尽管问,我能答的肯定会回答。下次我们开始写矩阵运算的函数,并对我做的时候遇到的问题进行详细讨论。敬请期待~

和我一起写矩阵类(三)

大连理工大学软件学院WAKU(转载请保留署名)

类的方法(也可叫做成员函数)从构造函数开始,我写的一个构造函数是这样的:

template <class T>CMatrix<T>::CMatrix(void){    m = 0; n  = 0;    p = NULL;}

由于使用了模板,所以在每个成员函数之前都要加上template <class T>,而且类名后一定要加上<T>,否则会编译出错。这是一个无参的构造函数,里面把行列值赋0,把数组的指针置为空,这是一个好习惯。除了安全带来的另一个好处是避免了定义对象数组时写出一长串的初始化表。

如果有可能定义类的对象数组,最好提供类的无参构造函数

然后再提供另外的成员函数完成有参构造函数所完成的功能。我写的一个有参构造函数是:

template <class T>CMatrix<T>::CMatrix(int m, int n){    int i, j;    p = new T*[m];    for (i = 0; i < m; i++)    {       p[i] = new T[n];       for (j = 0; j < n; j++)       {           p[i][j] = 0;       }    }    this->m = m; this->n = n;}

这是有参构造函数所完成的功能,无参构造函数由于不知道行列的具体值所以不能加入以上代码,所以我们必须要另写一个一模一样的函数“补充”这个功能:

template <class T>void CMatrix<T>::SetMatrix(int m, int n){    int i, j;    p = new T*[m];        for (i = 0; i < m; i++)    {       p[i] = new T[n];       for (j = 0; j < n; j++)       {           p[i][j] = 0;       }    }    this->m = m; this->n = n;}  

这两段代码实现了动态创建 m 行 n 列二维数组的功能。p = new T*[m];语句和第一篇里创建一维数组的语句非常类似,这里申请了有 m 个 T 类型的指针数组(即数组里存放的全是指向 T 类型的指针),并把这个数组的首地址赋给了 p 。然后外层 for 循环遍历数组里的每一个指针,并用每个指针 p[i] 接收由 new 创建的 n 个 T 类型数组。

明白前面我讲过二维数组,这段代码也就很好理解,p[i]是一个行指针,p[0]相当于double array[3][3]里的array[0]、p[1]就相当于array[1];然后每个行指针又“管辖”着刚申请的n个元素。里层for循环把这一行的元素值初始为0。其实说穿了也很简单吧。

下面把析构函数完成,很简单,就是释放申请的空间:

template <class T>CMatrix<T>::~CMatrix(void){    int i;        if (p)    {       for (i = 0; i < m; i++)       {           delete[] p[i];       }       delete[] p;    }}

记住一点,有几个new就要有几个delete。另外注意一点,delete后面的[]一定不要落下,否则编译器并不会报错,你的内存却还照样泄漏。

矩阵里面有一个二维数组,那么一个可以把二维数组填充到到矩阵里的函数无疑是非常有用的。让我们实现它吧:

template <class T>CMatrix<T>::CMatrix(int m, int n, T* array){    int i, j;      p = new T*[m];    for (i = 0; i < m; i++)    {       p[i] = new T[n];       for (j = 0; j < n; j++)       {           p[i][j] = array[i*n+j];       }    }    this->m = m; this->n = n;}

这是一个构造函数,用于在创建对象时就用二维数组赋值,对应重载的SetMatrix函数如下:

template <class T>void CMatrix<T>::SetMatrix(int m, int n, T* array){    int i, j;    if (p)    {       for (i = 0; i < this->m; i++)       {           delete[] p[i];       }       delete[] p;    }      p = new T*[m];    for (i = 0; i < m; i++)    {       p[i] = new T[n];       for (j = 0; j < n; j++)       {           p[i][j] = array[i*n+j];       }    }    this->m = m; this->n = n;}

由于调用SetMatrix函数时的对象有可能已经申请了空间,所以再数组赋值之前应该释放掉。

我们不是要传递二维数组吗?为什么第三个参数是T* array?这不是一个一级指针吗?不觉得赋值的时候用p[i][j] = array[i*n+j];这种语句很奇怪吗?请听下回分解