C++学习01:如何使用C++创建二维矩阵类并实现各类运算符的重载
文章目录
1.头文件的创建
C++对于创建一个类以及各项方法的推荐做法是在头文件.h中将类中所有的函数,变量预先定义,之后再在另一个.cpp文件中将这些所有方法一一实现,所以我们需要将我们对于矩阵类(我们之后都用CMatrix来表示这个类)的所有变量都在CMatrix.h中全部定义出来
#ifndef CMATRIX_H
#define CMATRIX_H
#include<iostream>
using namespace std;
class CMatrix
{
private:
// 我们定义二维矩阵类变量拥有三个
int m_nRow;//行数
int m_nCol;//列数
double * m_pData=NULL;//用于存储矩阵中所有值地址的指针,初始化为NULL
public:
CMatrix(); //构造器
CMatrix(int nRow, int nCol, double * pDate = NULL); //默认值不能放在变量值前面
CMatrix(const CMatrix& m);
CMatrix(const char * strPah);
~CMatrix();
bool Create(int nRow, int nCol, double* pDate);
void Set(int nRow, int nCol, double dVale);
void Release();
friend istream & operator >>(istream& is, CMatrix & m);
//全局函数
//friend关键字 在外部就可以访问到正常情况下无法访问到的私有属性和方法。
friend ostream& operator <<(ostream& os, const CMatrix &m);
CMatrix& operator = (const CMatrix& m);
CMatrix& operator+=(const CMatrix& m);
double & operator[](int nIndex);
double & operator()(int nRow,int nCol);
bool operator == (const CMatrix& m);
bool operator != (const CMatrix& m);
operator double();
};
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 // CMATRIX_H
//内联函数:就是当需要调用一个内联函数时,不是去调用而是将该函数代码整段插入到需要使用该内联函数的地方,从而省去调用过程,提高了运行速度。
//不建议超过十行的代码写成内联函数,递归函数也不建议写成内联函数
在头文件创造完之后,我们就开始对其中所有预先定义的方法进行一一实现,首先我们先对构造器进行实现
2.各类构造器的创建
接下来我们都是在CMatrix.cpp文件下实现这些方法,先引用一下头文件
#include "cmatrix.h"
#include <fstream>
#include <assert.h>
#include<iostream>
using namespace std;
2.1 不带参数的构造器
//不带参数的构造函数,我们默认自动赋值给矩阵参数0行0列,值为空
// 使用参数列表的方式进行赋值,当我们申请完参数的空间后便对该参数进行初始化
// 同时使用参数列表进行赋值的顺序不能和初始化的变量顺序不一致
CMatrix::CMatrix():m_nRow(0),m_nCol(0),m_pData(NULL)
{
}
//同时可以部分直接赋值,部分调用函数来赋值,这种写法也是可以的
CMatrix::CMatrix():m_nRow(0),m_pData(NULL)
{
m_nCol = 0
}
//该函数还可以有其他写法如下,但相比上面这种效率较低
CMatrix::CMatrix()
{
m_nCol = 0;
m_nRow = 0;
m_pData = NULL;
}
2.2 带行、列及数据指针等参数的构造函数,并且参数带默认值;
CMatrix::CMatrix(int nRow, int nCol, double * pData=NULL){
m_pData=NULL; //先将指针的值变为空
Create(nRow, nCol, pData); //在编写类之中发现对于赋值并创建类的操作会经常使用于是定义一个函数来增强复用性
}
bool CMatrix::Create(int nRow, int nCol, double *pData){
Release(); //在创建新的类空间之前先将之前占据的空间释放,防止出现bug
m_nRow = nRow;
m_nCol = nCol;
m_pData=new double[nRow*nCol]; //开辟一个浮点数数组用于存储所有的矩阵数值
if(m_pData!=NULL){ //当出现空间不够的时候,m_pData = NULL
if(pData!=NULL){
memcpy(m_pData, pData, nRow*nCol*sizeof (double)); //拷贝数组的值
}
}
return true;
}
void CMatrix::Release() //当类变量生命周期到了之后释放所占有的空间
{
if(m_pData) //如果m_pData的指针为非空则释放值
{
delete []m_pData;//删除数组需要添加'[]'
m_pData = NULL;
}
m_nRow = m_nCol=0;
}
2.3 带文件路径参数的构造函数;
在许多大规模对类的操作中,我们需要使用文件输入用来代替繁杂的手动输入数值,所以创造从文件输入来创造类变量对于类的完整性也是非常重要的:
CMatrix::CMatrix(const char * strPath)
{
m_pData = NULL;
m_nRow = m_nCol =0;
ifstream cin(strPath);//构造从硬盘到文件的输入流
cin>>*this;//所有成员函数潜藏着一个this指针
}
但是这里注意普通的 ‘>>’ 运算符只能传入一些整形数,浮点数,字符串,他是不知道如何读取我们创建的这个类,所以我们还需要对该运算符进行重载:
friend istream & operator >>(istream& is, CMatrix & m);
//这里使用friend代表友元函数,因为这个方法是pulic公开的,可他却需要访问到我们定义的private的变量
//所以我们需要使用friend告诉程序,他是友元函数他可以访问私有变量
istream & operator>>(istream& is, CMatrix & m) //重载运算符‘>>’
{
is>>m.m_nRow>>m.m_nCol; //输入流赋值行数和列数
m.Create(m.m_nRow, m.m_nCol,0); //开辟空间
for(int i=0;i<m.m_nRow*m.m_nCol;i++)
{
is>>m.m_pData[i];//逐一对矩阵内的值进行赋值
}
return is;
}
这里还需要注意一件事,就是我们从文件创造的变量,我们的文件必须符合格式,如先给出行数,列数,在一一写入矩阵的值(示例如下):
2 2
1
2
3
4
2.4 拷贝构造函数
通过将一个已经存在的类的所有值拷贝从而赋予给一个新的类
CMatrix::CMatrix(const CMatrix& m):m_pData(NULL)
{
*this = m; //拷贝构造函数
}
这里注意一件事,我们构造的这个拷贝函数和原有的变量共享一个地址,这只能算是浅拷贝,意思就是当我们修改其中一个变量的值,其他变量也会改变。
3.析构函数的创建
当一个类变量的生命周期到了尽头之后,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作,即释放内存,防止内存泄漏,当我们不写析构函数的时候,系统会自动替我们合成,但这样不确定性就影响着我们代码,所以一个完备的代码需要我们自己来完成析构函数的编写:
CMatrix::~CMatrix() //析构函数重载释放空间
{
Release(); //调用函数进行释放
}
void CMatrix::Release()
{
if(m_pData)
{
delete []m_pData;//删除数组需要添加'[]'
m_pData = NULL;
}
m_nRow = m_nCol=0;
}
4. 运算符的重载
加减乘除四则运算,在小数整数中都可以自由使用,但对于我们自己动手创造的类,程序并不知道加减乘除如何计算(如在线性代数中矩阵的乘法有他自己的一套规则),所以也需要我们自己通过对运算符进行重载来使得我们的类也可以进行加减
4.1 “ + ”, “+=” 号的重载
CMatrix& CMatrix::operator+=(const CMatrix &m) //将自身的值与另一个变量相加,返回自身
{
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;
}
4.2 " - ", " -= "的重载
减法不过是加法的逆运算,只需将上面的函数稍作修改即可:
CMatrix& CMatrix::operator-=(const CMatrix &m)
{
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;
}
4.3 关系运算符的重载
‘>’, ‘<’, '='运算符的重载
在定义大于小于符号的重定义之前,我们需要先定义一个值来表示变量的大小之分,这里我们使用矩阵所有值之和来表示他的大小:
CMatrix::operator double() //返回矩阵所有数值的总和
{
double dS=0;
for(int i=0;i<m_nRow*m_nCol;i++)
{
dS+=m_pData[i];
}
return dS;
}
//大小返回一个布尔值
bool operator > (CMatrix& m1, CMatrix& m2)
{
return double(m1)>double(m2);
}
bool operator < (CMatrix& m1, CMatrix& m2)
{
return double(m1)<double(m2);
}
//'==' 运算符的重载
//比较的规则是严格比较矩阵的所有值,是否一一相等,相等则返回True,不相等则返回False
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;
}
4.4.下标操作符的重载
既然是矩阵,我们就需要访问某些特定位置参数的功能,于是我们还需要进行 “[ ]”,"( )"这些函数功能的重载
//该运算符重载取出数据按照坐标在矩阵中的顺序,如(0,1)代表他是排序第一的所以我们访问使用下标0
double & CMatrix::operator[](int nIndex)
{
assert(nIndex<m_nRow*m_nCol);//确保访问没有超出数组最大上限
return m_pData[nIndex];
}
//该运算符重载按照坐标来直接进行获取值,如(0,1)则我们直接使用(0,1)来访问这个位置
double & CMatrix::operator()(int nRow, int nCol)
{
assert(nRow*m_nCol+nCol < m_nRow*m_nCol);
return m_pData[nRow*m_nCol + nCol];
}
4.5 赋值运算符的重载
CMatrix& CMatrix::operator=(const CMatrix &m)
{
if(this!=&m){
Create(m.m_nRow, m.m_nCol, m.m_pData);
}
//如果等号左右两个相等我们直接返回原值,不做任何修改
//如果不这样自己操作,create函数会在开辟之前直接将空间释放,那么使用m=m则会出现bug即m的值全部被清0
return *this;
}
5.输入和输出运输符:<<, >>的重载
就像java对每个类的toStrinfg()方法一样,我们同样需要定义类的如何向外输入和输出同时,由于需要访问私有变量,我们需要在定义函数的时候将他定义为友元函数
friend istream & operator >>(istream& is, CMatrix & m);
//全局函数
//friend关键字 在外部就可以访问到正常情况下无法访问到的私有属性和方法。
friend ostream& operator <<(ostream& os, const CMatrix &m);
istream & operator>>(istream& is, CMatrix & m) //重载运算符‘>>’
{
is>>m.m_nRow>>m.m_nCol;
m.Create(m.m_nRow, m.m_nCol,0);
for(int i=0;i<m.m_nRow*m.m_nCol;i++)
{
is>>m.m_pData[i];
}
return is;
}
//重载运算符‘<<’
ostream & operator<<(ostream & os, const CMatrix &m)
{
os<<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;
}
6. 运行测试代码
编写完上述这么多方法,我们当然需要测试一下代码来保证各个代码的正确性,编写测试代码如下:
#include <iostream>
#include "ccomplex.h"
#include<stdio.h>
#include "cmatrix.h"
using namespace std;
int main()
{
double pData[10] = {2, 3, 4,5 };
CMatrix m1,m2(2,5,pData),m3("D:\\1.txt"), m4(m2); //测试多个构造器方法是否正确
cin>>m1; //测试“>>”重载方法
m2.Set(1,3,10);
cout<<m1<<m2<<m3<<m4;
m4=m3; //测试“=”重载方法
m4[1]=m4+1; //测试“[]”重载方法
if(m4==m3)
{
cout<<"Error!!!!!"<<endl;
}
m4-=m3;
cout<<"sum of m4 = "<<double(m4)<<endl;//测试相减之后的m4总和
bool result = (m4>m3);
cout<<"m4 > m3 the Result is "<<result<<endl;//测试“>”重载结果
return 0;
}
实验结果:
6.结语
通过本次自己手打一个完整的二维矩类,实现了各类构造器函数功能的编写,我学会了C++中面向对象编程中的各类代码书写规范,和public,friend,private,inline等各项修饰名词在C++中的各项作用。以及学会了各项操作符的重载,了解了C++相对于java语言面向对象编程的更自由,拘束少,所以在细节方面也需更加注意。