前言

  上大学之前就对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++面向对象编程思想的经典案例之一,弄明白了其中的工作流程,就算这个思想的入门了,对之后的诸如应用程序、操作系统之类的大规模编程有很大的帮助。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐