运算符重载

整理自《面向对象程序设计》

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
*/
View Code

语句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
*/
View Code

 

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
*/
View Code

由于已经将+运算符重载为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
*/
View Code

 

方式二:作为友元函数

//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
*/
View Code

 

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
*/
View Code

在operator>>()函数最后有语句return input;,就是说执行cin>>t1以后返回值是istream类的input,而input是cin对象的引用(别名),接下来再执行cin>>t2。

C++规定运算符>>(<<)重载函数的第一个参数和函数的类型都必须是istream(ostream)类型的引用,这就是为了返回cin(cout)的当前值,以便连续输入(输出)。

如果把函数定义为成员函数,那么语句cout<<t;就会被编译为cout.operator<<(t),系统认为要调用cout对象所属类ostream类的成员函数operator<<(),这样就出错了。

posted @ 2018-01-17 23:02  Johnny、  阅读(392)  评论(0编辑  收藏  举报