运算符重载
整理自《面向对象程序设计》
3.1什么是运算符重载
为了实现两个Time类对象的加法运算,可以写出如下语句:
Time t1,t2;//定义时间类对象t1、t2 t1=Tadd(t1,t2);//调用函数Tadd()计算两个时间的和 //显然这种调用方式不直观,太繁琐 t1=t1+t2//使用运算符重载可以直接用加好来实现时间的加法运算
所谓重载,就是重新赋予新的含义。运算符重载是将系统中已有的运算符赋予不同的意义。使用运算符重载可以使C++的代码更直观、更易懂、更灵活,使得用户自定义的数据类型以一种更方便、更简洁的方式工作。
由于运算符也是函数,所以在用户自定义的类可以去重载这些函数。运算符重载的方法就是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。
运算符通常是对类中的私有成员进行操作,故重载运算符应能访问类中的私有成员,所以重载运算符一般采用成员函数或友元函数的形式。
3.2重载运算符的规则
- 重载运算符可以对运算符做出新的解释,但原有的基本语义不变。
- 不改变运算符的优先级和结合性。
- 不改变运算符所需要的操作数,即单目运算符只能重载为单目运算符,不能将单目运算符重载为双目运算符。
- 不能创建新的运算符,只有系统预定义的运算符才能被重载,除作用域操作符 :: 条件操作符 ? 点操作符 . 指向成员操作的指针操作符 ->*,.*预处理符号:#外 ,其他系统预定义的运算符都可以被重载。
- 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数。
- 重载的运算符必须和用户自定义类型的对象一起使用,其参数至少应该有一个是类对象或类对象的引用。
- 用于类对象的运算符一般必须重载,但有两个例外,运算符 = 和 &,用户不必重载这两个运算符。
- 运算符重载函数可以是类的成员函数,也可以是类的友元函数。对于=、()、[] 和 ->,运算符只能用成员函数的方式进行重载,对于 << 和 >> 运算符必须用友元函数的方式进行重载。
3.3运算符重载函数作为类的成员函数
可以将运算符重载函数作为类的成员函数,方法是在类中定义一个同名的运算符函数来重载该运算符。
定义运算符重载函数的格式如下:
函数类型 operator 运算符名称(形参表){ 函数体; } //例如 Time operator+(time t)
其中operator+ 为函数名,形参表中是运算符要求的操作数。在定义重载运算符的函数后,可以说函数operator+重载了运算符+。
【对自定义时间类Time 实现加法操作】
//Time.h class Time { private: int hour; int minute; int sec; public: Time():hour(0), minute(0), sec(0) {}; Time(int x, int y , int z ) :hour(x), minute(y), sec(z) {}; Time operator+(Time &t); void show(); }; //Time.cpp #include<iostream> using namespace std; #include"Time.h" Time Time::operator+(Time &t) { Time mt; int jinwei = 0; mt.sec = sec + t.sec; if (mt.sec >= 60) { mt.sec -= 60; jinwei = 1; } mt.minute = minute + t.minute+jinwei; jinwei = 0; if (mt.minute >= 60) { mt.minute -= 60; jinwei = 1; } mt.hour = hour + t.hour + jinwei; return mt; } void Time::show() { cout << hour << ":" << minute << ":" << sec; } //main.cpp #include"Time.h" #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; int main() { Time t1(10, 10, 10), t2(20, 55, 55), t3; t3 = t1 + t2; t1.show(); cout << "+"; t2.show(); cout << "+"; cout << "="; t3.show(); cout << endl; getchar(); } /* 10:10:10+20:55:55+=31:6:5 */
语句t3 = t1 + t2;在编译时变成什么样子呢?由于已经将+运算符重载为Time类的成员函数,这一语句首先编译成通过对象来调用类的成员函数的形式:t3 = t1.operatro+(t2);,在每一个成员函数中都包含一个this指针。这样在执行,C++把它处理为t3=t1.operator+(&t1,t2);即给operator+()函数新增一个参数&t1。对于Time::operator+()函数,C++把它处理为如下形式:
Time Time::operator+(Time *this, Time &t){ Time mt; int jinwei = 0; mt.sec = this->sec + t.sec; if (mt.sec >= 60) { mt.sec -= 60; jinwei = 1; } mt.minute = this->minute + t.minute + jinwei; jinwei = 0; if (mt.minute >= 60) { mt.minute -= 60; jinwei = 1; } mt.hour = this->hour + t.hour + jinwei; return mt; }
【对自定义的时间类Time重载运算符“!”作为其成员函数,用于判断时间对象是否为0】
//Time.h class Time { private: int hour; int minute; int sec; public: Time():hour(0), minute(0), sec(0) {}; Time(int x, int y , int z ) :hour(x), minute(y), sec(z) {}; Time operator+(Time &t); int operator!();//声明重载的“!”运算符 void show(); }; //Time.cpp #include<iostream> using namespace std; #include"Time.h" int Time::operator!() { if ((hour == 0) && (minute == 0) && (sec == 0)) return 1; else return 0; } Time Time::operator+(Time &t) { Time mt; int jinwei = 0; mt.sec = sec + t.sec; if (mt.sec >= 60) { mt.sec -= 60; jinwei = 1; } mt.minute = minute + t.minute+jinwei; jinwei = 0; if (mt.minute >= 60) { mt.minute -= 60; jinwei = 1; } mt.hour = hour + t.hour + jinwei; return mt; } void Time::show() { cout << hour << ":" << minute << ":" << sec; } //main.cpp #include"Time.h" #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; int main() { Time t1(10, 10, 10), t2; if (!t1) cout << "t1 is 0" << endl; else cout << "t1 is not 0" << endl; if (!t2) cout << "t2 is 0" << endl; else cout << "t2 is not 0" << endl; getchar(); } /* t1 is not 0 t2 is 0 */
3.4运算符重载函数作为类的友元函数
【为了便于理解,重载函数operator+()不作为Time类的成员函数,而是把重载函数放在类外,作为Time类的友元函数】
//Time.h class Time { private: int hour; int minute; int sec; public: Time():hour(0), minute(0), sec(0) {}; Time(int x, int y , int z ) :hour(x), minute(y), sec(z) {}; friend Time operator+(Time &t1, Time &t2);//重载“+”运算符函数作为友元函数 void show(); }; //Time.cpp #include<iostream> using namespace std; #include"Time.h" void Time::show() { cout << hour << ":" << minute << ":" << sec; } //main.cpp #include"Time.h" #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; Time operator+(Time &t1, Time &t2) { Time mt; int jinwei = 0; mt.sec = t1.sec + t2.sec; if (mt.sec >= 60) { mt.sec -= 60; jinwei = 1; } mt.minute = t1.minute + t2.minute + jinwei; jinwei = 0; if (mt.minute >= 60) { mt.minute -= 60; jinwei = 1; } mt.hour = t1.hour + t2.hour + jinwei; return mt; } int main() { Time t1(10, 10, 10), t2(20,55,55),t3; t3 = t1 + t2; t1.show(); cout << "+"; t2.show(); cout << "="; t3.show(); cout << endl; getchar(); } /* 10:10:10+20:55:55=31:6:5 */
由于已经将+运算符重载为Time类的友元函数,因此语句t3+t1+t2;编译称为调用普通函数的形式:t3=operator+(t1,t2);。
二者区别?(精髓)
运算符重载函数可以是类的成员函数,也可以是类的友元函数,那么二者有何区别,在参数个数和调用方式有什么不同?
把Time类的定义及主函数修改如下:
class Time{ private: int hour,minute,sec; public: Time(){hour=0;minute=0;sec=0;} Time(int x,int y,int z){hour=x,minute=y;sec=z;} Time(int z){hour=0;minute=0;sec=z;} friend Time operator+(Time &t1,Time &t2); }; int main(){ Time t1(10,10,10),t2; t1=t1+45; t2=45+t1; return 0; }
编译系统怎样处理语句t2=t1+45;呢?在编译时,系统发现运算符左侧的t1是Time类对象,右侧45是一个整数。那么编译系统首先寻找有没有对+运算符的重载,发现有operator+()函数,它是类的友元函数,要求两个Time类的形参,而现在45是一个整数,不符合要求。然后编译系统就去找有没有转换构造函数,发现有Time(int z)这个转换构造函数,于是去调用Time(int z)这个转换构造函数,将45转换为Time类常量后,才去调用operator+()函数。该过程相当于执行语句t2=t1+Time(45);同理,语句t2=45+t1;相当于执行语句t2=Time(45)+t1;
如果主函数不变,修改Time类的定义如下:
class Time{ private: int hour,minute,sec; public: Time(){hour=0;minute=0;sec=0;} Time(int x,int y,int z){hour=x,minute=y;sec=z;} Time(int z){hour=0;minute=0;sec=z;} Time operator+(Time &t); };
编译系统怎样处理语句t2=t1+45;呢?在编译时,编译系统首先寻找有没有对+运算符的重载,发现有operator+()函数,它是类的成员函数,要求一个Time类的形参,而现在45是一个整数,不符合要求。然后编译系统就去找有没有转换构造函数,发现有Time(int z)这个转换构造函数,于是去调用Time(int z)这个转换构造函数,将45转换为Time类常量后,才去调用t1.operator+()函数。相当于执行语句t2=t1.operator+(Time(45));而语句t2=45+t1;会编译成t2=45.operator+(t1),由于45不是Time类对象不能调用Time类的成员函数,所以编译出错。
成员函数重载的+运算符不支持交换律,从这个例子中可以看出在第一个参数需要隐式转换的情形下,使用友元函数重载运算符是正确的选择。
什么时候应该用成员函数,什么时候应该用友元函数重载?
由于友元的使用会破坏类的封装,因此从原则上说,要尽量将运算符函数作为类的成员函数。但考虑到各方面的因素,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。另外,C++规定,有的运算符(=、()、[ ]、->)必须定义为类的成员函数,(流插入运算符<<和流提取运算符>>、类型转换运算符)则使用友元函数重载。若一个运算符的操作需要修改类对象的状态时(如=、*= 和 ++),应该用成员函数重载,如果运算符的左右操作数类型不同,左操作数可以是常数或其他类型的数时,希望有隐式转换,则必须用友元函数。
3.5重载++和--运算符
++和--运算符属于单目运算符,但是它们有两种使用方式,即前置方式++x 和后置方式 x++,其意义也不相同。前置方式是先自加,返回的是修改后的对象本身,而后置方式返回的是自加前的对象,然后对象自加。C++对此做了约定:在自增(自减)运算符重载中,增加(减少)一个int型形参,就是后置自增(自减)运算符函数,否则就是前置方式。
int在这里只是一个占位符,用来区分函数是前置还是后置,并没有实际意义。
【对Time类进行自增运算,每次走一秒,假设当前秒数小于59】
方式一:作为成员函数
//Time.h class Time { private: int hour; int minute; int sec; public: Time():hour(0), minute(0), sec(0) {}; Time(int x, int y , int z ) :hour(x), minute(y), sec(z) {}; Time operator++() { sec++; return *this; } Time operator++(int x) { Time t; t = *this; sec++; return t; } void show() { cout << hour << ":" << minute << ":" << sec<<endl; }; }; //main.cpp #include"Time.h" #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; int main() { Time t1(10, 10, 10), t2,t3; t1.show(); t2 = ++t1; t1.show(); t2.show(); t3 = t1++; t1.show(); t3.show(); getchar(); } /* 10:10:10 10:10:11 10:10:11 10:10:12 10:10:11 */
方式二:作为友元函数
//Time.h class Time { private: int hour; int minute; int sec; public: Time():hour(0), minute(0), sec(0) {}; Time(int x, int y , int z ) :hour(x), minute(y), sec(z) {}; friend Time operator++(Time &t) { t.sec++; return t; } friend Time operator++(Time &t,int x) { Time tt; tt = t; t.sec++; return tt; } void show() { cout << hour << ":" << minute << ":" << sec<<endl; }; }; //main.cpp #include"Time.h" #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; int main() { Time t1(10, 10, 10), t2,t3; t1.show(); t2 = ++t1; t1.show(); t2.show(); t3 = t1++; t1.show(); t3.show(); getchar(); } /* 10:10:10 10:10:11 10:10:11 10:10:12 10:10:11 */
3.6重载流插入运算符和流提取运算符
在程序中,人们希望能用流插入运算符<<来输入用户自己声明的类的对象的信息,用流提取运算符>>来输入用户自己声明的类的对象的信息,这就需要重载流插入运算符<<和流提取运算符>>。
将<<和>>重载为友元函数的形式如下:
istream & operator>>(istream &,自定义类 &);
ostream & operator<<(ostream &,自定义类& ) ;
cin是istream类的对象,cout是ostream类的对象。当执行cout<<3时,该语句被编译为cout.operator<<(3);然后去调用ostream类的operator<<()函数,而且调用后的返回值仍然是cout。
【在Time类上,重载流插入运算符<<和流提取运算符>>】
//Time.h #include<iostream> using namespace std; class Time { private: int hour; int minute; int sec; public: friend ostream& operator<<(ostream &output,Time &t); friend istream& operator>>(istream &input,Time &t); }; //Time.cpp #include<iostream> using namespace std; #include"Time.h" ostream& operator<<(ostream& output, Time &t) { output << t.hour << ":" << t.minute << ":" << t.sec; return output; } istream& operator>>(istream& input, Time &t) { cout << "input hour,minute and second of a time:"; input >> t.hour >> t.minute >> t.sec; return input; } //main.cpp #include"Time.h" #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; int main() { Time t1, t2; cin >> t1 >> t2; cout << "t1=" << t1 << endl; cout << "t2=" << t2 << endl; getchar(); } /* input hour,minute and second of a time:10 20 30 input hour,minute and second of a time:4 5 6 t1=10:20:30 t2=4:5:6 */
在operator>>()函数最后有语句return input;,就是说执行cin>>t1以后返回值是istream类的input,而input是cin对象的引用(别名),接下来再执行cin>>t2。
C++规定运算符>>(<<)重载函数的第一个参数和函数的类型都必须是istream(ostream)类型的引用,这就是为了返回cin(cout)的当前值,以便连续输入(输出)。
如果把函数定义为成员函数,那么语句cout<<t;就会被编译为cout.operator<<(t),系统认为要调用cout对象所属类ostream类的成员函数operator<<(),这样就出错了。