【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。(这里先留一个小疑问)

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

猜你喜欢

转载自blog.csdn.net/SESESssss/article/details/120639288