由编写矩阵class想到的

来源:互联网 发布:游戏编程精粹 pdf 云盘 编辑:程序博客网 时间:2024/06/06 18:39

         我们今天线性代数刚好学到矩阵了,于是尝试将所学的编程知识加以实践,利用在高数上面。再加上C++如此强大的表达能力,写好了一个类之后就可以把它当做一个组件,甚至作为内置类型来用。

         当然,说起来容易做起来难。编程就是这样,萌生一个想法很容易,但是如果要把这个想法加以细化,再予以实现,需要的不仅仅是耐心,还有扎实的编程功底。笔者这个寒假看了一本叫做《Effective C++》的书。其实看之前我还是怀着将信将疑的态度的,既然是Effective那应该和我们没有太大的关系吧,反正那是以后的事?因为有牛人推荐去看一下这本书,所以我看下去了,一探究竟为何推荐这本书。现在看来,笔者认为,《Effective C++》确实是一本好书的。其一,养成良好的编程习惯是要从学生开始的;同样,我们应该要养成高效的编程习惯,有时一个小小的细节可能会对性能产生绝对的影响(比如对象的函数传递不用const引用类型)。其二,我们在学习如何高效率编程的时候其实会对语言实现的原理进行深入的了解。所以,这是一本好书,值得一读的。

顺便一提的是,一开始看的是中文版电子书,不过后面我在网上买了一本评注版的,也就是英文版,看得云里雾里,一知半解。我以前对自己的英文水平有着过分的自信,可惜面对英文版的书,还是原形毕露了。尽管也许每个词都看得懂,句子也勉强通过,文意老是捉摸不透。所以说我现在明白了,除非是有特殊的需要,尽量选择中文版的书看,不要因为图新鲜或为了证明自己的能力而去买英文版的,真的,中文版可能翻译得不好,但是我们读者可以明白得一样好。

好了,说远了,下面是我这个class的设计。

class Matrix{

private:

    int **Mtx;

    int cols;

    int rows;

    static void intMult(Matrix& mtx, intLamda);

public:

    friend ostream&operator<<(ostream& out, const Matrix& mt);

    friend istream&operator>>(istream& in, Matrix& mt);

    friend const Matrix operator*(int lamda,const Matrix& rhs);

 

    explicit Matrix(int i=0, int j=0);

    Matrix(const Matrix& rhs); //Copy fx

         void operator=(const Matrix& rhs);//copy assignment

    ~Matrix();

 

    const Matrix operator+(const Matrix&rhs) const;

    const Matrix operator-(const Matrix& rhs)const;

    const Matrix operator*(const Matrix&rhs) const;

    const Matrix operator*(int lamda) const;

 

    const int getElem(int row, int col) const;

    const int getRow() const {return rows;}

    const int getCol() const {return cols;}

    const int getMtx(int i, int j) const{return Mtx[i][j];}

};

一开始考虑该如何存放矩阵,想着简单,但却着实烦扰了我半天。我可以选择复合的vector,但是因为是练习嘛,我打算挑战自己,建立一个二位数组。考虑到需要动态存储,我将二维数组的指针存放在int **Mtx中。大家都懂得,什么程序,一旦扯上指针就是处于灾难的边缘了。更何况指针的指针。Mtx指向一组int*,是个指针的数组。而每个int*又指向一组int。于是一个二维数组就这么诞生了(想象一下一个二维表,表头指向许多行,而每一行又指向了该行的所有元素)。所以,我令Mtx指向的一组int*作为行,而int*指向的一组int作为这一行的所有列。那么,Mtx[i][j]是第i行第j列呢,还是反着说呢?我一直都没弄清楚。经过试验可以确定从左向右的原则任然成立,也就是说是i行j列。大家也可以试一试,比较下(Mtx[i])[j](i行j列)和Mtx[i][j]的效果就可以了。看这个代码:

    Ptr[1][2]=10;

    cout << Ptr[1][2] << endl;

    cout << (Ptr[1])[2];

假设Ptr是经过分配并已初始化的int**,上面的结果两个都是10,也就是说从左向右的顺序成立。

初始化代码如下:

    Mtx=new int*[rows];

    for(int i=0;i<rows;i++){

        Mtx[i]=new int[cols];

        for(int j=0;j<cols;j++)Mtx[i][j]=rhs.Mtx[i][j];

    }

         存储问题解决了,一切就可以循序渐进了。

         Explicit的构造函数是什么呢?它是为了阻止编译器在幕后进行隐式类型转换。比如说令int a=1.22f,这时候就涉及到隐式类型转换了,编译器在幕后将(float)1.22转换为了(int)1,并且赋值给a。而显示转换比如说static_cast等,在此不赘述,可以参考reference。隐式转换在有些时候会给我们带来麻烦,这是Effective C++里面提到的。于是,一开始,我遵守了约定,将Matrix::Matrix(const Matrix&)也声明为explicit,问题就出现了。比如,设在函数里有一个Matrix M,如果我要return M的话,编译器却不干了:Error: no matching function for call to'Matrix::Matrix(Matrix&)'。我猜想,return需要创建一个对象保存结果并传递,而不是直接将M的内存区域移交给函数调用者。这就涉及到一个隐式构造函数(编译器自动创建了一个临时对象),而这个行为是被explicit所禁止的。将copy构造的explicit去掉就可以通过了。所以,可以得出,对于一个copy构造函数修饰explicit是坏主意,除非你认为这确实有必要。

         笔者还遇上了一个问题,在刚才也提到过传递const引用会对性能有很大的提升,但是,这不仅仅是性能,有的时候如果忘记给引用加const结果会很糟糕。比如说如果把构造函数写为Matrix::Matrix(Matrix&),看似没什么问题,但是一旦要构造MatrixM(M1+M2);呢?编译器又不干了,因为M1+M2的结果是const的。那如果我把Matrix::operator+()的返回值修改为非const的呢?会导致一些问题,比如M1+M2=M3;这类无效的语句却会通过编译。这是不被提倡的。

         这些做完之后,对照我们线性代数的书,根据矩阵的性质一条条地编写运算律。当然,这只是一小部分,我还会在学习线性代数的同时对它加以完善。做一个Matrix类是件小事,可能标准库还带了,但是更重要的是实践。通过实践可以让我们注意到平时看书时不被注意到的一些细节。编程是要求思维严密过细的。我感觉,C++继承了C语言的一些特性,保留了指针,使得底层高效的东西得以实现。又有灵活的类型系统,让STL、TR1这些杰作方便了我们编程的过程。C++确实是一个很优秀的语言,如果我们能掌握它,它就能很好地为我们服务。

博文

2012/3/3 0:43 于宿舍

(转载请保留)