C++实战笔记(一):矩阵类
前言 上大学之前就对C++有过一些了解,知道C++是一种功能十分强大的语言。大一时学习了C语言,了解了面向过程的编程方法。现在大三开始学习C++,初步接触了面向对象的编程思想,对C++功能更为着迷,其既有C语言面向过程的特点,又能兼顾对问题对象的操作,自由度很高,是底层架构和高层架构对接的完美工具。 在学习C++的过程中,我将在这里整理学习的笔记,希望能对自己和他人有所帮助。正文 第...
前言
上大学之前就对C++有过一些了解,知道C++是一种功能十分强大的语言。大一时学习了C语言,了解了面向过程的编程方法。现在大三开始学习C++,初步接触了面向对象的编程思想,对C++功能更为着迷,其既有C语言面向过程的特点,又能兼顾对问题对象的操作,自由度很高,是底层架构和高层架构对接的完美工具。
在学习C++的过程中,我将在这里整理学习的笔记,希望能对自己和他人有所帮助。
正文
第一次编程练习:创建一个矩阵类。
一、构建类
1.类的属性
矩阵的行数:m_Row;
矩阵的列数:m_Col;
矩阵的元素:m_pMatrix;
2.类的方法
构造方法:(1)默认构造;(2)方阵构造;(3)一般矩阵构造;
(4)复制构造;(5)移动构造。
运算符重载:(1)加法 + ;(2)减法 - ;(3)矩阵数乘、矩阵相乘 * ;
(4)复制赋值、移动赋值 = ;(5)输出流运算符 << 。
其他方法:(1)获取成员;(2)修改成员;(3)动态内存分配;
(4)矩阵打印。
二、代码实现
1.类的属性
private: //出于程序安全,一般要将属性设为私有
int m_Col; // 矩阵的列数
int m_Row; // 矩阵的行数
double **m_pMatrix; // 通过二重指针来实现动态矩阵
2.类的方法
[1]构造方法:
(1)默认构造
CMatrix()
{
cout << "调用默认构造函数,生成对象地址为:" << this << endl;
}; // 默认构造函数
(2)方阵构造
CMatrix(int colNum)
{
m_Col = colNum;
m_Row = colNum;
if (colNum < 0)
cout << "错误!方阵行列数不能为负数!" << endl;
else
{
alloc(m_Col, m_Row);
for (int i = 0; i < colNum; i++)
for (int j = 0; j < colNum; j++)
m_pMatrix[i][j] = 1;
cout << "调用方阵构造函数,生成对象地址为:" << this << endl;
}
}; // 方阵构造函数:初始化一个 方阵,矩阵中元素全为1
(3)一般矩阵构造
CMatrix(int colNum, int rowNum)
{
m_Col = colNum;
m_Row = rowNum;
if (colNum < 0)
cout << "错误!矩阵列数不能为负数!" << endl;
else if (rowNum < 0)
cout << "错误!矩阵行数不能为负数!" << endl;
else
{
alloc(m_Col, m_Row);
for (int i = 0; i < rowNum; i++)
for (int j = 0; j < colNum; j++)
m_pMatrix[i][j] = 1;
cout << "调用一般矩阵构造函数,生成对象的地址为:" << this << endl;
}
}; // 一般矩阵构造函数:初始化一个 一般矩阵,矩阵中元素全为1
(4)复制构造
需要注意的是,复制构造时必须将被复制的类的属性成员全部复制给要产生的新类,不能有遗漏。
我在刚写这个函数时,只复制了二维数组,忘记了复制行数和列数。
同时,还要记得,一旦创建了新的二维数组,必须分配内存。
CMatrix(const CMatrix& copy_from_me)
{
m_Col = copy_from_me.m_Col;
m_Row = copy_from_me.m_Row;
alloc(m_Col, m_Row); //将右边矩阵的行数和列数赋给左边的矩阵,便于进行之后的操作
for (int i = 0; i < m_Row; i++)
{
for (int j = 0; j < m_Col; j++)
{
this->m_pMatrix[i][j] = copy_from_me.m_pMatrix[i][j];
}
}
cout << "调用复制构造函数,生成对象的地址为:" << this << endl;
}; // 复制构造函数
(5)移动构造
这是C++11的新玩意儿,首先用一张图说明一下
由图可知,移动构造实际上有点像引用,构造期间并没有开辟新的内存,而是通过指针共用了内存。
这种办法节省了内存的开支,而且操作起来也更为方便,处理器反应会更迅速,但是由于是两个指针共享一块内存,在两个对象内进行操作时会影响同一块内存,尤其是在进行析构操作,释放内存空间时尤为危险。
解决办法就是,在构造结束后,将临时对象指向堆内存的指针赋为空指针,避免操作时互相影响。
CMatrix(CMatrix&& copy_from_me)
{
cout << "调用移动复制构造函数" << endl;
this->m_Col = copy_from_me.m_Col;
this->m_Row = copy_from_me.m_Row;
this->m_pMatrix = copy_from_me.m_pMatrix;
copy_from_me.m_pMatrix = nullptr; //右边矩阵移动复制结束后,将其指针设为空指针
//以防止修改右边矩阵时破坏了左边矩阵
}; //移动复制构造函数
不要忘记复制行数和列数。由于行数和列数是数值,并不需要修改指针。
[2]重载运算符
(1)加法 +
两相同的矩阵才能相加
CMatrix operator + (const CMatrix& rightM)
{
cout << "调用重载加法运算符" << endl;
CMatrix a; //定义一个临时矩阵a,以存储运算结果后返回
if (this->m_Col == rightM.m_Col && this->m_Row == rightM.m_Row)
{
a.alloc(m_Col, m_Row);
a.setCol(m_Col);
a.setRow(m_Row);
for (int i = 0; i < this->m_Row; i++)
{
for (int j = 0; j < this->m_Col; j++)
{
a.m_pMatrix[i][j] = this->m_pMatrix[i][j] + rightM.m_pMatrix[i][j];
}
}
return a;
}
else
{
cout << "两矩阵无法相加!" << endl;
return 0;
}
}; // 重载加法运算符加实现矩阵整体相加
(2)减法 -
CMatrix operator - (const CMatrix& rightM)
{
cout << "调用重载减法运算符" << endl;
CMatrix a;//定义一个临时矩阵a,以存储运算结果后返回
if (this->m_Col == rightM.m_Col && this->m_Row == rightM.m_Row)
{
a.alloc(m_Col, m_Row);
a.setCol(m_Col);
a.setRow(m_Row);
for (int i = 0; i < this->m_Row; i++)
{
for (int j = 0; j < this->m_Col; j++)
{
a.m_pMatrix[i][j] = this->m_pMatrix[i][j] - rightM.m_pMatrix[i][j];
}
}
return a;
}
else
{
cout << "两矩阵无法相减!" << endl;
return 0;
}
}; // 重载减法运算符加实现矩阵整体相减
(3)矩阵数乘、矩阵相乘 *
矩阵数乘的实现很简单
CMatrix operator * (double k)
{
cout << "调用重载乘法运算符" << endl;
for (int i = 0; i < this->m_Row; i++)
{
for (int j = 0; j < this->m_Col; j++)
{
this->m_pMatrix[i][j] = this->m_pMatrix[i][j] * k;
}
}
return *this;
}; // 重载乘法运算符加实现矩阵数乘
矩阵相乘要稍微麻烦些
对于“ 矩阵1 × 矩阵2 ”要注意以下特点
- 矩阵1的行数等于矩阵2的列数;
- 矩阵1的每一行与矩阵2的每一列中的每一项相乘,然后相加,即是新矩阵的每一项,如:
从其过程可以看出,实现矩阵相乘最少需要三个循环:第一个实现矩阵1行的遍历;第二个实现矩阵2列的遍历;第三个实现每一项相乘后再相加。
CMatrix operator * (const CMatrix & rightM)
{
cout << "调用重载乘法运算符" << endl;
CMatrix a{ rightM.m_Col,this->m_Row }; //定义一个临时矩阵a,以存储运算结果后返回
if (this->m_Col == rightM.m_Row) //判断两矩阵能否相乘
{
for (int i = 0; i < a.m_Row; i++)
{
for (int j = 0; j < a.m_Col; j++)
{
a.m_pMatrix[i][j] = 0;
}
}
for (int i = 0; i < this->m_Row; i++)
{
for (int j = 0; j < rightM.m_Col; j++)
{
for (int k = 0; k < rightM.m_Row; k++)
{
a.m_pMatrix[i][j] += this->m_pMatrix[i][k] * rightM.m_pMatrix[k][j];
}
}
} //三重循环实现矩阵相乘
return a;
}
else
{
cout << "两矩阵无法相乘!" << endl;
return 0;
}
}; // 重载乘法运算符加实现两矩阵相乘
(4)复制赋值、移动赋值 =
这里的“移动赋值”和上面的“移动复制”有些类似,都是利用指针来进行内容传递
复制赋值
CMatrix operator = (const CMatrix& rightM)
{
cout << "调用重载赋值运算符" << endl;
if (this != &rightM) //判断等号两边矩阵是否相等,以防止自己给自己赋值,造成内存混乱,下同
{
delete[] m_pMatrix;
m_Col = rightM.m_Col;
m_Row = rightM.m_Row;
alloc(m_Col, m_Row); //将右边矩阵的行数和列数赋给左边的矩阵,便于进行之后的操作
for (int i = 0; i < m_Row; i++)
{
for (int j = 0; j < m_Col; j++)
{
this->m_pMatrix[i][j] = rightM.m_pMatrix[i][j];
}
}
}
return *this;
}; // 重载赋值运算符
移动赋值
CMatrix operator = (CMatrix&& rightM)
{
cout << "调用重载移动赋值运算符" << endl;
if (this != &rightM)
{
this->m_Col = rightM.m_Col;
this->m_Row = rightM.m_Row;
this->m_pMatrix = rightM.m_pMatrix;
rightM.m_pMatrix = nullptr; //右边矩阵移动赋值结束后,将其指针设为空指针
//以防止修改右边矩阵时破坏了左边矩阵
}
return *this;
}; // 重载移动赋值运算符
(5)输出流运算符 <<
这里所谓的矩阵输出,实际上跟其他函数里面的“矩阵打印函数”类似,只不过利用了重载输出运算符而已,这样实际上来的便利些
friend std::ostream & operator << (std::ostream &os, const CMatrix &rightM)
{
cout << "调用重载插入运算符" << endl;
cout << "输出的矩阵为:" << endl;
for (int i = 0; i < rightM.m_Row; i++)
{
for (int j = 0; j < rightM.m_Col; j++)
{
if (j == 0)
cout << " | "; //为了美观,增加竖线分隔
cout << rightM.m_pMatrix[i][j] << " ";
}
cout << "|";
cout << endl;
}
return os;
}; // 重载插入运算符实现矩阵直接输出
[3]其他方法
(1)获取成员
double getElement(int m, int n)
{
cout << "调用特殊位置返值函数" << endl;
cout << "矩阵(" << m << ',' << n << ")位置的元素值为:";
return m_pMatrix[m][n];
};
(2)修改成员
bool setElement(int m, int n, double val)
{
cout << "调用特殊位置赋值函数" << endl;
if (m_pMatrix[m][n] = val)
return true;
else
{
cout << "设置失败" << endl;
return false;
}
}; //将矩阵(m, n)位置元素的值设为val,成功返回true, 失败返回错误提示
(3)动态内存分配
内存是按列排序的,是一维列向量,尤其在matlab中体现明显,而数组是二维的存储空间,因此如果要为数组动态分配内存,需要先为一行(列)分配内存,之后用一层循环,按行(列)为每一列(行)分配内存。本文中的矩阵是用指针来实现的,因此创建的是二维指针数组。
inline void alloc(int colNum, int rowNum)
{
cout << "调用动态内存分配函数" << endl;
m_pMatrix = (double **)malloc(sizeof(double*)*rowNum);
for (int i = 0; i < rowNum; i++)
{
m_pMatrix[i] = (double *)malloc(sizeof(double)*colNum);
}
};//实现动态内存分配,将此部分独立出来,防止代码重复出现过于累赘
(4)矩阵打印
void showIt()
{
cout << "调用打印函数;" << endl;
cout << "矩阵行数为:" << m_Row << endl;
cout << "矩阵列数为:" << m_Col << endl;
cout << "矩阵各元素为:" << endl;
for (int i = 0; i < m_Row; i++)
{
for (int j = 0; j < m_Col; j++)
{
if (j == 0)
cout << " | ";
cout << m_pMatrix[i][j] << " ";
}
cout << "|";
cout << endl;
}
}; // 打印出矩阵的行数、列数及各元素的值
后记
矩阵类是体现C++面向对象编程思想的经典案例之一,弄明白了其中的工作流程,就算这个思想的入门了,对之后的诸如应用程序、操作系统之类的大规模编程有很大的帮助。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)