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 };