【C++学习一】C++实战CMatrix类的创建

初识C++:CMatrix类的实现

1.头文件声明 Cmatrix.h

头文件主要用于声明CMatrix类以及类内部的属性和构造函数,和各种实现方法。

#ifndef CMATRIX_H
#define CMATRIX_H

#include <iostream>
using namespace std;

class CMatrix
{

public:
    // 构造器
    CMatrix();
    CMatrix(int nRow, int nCol, double *pData=NULL);
    CMatrix(const CMatrix &m);
    CMatrix(const char *strPath);
    // 析构函数
    ~CMatrix();
    // 初始化方法
    bool Create(int nRow, int nCol, double *pData=NULL);
    // 释放内存方法
    void Release();
    // 内联函数
    void Set(int nRow, int nCol, double dVale);
    // 友元函数,允许一个函数或类访问类的私有属性
    // 重载操作符
    friend istream & operator>>(istream& is, CMatrix & m);
    friend ostream & operator<<(ostream& os, const CMatrix &m);
    // 重载运算符
    CMatrix& operator=(const CMatrix &m);
    CMatrix& operator+=(const CMatrix &m);
    CMatrix& operator-=(const CMatrix& m);
    bool operator ==(const CMatrix& m);
    bool operator !=(const CMatrix& m);
    
    double & operator[](int nIndex);
    double & operator()(int nRow, int nCol);
    // 重载类型转换
    operator double();

// vscode
private:
    int m_nRow;
    int m_nCol;
    double *m_pData = NULL;
};


// 重载"+"运算符
CMatrix operator+(const CMatrix& m1, const CMatrix& m2);
CMatrix operator-(const CMatrix& m1, const CMatrix& m2);

// 内联函数(修改矩阵某元素的值)
// 在编译时, 编译器会把内联函数的代码块放置在每个调用该函数的地方
inline void CMatrix::Set(int nRow, int nCol, double dVal)
{
    m_pData[nRow*m_nCol+nCol]=dVal;
}

#endif

其中,这三行代码属于条件编译宏定义,可以根据条件选择性的只编译某段程序,也可以防止重复定义。

#ifndef CMATRIX_H
#define CMATRIX_H
// ... ...
#endif

2.类内部方法,函数的实现 CMatrix.cpp

2.1构造器(Constructor)

构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统会为这个对象的实例进行默认的初始化。如果想改变这种默认的初始化,就可以通过自定义构造器来实现。
由于c++面向对象语言的特性,构造器可以实现重载,即一个类可以有多个构造器。一个类的构造器的名称必须与该类的名称一致。

2.1.1 缺省构造器
// 无参构造器:
CMatrix::CMatrix()
{
    m_nRow = 0;
    m_nCol = 0;
    m_pData = NULL;
}

无参构造器(使用初始化表达式):

// 无参构造器(使用初始化表达式)
// 其中传入参数的顺序和在类间定义时的顺序一致
// 在初始化时更有效率
CMatrix::CMatrix():m_nRow(0),m_nCol(0),m_pData(0)
{
    //初始化为NULL
}
2.1.2 有参构造器
//有参构造器
CMatrix::CMatrix(int nRow, int nCol, double *pData):m_pData(0)
{
    Create(nRow,nCol,pData); // 调用新建类对象方法
}

拷贝构造函数

// 拷贝构造函数
// 使用一个已经创建完毕的对象来初始化一个新对象
// 该新对象是原有对象的浅拷贝
CMatrix::CMatrix(const CMatrix& m):m_pData(0)
{
    *this = m; // *this表示对象指针,因此只赋值了地址
}

外部数据流构造函数

// 外部数据流构造函数
CMatrix::CMatrix(const char * strPath):m_nRow(0),m_nCol(0),m_pData(0)
{
    ifstream cin(strPath); //通过ifstream定义输入流对象
    //将输入流地址赋值给对象指针
    cin>>*this;
}

2.2析构函数(Destructor)

析构函数与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

// 析构函数
// 析构函数无参, 不可重载
// 程序在对象销毁前自动调用析构函数,无需手动调用
CMatrix::~CMatrix()
{
    Release(); // 调用类方法
}

2.3 CMatrix 对象方法

2.3.1对象初始化

// 新建类对象方法
bool CMatrix::Create(int nRow, int nCol, double *pData)
{
    // 首先在构造前需将其数据指针赋值为空
    Release(); 
    // 赋值
    m_pData = new double[nRow*nCol];
    m_nRow = nRow;
    m_nCol = nCol;
    if(pData != NULL)
    {
        // 将传入的pData赋值给类内部变量m_pData(内存拷贝的方式)
        memcpy(m_pData, pData, nRow*nCol*sizeof(double));
    }
}

2.3.2 对象销毁方法

// 销毁对象方法
void CMatrix::Release()
{
    //如果指针非空将其指向空
    if(m_pData != NULL)
    {
        delete []m_pData;
        m_pData = NULL;
    }
    //初始化0
    m_nRow = m_nCol = 0;
}

2.4 CMatrix 运算符重载

在c++中,可以重定义或重载大部分内置的运算符。这样就可以使用自定义类型的运算符,实现更为复杂的运算。

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

2.4.1 赋值运算符重载

//运算符重载(对已有运算符重新定义,赋予其另一种功能,以适应不同的数据类型)
// "="重载
CMatrix& CMatrix::operator=(const CMatrix& m)
{
    //如果自己对自己赋值就直接跳过,这是因为Create方法会首先调用Release(),导致原数据被释放
    if(this!=&m){
        // 这里的“=”赋值采用Create方法,是深拷贝
        Create(m.m_nRow, m.m_nCol, m.m_pData);
    }
    return *this;
}

2.4.2关系运算符重载

运算符”==“重载

//运算符”==“重载
bool CMatrix::operator == (const CMatrix& m)
{
    //如果两者连尺寸都不相等则直接返回不相等
    if(!(m_nRow==m.m_nRow && m_nCol==m.m_nCol)){
        return false;
    }
    //否则一个个比较元素
    for(int i=0;i<m_nRow*m_nCol;i++)
    {
        if(m_pData[i]!=m.m_pData[i]){
            return false;
        }
    }
    return true;
}

“!=“运算符重载

// !=运算符重载
bool CMatrix::operator !=(const CMatrix& m){
    //"=="重载实现"!="重载
    return !((*this)==m);
}

2.4.3运算符重载

// "+="重载
CMatrix& CMatrix::operator+=(const CMatrix& m)
{
    //assert断言函数,对括号内的假设进行判断,假如不符合条件就抛出错误,终止程序运行
    // 这里的断言函数保证运算符两边的size相等
    assert(m_nRow==m.m_nRow && m_nCol==m.m_nCol);
    for (int i=0;i<m_nRow*m_nCol;i++){
        //内部实现是一个个赋值
        m_pData[i]+=m.m_pData[i];
    }
    return *this;
}

"-="重载

// "-="重载
CMatrix& CMatrix::operator-=(const CMatrix& m)
{
    //assert断言函数,对括号内的假设进行判断,假如不符合条件就抛出错误,终止程序运行
    // 这里的断言函数保证运算符两边的size相等
    assert(m_nRow==m.m_nRow && m_nCol==m.m_nCol);
    for (int i=0;i<m_nRow*m_nCol;i++){
        //内部实现是一个个赋值
        m_pData[i]-=m.m_pData[i];
    }
    return *this;
}

"+"重载

// "+"重载
CMatrix operator+(const CMatrix& m1, const CMatrix& m2)
{
    //"+="重载实现"+"重载
    CMatrix m3(m1);
    
    m3 += m2;
    return m3;
}

"-"重载

// "-"重载
CMatrix operator-(const CMatrix& m1, const CMatrix& m2)
{
    //"-="重载实现"-"重载
    CMatrix m3(m1);
    m3 -= m2;
    return m3;
}

2.4.4 操作符重载

下标操作符[]重载

// 下标操作符[]重载
double & CMatrix::operator[](int nIndex)
{
    //保证下标不越界
    assert(nIndex<m_nRow*m_nCol);
    return m_pData[nIndex];
}

操作符()重载

// 操作符()重载
// a(2,5)读取矩阵a的第二行第五列
double & CMatrix::operator()(int nRow, int nCol)
{
    //保证下标不越界
    assert(nRow * m_nCol + nCol < m_nRow * m_nCol);
    return m_pData[nRow * m_nCol + nCol];
}

操作符”>>“重载

// 重载操作符”>>“
// 使得“>>”操作符能够读取 CMatrix 数据类型
istream & operator>>(istream& is, CMatrix & m)
{
    is>>m.m_nRow>>m.m_nCol;
    // 在读取矩阵之前先初始化
    m.Create(m.m_nRow, m.m_nCol);
    // 具体实现是一行行赋值
    for(int i=0;i<m.m_nRow*m.m_nCol;i++)
    {
        is>>m.m_pData[i];
    }
    return is;
}

操作符”<<“重载

// 重载操作符”<<“
// 使得“<<”操作符能够打印 CMatrix 数据类型
ostream & operator<<(ostream& os, const CMatrix &m)
{
    os<<"size:["<<m.m_nRow<<","<<m.m_nCol<<']'<<endl;
    double * pData = m.m_pData;
    // 按行列顺序输出矩阵元素
    for(int i=0;i<m.m_nRow;i++)
    {
        for(int j=0;j<m.m_nCol;j++)
        {
            os<<*pData++<<" ";
        }
        os<<endl;
    }
    return os;
}

重载强制类型转换

// 重载强制类型转换
CMatrix::operator double()
{
    double dS=0;
    // 将类型转换重载为矩阵所有元素相加
    for(int i=0;i<m_nRow*m_nCol;i++){
        dS+=m_pData[i];
    }
    return dS;
}

3.主函数测试样例main.cpp

#include <iostream>
#include <stdio.h>
#include "cmatrix.h"
using namespace std;


int main(int argc, char** argv) {
    double pData[10]={2,3,4,5};
    CMatrix m1, m2(2,5,pData), m3("../1.txt"), m4(m2);
    cin>>m1;
    m2.Set(1,3,10);
    m4=m3;
    m4[2] = m4 + 1;
    cout<<m1<<m2<<m3<<m4;
    if(m4 == m3)
    {
        cout<<"Error !"<<endl;
    }
    m4 -= m3;
    cout<<"m4 -= m3:\n"<<m4;
    cout<<"m4 -= m3 = "<<(double)m4<<endl;

    // double data[10] = {1,2,3,4,5,6}
    // CMatrix ms[3] = {CMatrix(), CMatrix(), "../1.txt"}

    return 0;
}

运行结果:

pi@raspberrypi:~/Desktop/cpp/build $ ./main
2 2 
4 4 4 4
size:[2,2]
4 4 
4 4 
size:[2,5]
2 3 4 5 0 
0 0 0 10 0 
size:[2,2]
1 2 
3 4 
size:[2,2]
1 2 
11 4 
m4 -= m3:
size:[2,2]
0 0 
8 0 
m4 -= m3 = 8
pi@raspberrypi:~/Desktop/cpp/build $

值得注意的是,在执行m4 + 1(或者1 + m4)运算的过程中,程序会默认先将m4解析为(double)m4,然后再加1。(这里先留一个小疑问)

还有就是对于双目运算符"+" , "-"的重载,必须定义在类的外部,否则编译过程中会报错。

Introduction ============ This is a class for symmetric matrix related computations. It can be used for symmetric matrix diagonalization and inversion. If given the covariance matrix, users can utilize the class for principal component analysis(PCA) and fisher discriminant analysis(FDA). It can also be used for some elementary matrix and vector computations. Usage ===== It's a C++ program for symmetric matrix diagonalization, inversion and principal component anlaysis(PCA). To use it, you need to define an instance of CMatrix class, initialize matrix, call the public funtions, and finally, free the matrix. For example, for PCA, CMarix theMat; // define CMatrix instance float** C; // define n*n matrix C = theMat.allocMat( n ); Calculate the matrix (e.g., covariance matrix from data); float *phi, *lambda; // eigenvectors and eigenvalues int vecNum; // number of eigenvectors (<=n) phi = new float [n*vecNum]; lambda = new float [vecNum]; theMat.PCA( C, n, phi, lambda, vecNum ); delete phi; delete lambda; theMat.freeMat( C, n ); The matrix diagonalization function can also be applied to the computation of singular value decomposition (SVD), Fisher linear discriminant analysis (FLDA) and kernel PCA (KPCA) if forming the symmetric matrix appropriately. For data of very high dimensionality (n), the computation of nxn matrix is very expensive on personal computer. But if the number m of samples (vectors) is smaller than dimenionality, the problem can be converted to the computation of mxm matrix. The users are recommended to read the paper KPCA for how to form mxm matrix: B. Sch枚lkopf, A. Smola, K.-R. M眉ller. Nonlinear component analysis as a kernel eigenvalue problem, Neural Computation, 10(5): 1299-1319, 1998. Example ======= Refer to `example' directory for a simple demonstration.
实现有两个 CVector 存放数据的自定义动态数组,采用一维动态数组存储矩阵数据 CMatrix 实现的矩阵 使用的时候包含#include "Matrix.h"就行 CMatrix的接口函数都在"Matrix.h"里面 CVector的接口函数在"Vector.h"里,"Matrix.h"里包含了"Vector.h" 具体用法与测试用例Main.cpp里有3个测试用例,分别是针对构造函数属性计算与运算符重载的 内已包含测试工程xp\vc6.0\上亲测通过,并经过BoundsChecker测试没有内存泄漏。有兴趣的童鞋可以下作参考。 注意: 1、下标都是从0开始,数学课上矩阵下标都是从1开始,但是工作后习惯0开始,矩阵M的第一个元素是M(0,0) 2、型定死为double,原来作业是模板,由于vc6对模版支持不好,另矩阵计算double比较理想、整型几乎只能作加减 提供了多种初始化方式,int[]、float[]、double[]均可构造初始化,或则先构造出CVector再由CVector初始化。 3、定义了一个最大允许误差#define permit_eof (1.0e-13),判断相等使用宏 #define EQUAL(a,b) ( ((b) - (a) < permit_eof)&&((a) - (b) < permit_eof) ? (TRUE) : (FALSE) ) 正常输出的时候绝对值小于permit_eof 的时候清零处理,想要指定精度输出请用PrintOut 鸣谢:CSDN上supermegaboy君的C/C++左值性精髓,读后略有所感,空闲时重构了下大学时的作业,着重区分了函数返回值的左右值 =================================================附录:接口函数========================================================================== 开放的接口: CVector //构造函数 CVector(); virtual ~CVector(); CVector(const size_t &nSize;); CVector(const CVector & vIn);//拷贝构造函数 CVector(const double* const pData,const size_t &nSize;); CVector(const float* const pData,const size_t &nSize;); CVector(const int* const pData,const size_t &nSize;); //公开的成员函数 double at(const size_t& uIndex)const;//作右值 BOOL push_back(const double& dbIn ); BOOL resize(const size_t& nSize); size_t size()const; //重载操作符 double& operator()(const UINT& uIndex);//重载()运算符,可作左值 //重载的运算符 double& operator()(const size_t& xr,const size_t& xc);//重载()运算符,可作左值 CVector& operator=(const CVector &);//重载=运算符 double operator*(const CVector & )const;//重载*运算符,两向量相乘 CVector operator*(const double α)const;//重载*运算符,向量乘以实数alpha CVector& operator*=(const double α);//重载*=算符,向量乘以实数alpha CVector operator+(const CVector & )const;//重载+运算符,向量加上向量 CVector& operator+=(const CVector & );//重载+=算符,向量加上向量 CVector operator-(const CVector & )const;//重载+运算符,向量加上向量 CVector& operator-=(const CVector & );//重载+=算符,向量加上向量 CVector operator+(const double α)const;//重载+运算符,向量加上实数alpna CVector& operator+
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值