C++笔记 —— 在模板类中重载操作符

实现了一个Matrix模板类,以此为例记录一下在模板类中重载常用的运算符。

不是所有运算符都可以重载,不能被重载的运算符有:长度运算符sizeof,条件运算符?,成员选择运算符.,域解析运算符::

重载操作符需要特别注意的一点是函数的参数表和返回值的形式。

重载操作符有两种方法,一种是重载为成员函数,一种是重载为友元。

先实现一个矩阵类模板的框架

 1 template <typename T>
 2 class Matirx{
 3 public:
 4     Matirx(int R = 0, int C = 0):row(R), col(C), values(nullptr){
 5         if(row * col != 0)
 6             values = new T[row * col]
 7         for(int i = 0; i < row * col; i++)
 8             values[i] = T(); // 注意一下这里初始化
 9     }
10 
11     ~Matirx(){
12         if(values != nullptr)
13             delete []values;
14     }
15 
16 private:
17     int row, col;
18     T * values;
19 };

为方便起见,使用一维数组储存矩阵,对于一个row * col 大小的矩阵A而言,它的第i行j列个元素表示成A[(i-1)*col+(j-1)]

接下来为该模板类重载操作符(以下函数皆为Matrix类内定义,只是为了方便说明单独把每一个函数拎到外面)

1.重载+运算符以实现两个矩阵相加

1     Matirx<T> operator+(const Matirx<T> & Other){
2         Matirx<T> temp(row, col);
3         for(int i = 0; i < row * col; i++)
4             temp.values[i] = values[i] + Other.values[i];
5         return temp;
6     }

该函数参数表使用的是 const Matrix<T> & 型的矩阵作为参数,如果把类型改成Matrix<T> 也完全不会出现编译上的问题,使用引用是为了在该函数内不必生成一个与Other完全一致的临时变量,减少开销,节约时间。同时为了防止引用会直接修改传入参数本身的值,加const限定。

该函数的返回值是Matrix<T>,该函数内的操作是产生一个和自身一样的副本,修改该副本为两矩阵相加的结果,返回该副本,而原矩阵不会被改变。

2.重载+运算符以实现矩阵+整数/浮点数/字符的操作

 1     template <typename T1>
 2     Matirx<T> operator+(T1 a){
 3         Matirx<T> temp(row, col);
 4         for(int i = 0; i < row * col; i++)
 5             values[i] += (T)a;
 6         return temp;
 7     }
 8 
 9     template <typename T1>
10     friend Matirx<T1> operator+(T1 a, const Matirx<T> & Other){
11         Matirx<T1> temp(row, col);
12         for(int i = 0; i < row * col; i++)
13             temp[i] = a + (T1)values[i];
14         return temp;
15     } 

假设A为Matrix<int>类的矩阵,且n为int类型整数,如果想要实现A+n和n+A,使得A中每一个元素都加n,前者应该实现为Matrix类的成员函数,后者应该实现为友元。因为A+n等价于A.operator+(n),是Matirx<int>类的成员函数。n+A等价于n.operator+(A),是int类的成员函数,只能重载为Matirx<T>类的友元

3.赋值运算符

1     Matirx<T>& operator=(const Matirx<T> & Other){
2         for(int i = 0; i < row * col; i++)
3             values[i] += Other.values[i];
4         return *this;
5     }

赋值运算符A=B等价于A.operator=(B),功能是改变A的值和B相同,因此无需产生一个部分,应该直接修改自身。如果赋值运算符的返回值为void,就会导致诸如A=B=C的连等写法不可使用。(A=B=C等价于(A.operator=(B)).operator=(C),如果返回值为void,第二个赋值运算符将无法使用)

4.自加运算符(自减运算符同理)

 1 Matrix<T>& operator++(){
 2     for(int i = 0; i < col * row; i++)
 3         values[i] ++;
 4     return *this;
 5 }
 6 
 7 Matrix<T> operator++(int a){
 8     Matrix<T> temp = *this;
 9     for(int i = 0; i < col * row; i++)
10         values[i] ++;
11     return temp;
12 }

以上两者的区别:

1.后者有参数而前者没有。实际上,这个参数表是任意的,该参数表的唯一作用是区分两者。前者用来执行前置的自加运算,比如++A,后者用来执行后置的自加运算,比如A++。

2.后者会生成一个零时对象,返回值是该零时对象,因此 i++ 的开销要比 ++i 大,对于两者都可以的情况(比如for循环中),应该尽量使用后者。

某些编译器在没有重载前置自加运算符时无法重载后置自加运算符,因此一般来说这两个函数是同时被重载。

5.类型转换运算符

1     template <typename T1>
2     operator Matirx<T1>(){
3         Matirx<T1> temp(row, col);
4         for(int i = 0; i < row * col; i++)
5             temp.values[i] = (T1)values[i];
6         return temp;
7     }

类型转换运算符的重载既没有参数表,也不需写返回值,因为重载函数名Matirx<T>就是返回值的类型。对于一个Matirx<int>类型的矩阵A而言,在重载了类型转换运算符后,可以使用

B=(Matrix<char>) A的方法进行类型转换。类型转换运算符最好在类内定义,类外定义会存在某些问题(主要是由于双重模板引起的作用域问题)。

6.流插入和流提取运算符

 1     template <typename T1>
 2     friend ostream & operator<<(ostream & O, const Matirx<T1> & Other){
 3         for(int i = 0; i < Other.row; i++){
 4             for(int i = 0; i < Other.col; i++)
 5                 cout << Other.values[i * Other.col + j] << "  ";
 6             cout << endl;
 7         }
 8         return O;
 9     }
10 
11     template<typename T1>
12     friend istream & operator>>(istream & I, Matirx<T1> & Other){
13         for(int i = 0; i < Other.row * Other.col; i++)
14             cin >> Other.values[i];
15         return I;
16     }

这两个运算符其实可以说的东西比较多。首先,这两个运算符的返回值其实都可以携程void,但是这样写的后果是类似cout << a << b这样的连续输出就不能用了,原因是,cout是在ostream类里的实例,cout << a << b等价于(cout.opeator<<(a)).operator<<(b)。其次,这两个函数的第一个参数一定要写引用,因为ostream和istream类是无法用户自己实例化的。最后由于这两个函数是友元,不能使用模板类的参数T,只能自己定义成模板使用另外一个表示符号T1。

7.比较运算符

1     template<typename T1>
2     bool operator<(const Matirx<T1> & Other){
3         if((row < Other.row) || (row == Other.row && col < Other.col))
4             return true;
5         else
6             return false;
7     }

对于比较运算符(<,>,!=,==),返回值是bool类型,比较判断依据可自己定义。

注意:对于STL中的算法函数,a==b被定义为a<b和b<a同时不成立,而不会调用类的==运算符。

8.圆括号运算符(函数对象)

1     T operator()(int a, int b){
2         return values[(a - 1) * col + (b - 1)];
3     }

重载圆括号运算符可以让类的实例以函数的方式被使用,重载了圆括号操作符的类又称为函数对象类

在本例圆括号运算符被重载为获取矩阵中某个元素的功能。

9.方括号运算符

方括号运算符有且只能有一个参数。而且在某些情况下必须提供两个[]运算符,两者只有声明中const的区别。为了方便此处实现的[]运算符仅是取原矩阵中的某一行作为新矩阵的功能。

 1     Matirx<T> operator[] (int r){
 2         Matirx<T> tmp(1, col);
 3         for(int i = 0; i < col; ++i)
 4             tmp.values[i] = values[(r - 1) * col + i];
 5         return tmp;
 6     }
 7 
 8     const Matirx<T> operator[] (int r) const {
 9         Matirx<T> tmp(1, col);
10         for(int i = 0; i < col; ++i)
11             tmp.values[i] = values[(r - 1) * col + i];
12         return tmp;
13     }

10.其他

除了面提到的运算符,常用的运算符还有复合运算符(比如+=,*=)和方括号运算符[](用于支持随机访问)以及delete和delete[] 运算符,由于这些运算符重载方式都大同小异,基本上能在以上的几种中找到差不多的例子,不再赘述。

完整代码如下:

  1 template <typename T>
  2 class Matirx{
  3 public:
  4     Matirx(int R = 0, int C = 0):row(R), col(C), values(nullptr){
  5         if(row * col != 0)
  6             values = new T[row * col];
  7         for(int i = 0; i < row * col; i++)
  8             values[i] = T(); // 注意一下这里初始化
  9     }
 10     ~Matirx(){
 11         if(values != nullptr)
 12             delete []values;
 13     }
 14 
 15     Matirx<T> operator+(const Matirx<T> & Other){
 16         Matirx<T> temp(row, col);
 17         for(int i = 0; i < row * col; i++)
 18             temp.values[i] = values[i] + Other.values[i];
 19         return temp;
 20     }
 21 
 22     template <typename T1>
 23     Matirx<T> operator+(T1 a){
 24         Matirx<T> temp(row, col);
 25         for(int i = 0; i < row * col; i++)
 26             values[i] += (T)a;
 27         return temp;
 28     }
 29 
 30     template <typename T1>
 31     friend Matirx<T1> operator+(T1 a, const Matirx<T> & Other){
 32         Matirx<T1> temp(Other.row, Other.col);
 33         for(int i = 0; i < Other.row * Other.col; i++)
 34             temp[i] = a + (T1)Other.values[i];
 35         return temp;
 36     } 
 37 
 38     Matirx<T>& operator=(const Matirx<T> & Other){
 39         for(int i = 0; i < row * col; i++)
 40             values[i] += Other.values[i];
 41         return *this;
 42     }
 43 
 44     Matirx<T>& operator++(){
 45         for(int i = 0; i < row * col; i++)
 46             values[i] ++;
 47         return *this;
 48     }
 49 
 50     Matirx<T>& operator++(int a){
51 for(int i = 0; i < row * col; i++) 52 values[i] ++; 53 return *this; 54 } 55 56 template <typename T1> 57 operator Matirx<T1>(){ 58 Matirx<T1> temp(row, col); 59 for(int i = 0; i < row * col; i++) 60 temp.values[i] = (T1)values[i]; 61 return temp; 62 } 63 64 template <typename T1> 65 friend ostream & operator<<(ostream & O, const Matirx<T1> & Other){ 66 for(int i = 0; i < Other.row; i++){ 67 for(int j = 0; j < Other.col; j++) 68 cout << Other.values[i * Other.col + j] << " "; 69 cout << endl; 70 } 71 return O; 72 } 73 74 template<typename T1> 75 friend istream & operator>>(istream & I, Matirx<T1> & Other){ 76 for(int i = 0; i < Other.row * Other.col; i++) 77 cin >> Other.values[i]; 78 return I; 79 } 80 81 template<typename T1> 82 bool operator<(const Matirx<T1> & Other){ 83 if((row < Other.row) || (row == Other.row && col < Other.col)) 84 return true; 85 else 86 return false; 87 } 88 89 T operator()(int a, int b){ 90 return values[(a - 1) * col + (b - 1)]; 91 } 92 93 Matirx<T> operator[] (int r){ 94 Matirx<T> tmp(1, col); 95 for(int i = 0; i < col; ++i) 96 tmp.values[i] = values[(r - 1) * col + i]; 97 return tmp; 98 } 99 100 const Matirx<T> operator[] (int r) const { 101 Matirx<T> tmp(1, col); 102 for(int i = 0; i < col; ++i) 103 tmp.values[i] = values[(r - 1) * col + i]; 104 return tmp; 105 } 106 107 private: 108 int row, col; 109 T * values; 110 };