13. C++基本运算符重载

基本上我们进行运算符重载时有两种形式,类内的运算符重载和顶层函数位置的运算符重载。

操作符重载指的是将C++提供的操作符进行重新定义,使之满足我们所需要的一些功能。
运算符重载的格式为:

返回值类型 operator 运算符名称 (形参表列){   }

operator是关键字,专门用于定义重载运算符的函数。我们可以将operator 运算符名称这一部分看做函数名,对于上面的代码,函数名就是operator+。

运算符重载函数除了函数名有特定的格式,其它地方和普通函数并没有区别。
在C++中可以重载的操作符有:

+  -  *  /  %  ^  &  |  ~  !  =  <  >  +=  -=  *=  /=  %=  ^=  &=  |= 
<<  >>  <<=  >>=  ==  !=  <=  >=  &&  ||  ++  --  ,  ->*  ->  ()  [] 
new  new[]  delete  delete[]

上述操作符中,[]操作符是下标操作符,()操作符是函数调用操作符。自增自减操作符的前置和后置形式都可以重载。长度运算符“sizeof”、条件运算符“:?”成员选择符“.”、对象选择符“.*”和域解析操作符“::”不能被重载。


这里我们重点看几个运算符的重载
13.1 重载输入输出
13.2 重载赋值
13.3 重载下标
13.4 重载函数调用
13.5 重载自增自减
13.6 重载转型

为了介绍基本操作符的重载,我们先来看一个操作符重载的示例。
在这个例子中,我们定义了一个复数类,一个复数包含实部和虚部两部分,我们分别用real和imag来表示复数的实部和虚部,并将这两个变量作为复数类的成员变量,并设置为private属性。在复数类中,我们定义了三个构造函数用于初始化复数类的对象。之后声明了四个操作符重载函数,分别重载加减乘除四种操作符。最后定义了一个打印复数的函数display。

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    complex operator+(const complex & A)const;
    complex operator-(const complex & A)const;
    complex operator*(const complex & A)const;
    complex operator/(const complex & A)const;
    void display()const;
private:
    double real;   //复数的实部
    double imag;   //复数的虚部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印复数
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重载加法操作符
complex complex::operator+(const complex & A)const
{
    complex B;
    B.real = real + A.real;
    B.imag = imag + A.imag;
    return B;
}

//重载减法操作符
complex complex::operator-(const complex & A)const
{
    complex B;
    B.real = real - A.real;
    B.imag = imag - A.imag;
    return B;
}

//重载乘法操作符
complex complex::operator*(const complex & A)const
{
    complex B;
    B.real = real * A.real - imag * A.imag;
    B.imag = imag * A.real + real * A.imag;
    return B;
}

//重载除法操作符
complex complex::operator/(const complex & A)const
{
    complex B;
    double square = A.real * A.real + A.imag * A.imag;
    B.real = (real * A.real + imag * A.imag)/square;
    B.imag = (imag * A.real - real * A.imag)/square;
    return B;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;
   
    //复数的加法
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;
   
    //复数的减法
    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;
   
    //复数的乘法
    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;
   
    //复数的除法
    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;
   
    return 0;
}

运算符重载的注意事项
1、并不是所有的运算符都可以重载。长度运算符sizeof、条件运算符: ?、成员选择符.和域解析运算符::不能被重载。
2、重载不能改变运算符的优先级和结合性。
3、重载不会改变运算符的用法,原有有几个操作数、操作数在左边还是在右边,这些都不会改变。

  • 重载为类成员函数时:形参个数=原操作数个数- 1(后置++、--除外)
  • 这一规则限制了重载函数写成静态形式。
  • 重载为友元函数时:形参个数=原操作数个数(后置++、--除外)

4、运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的。
5、运算符重载函数既可以作为类的成员函数,也可以作为全局函数。


顶层函数重载操作符

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    double getreal() const { return real; }
    double getimag() const { return imag; }
    void setreal(double a){ real = a; }
    void setimag(double b){ imag = b; }
    void display()const;
private:
    double real;   //复数的实部
    double imag;   //复数的虚部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印复数
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重载加法操作符
complex operator+(const complex & A, const complex &B)
{
    complex C;
    C.setreal(A.getreal() + B.getreal());
    C.setimag(A.getimag() + B.getimag());
    return C;
}

//重载减法操作符
complex operator-(const complex & A, const complex &B)
{
    complex C;
    C.setreal(A.getreal() - B.getreal());
    C.setimag(A.getimag() - B.getimag());
    return C;
}

//重载乘法操作符
complex operator*(const complex & A, const complex &B)
{
    complex C;
    C.setreal(A.getreal() * B.getreal() - A.getimag() * B.getimag() );
    C.setimag(A.getimag() * B.getreal() + A.getreal() * B.getimag() );
    return C;
}

//重载除法操作符
complex operator/(const complex & A, const complex & B)
{
    complex C;
    double square = A.getreal() * A.getreal() + A.getimag() * A.getimag();
    C.setreal((A.getreal() * B.getreal() + A.getimag() * B.getimag())/square);
    C.setimag((A.getimag() * B.getreal() - A.getreal() * B.getimag())/square);
    return C;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;
   
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;

    return 0;
}

顶层函数的形式进行操作符重载,但是因为无法直接访问complex类中的私有成员,故而我们在类中增添了getimag、getreal、setimag和setreal函数以操作类中的私有成员变量,如此一来实现这些操作符重载函数看上去就有些复杂了,不是那么直观。除了此种方法以外,我们还可以将complex类中的私有成员real和imag声明为public属性,但是如此一来就有悖类的信息隐藏机制了。

进行如下更新

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    friend complex operator+(const complex & A, const complex & B);
    friend complex operator-(const complex & A, const complex & B);
    friend complex operator*(const complex & A, const complex & B);
    friend complex operator/(const complex & A, const complex & B);
    void display()const;
private:
    double real;   //复数的实部
    double imag;   //复数的虚部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印复数
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重载加法操作符
complex operator+(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real + B.real;
    C.imag = A.imag + B.imag;
    return C;
}

//重载减法操作符
complex operator-(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real - B.real;
    C.imag = A.imag - B.imag;
    return C;
}

//重载乘法操作符
complex operator*(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real * B.real - A.imag * B.imag;
    C.imag = A.imag * B.real + A.real * B.imag;
    return C;
}

//重载除法操作符
complex operator/(const complex & A, const complex & B)
{
    complex C;
    double square = A.real * A.real + A.imag * A.imag;
    C.real = (A.real * B.real + A.imag * B.imag)/square;
    C.imag = (A.imag * B.real - A.real * B.imag)/square;
    return C;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;
   
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;

    return 0;
}

如此实现既能继承操作符重载函数是顶层函数的优势,同时又能够使操作符重载函数实现起来更简单。

13.1 重载输入与输出操作符

在C++中,系统已经对左移操作符“<<”和右移操作符“>>”分别进行了重载,使其能够用作输入输出操作符,但是输入输出的处理对象只是系统内建的数据类型。系统重载这两个操作符是以系统类成员函数的形式进行的,因此cout<< var语句可以理解为:

    cout.operator<<( var )

顶层函数的形式来实现输入操作符的重载。

istream & operator>>(istream & in, complex & A)
{
    in >> A.real >> A.imag;
    return in;
}

在上面函数中istream是指输入流。因为重载操作符函数需要用到complex类的私有成员变量,为了方便,我们将这个函数声明为complex类的友元函数。其声明形式如下:

    friend istream & operator>>(istream & in , complex & a);

该函数可以按照如下方式使用:

    complex c;
    cin>> c;

输入两个数据就分别成立复数类对象c的实部和虚部了。“cin>> c;”这一语句其实可以理解为:

    operator<<(cin , c);

在重载输入操作符时,我们采用的是引用的方式进行传递参数的,输入的参数里面包含一个istream流的引用,返回值仍然为该引用,因此我们仍然可以使用输入操作符的链式输入。

    complex c1, c2;
    cin>> c1 >> c2;

同样的,我们也可以将输出操作符进行重载,使之能够输出复数。函数在类内部的声明如下:

    friend ostream &(ostream & out, complex & A);

顶层函数的实现如下:

ostream & operator<<(ostream & out, complex & A)
{
    out << A.real <<" + "<< A.imag <<" i ";
    return out;
}

与istream一样,ostream用于表示输出流,同样为了能够直接访问complex类的私有成员变量,我们将其在类内部声明为complex类的友元函数,同样该输出操作符重载函数可以实现链式输出。

重载输出运算符

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    friend complex operator+(const complex & A, const complex & B);
//    friend istream & operator>>(istream & in, complex & A);
//    friend ostream & operator<<(ostream & out, complex & A);
    ostream & operator<<(ostream & out);
    void display()const;
private:
    double real;   //复数的实部
    double imag;   //复数的虚部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印复数
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重载加法操作符
complex operator+(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real + B.real;
    C.imag = A.imag + B.imag;
    return C;
}


////重载输入操作符
//istream & operator>>(istream & in, complex & A)
//{
//    in >> A.real >> A.imag;
//    return in;
//}

////重载输出操作符
//ostream & operator<<(ostream & out, complex & A)
//{
//    out << A.real <<" + "<< A.imag <<" i ";;
//    return out;
//}

//输出的成员函数重载
ostream& complex::operator <<(ostream & out)
{
    out << real << "+" << imag << "i" <<endl;
    return out;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;

    c3 = c1 + c2;
    //cout<<"c1 + c2 = "<<c3<<endl;
    c3 << cout;//但显然和我们想要的输出格式不符合

    return 0;
}

image.png

关于输入输出我们习惯用类外的友元函数重载形式。


13.2 重载赋值操作符

赋值操作符“=”可以用来将一个对象拷贝给另一个已经存在的对象。当然拷贝构造函数同样也会有此功能,拷贝构造函数可以将一个对象拷贝给另一个新建的对象。如果我们没有在类中显式定义拷贝构造函数,也没有重载赋值操作符,则系统会为我们的类提供一个默认的拷贝构造函数和一个赋值操作符。系统为我们提供的默认的拷贝构造函数只是将源对象中的数据一一拷贝给目标对象,而系统为类提供的赋值操作符也是这样的一种功能。

complex c1(4.3, -5.8);
complex c2;
c2 = c1;
cout<<c1<<endl;
cout<<c2<<endl;

在前面定义复数类时我们并未定义拷贝构造函数,也没有重载过赋值操作符,但是在例子中“c2 = c1”并未有语法错误,并且根据函数输出结果也可以得知可以完成我们所需要的赋值操作。这是因为系统默认为类提供了一个拷贝构造函数和一个赋值操作符,而数据一对一的拷贝也满足我们复数类的需求了。

系统提供的默认拷贝构造函数有一定缺陷,当类中的成员变量包含指针的时候就会有问题,会导致一些意想不到的程序漏洞,此时则需要重新定义一个拷贝构造函数,同样的此时系统提供的赋值操作符也已经不能满足我们的需求了,必须要进行重载。

#include<iostream>
using namespace std;

class Array
{
public:
    Array(){length = 0; num = NULL;}
    Array(int * A, int n);
    Array(Array & a);
    Array & operator= (const Array & a);
    void setnum(int value, int index);
    int * getaddress();
    void display();
    int getlength(){return length;}
private:
    int length;
    int * num;
};

Array::Array(Array & a)
{
    if(a.num != NULL)
    {
        length = a.length;
        num = new int[length];
        for(int i=0; i<length; i++)
        {
            num[i] = a.num[i];
        }
    }
    else
    {
        length = 0;
        num = 0;
    }
}

//重载赋值操作符
Array & Array::operator= (const Array & a)
{
    if( this != &a )
    {
        delete[] num;
        if(a.num != NULL)
        {
            length = a.length;
            num = new int[length];
            for(int i=0; i<length; i++)
                num[i] = a.num[i];
        }
        else
        {
            length = 0;
            num = 0;
        }
    }
    return *this;
}

Array::Array(int *A, int n)
{
    num = new int[n];
    length = n;
    for(int i=0; i<n; i++)
        num[i] = A[i];
}

void Array::setnum(int value, int index)
{
    if(index < length){
        num[index] = value;
    }
    else{
        cout<<"index out of range!"<<endl;
    }
}

void Array::display()
{
    for(int i=0; i<length; i++){
        cout<<num[i]<<" ";
    }
    cout<<endl;
}

int * Array::getaddress()
{
    return num;
}

int main()
{
    int A[5] = {1,2,3,4,5};
    Array arr1(A, 5);
    arr1.display();
    Array arr2(arr1);
    arr2.display();
    arr2.setnum(8,2);
    arr1.display();
    arr2.display();
    cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
    arr1 = arr2;
    arr1.display();
    arr2.display();
    arr2.setnum(9,3);
    arr1.display();
    arr2.display();
    cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
    return 0;
}

image.png

例子中我们以类成员函数的形式重载了赋值操作符,从arr1 = arr2语句开始看起。这个语句就会调用类中的操作符重载函数,我们可以将这一语句理解为:

    arr1.operator=( arr2 );

然后就会执行赋值操作符重载函数的函数体中的代码,在该函数体中我们为arr1重新开辟了一个内存空间,因此就可以规避arr1和arr2中的num指向同一块存储区域的风险。如此一来使用系统默认提供的赋值操作符所带来的风险就可以避免了。在这之后的语句中,我们还修改了arr2中的数据,但是这样的修改并没有影响到arr1。

当然,如果在类中并没有包含需要动态分配内存的指针成员变量时,我们使用系统提供的默认拷贝构造函数和赋值操作符也就可以了,无需再自己多此一举的重新定义和重载一遍的。


13.3 C++重载下标操作符

下标操作符是必须要以类的成员函数的形式进行重载的。其在类中的声明格式如下:

    返回类型 & operator[] (参数)
或
    const 返回类型 & operator[] (参数)

如果使用第一种声明方式,操作符重载函数不仅可以访问对象,同时还可以修改对象。
如果使用第二种声明方式,则操作符重载函数只能访问而不能修改对象。

在我们访问数组时,通过下标去访问数组中的元素并不具有检查边界溢出功能,我们可以重载下标操作符使之具有相应的功能。

#include<iostream>
#include<string>
using namespace std;

class Array
{
public:
    Array(){length = 0; num = NULL;}
    Array(int n);
    int & operator[]( int );
    const int & operator[]( int )const;
    int getlength() const {return length;}
private:
    int length;
    int * num;
};

Array::Array(int n)
{
    try
    {
        num = new int[n];
    }
    catch(bad_alloc)
    {
        cerr<<"allocate storage failure!"<<endl;
        throw;
    }
    length = n;
}

int& Array::operator[](int i)
{
    if(i < 0 || i >= length){
        throw string("out of bounds");
    }
    return num[i];
}

const int & Array::operator[](int i) const
{
    if(i < 0 || i >= length){
        throw string("out of bounds");
    }
    return num[i];
}

int main()
{
    Array A(5);
    int i;
    try
    {
        for(i = 0; i < A.getlength(); i++){
            A[i] = i;
        }
        for(i = 0 ;i < 6; i++ ){
            cout<< A[i] <<endl;
        }
    }
    catch(string s)
    {
        cerr<< s <<", i = "<< i <<endl;
    }

    return 0;
}

image.png

本例中定义了一个Array类,表示的是一个整形数组,在类中我们重载了下标操作符,使之具备检测下标溢出的功能。重载下标操作符,我们提供了两个版本的重载下标操作符函数:

    int & operator[]( int );
    const int & operator[]( int )const;

注意:
第一个下标操作符重载函数最后面不带有const,加上const意味着该成员函数是常成员函数,如果第一个函数后面也加上了const,则两个函数仅有返回值不相同,这个不足以用于区分函数,编译器会提示语法错误。

这两种版本的下标操作符重载函数其实很好理解,一个是可以修改类对象,下面一个则只可以访问对象而不能修改对象。对于上面一种下标操作符重载函数的声明,以下两个语句都是有效的:

    arr[5] = 7;
    int var = arr[3];

换言之,我们既可以访问类对象,同时又能修改类对象。“arr[5]”其实可以理解为:

    arr.operator[]( 5 )

而对于下面一种下标操作符重载函数,我们不能修改对象,也就是说语句“arr[5] = 7;”语句是无效的,但是它依然可以用于访问对象,因此“int var = arr[3];”语句仍然有效。

我们再来看一下下标操作符重载函数的定义,在函数体内部,先进行下标越界检测,如果出现越界则抛出异常,否则就返回下标 i 所对应的数据。这两种版本的下标操作符重载函数的函数定义都是如此。

注意:
非const成员函数不能处理const对象,因此通常我们在设计程序时,会同时提供两种版本的操作符重载函数。
display顶层函数,用于打印对象数组中的所有元素

void display(const Array & A)
{
    for(int i=0; i < A.getlength(); i++)
        cout<< A[i] <<endl;
}

此时如果没有定义const版本的下标操作符重载函数,则将会出现语法错误而无法编译通过的。


13.4 C++函数调用操作符重载

与下标操作符重载函数相同,我们同样需要以类成员函数的形式对函数调用操作符“()”进行重载。其声明语法只有一种形式:

    返回类型 operator()( 参数列表 );
#include<iostream>
#include<string>
using namespace std;

class Array
{
public:
    Array(){len1 = 0; len2 = 0; num = NULL; }
    Array(int m, int n);
    int & operator()(int, int);
    const int & operator()(int, int)const;
    int getlen1()const {return len1;}
    int getlen2()const {return len2;}
private:
    int len1;
    int len2;
    int * num;
};

Array::Array(int m, int n)
{
    int size = m * n;
    try
    {
        num = new int[size];
    }
    catch(bad_alloc)
    {
        cerr<<"allocate storage failure!"<<endl;
        throw;
    }
    len1 = m;
    len2 = n;
}

int & Array::operator()(int i, int j)
{
    if(i < 0 || i >= len1)
        throw string("1 out of bounds!");
    if(j < 0 || j >= len2)
        throw string("2 out of bounds!");
    return num[ i*len2 + j ];
}

const int & Array::operator()(int i, int j)const
{
    if(i < 0 || i >= len1)
        throw string("1 out of bounds!");
    if(j < 0 || j >= len2)
        throw string("2 out of bounds!");
    return num[ i*len2 + j ];
}

int main()
{
    Array A(3,4);
    int i,j;
    for(i = 0; i < A.getlen1(); i++){
        for(j = 0; j < A.getlen2(); j++){
            A(i,j) = i * A.getlen2() + j;
        }
    }
    for(i = 0; i < A.getlen1(); i++){
        for(j = 0; j < A.getlen2(); j++){
            cout<< A(i,j)<<" ";
        }
        cout<<endl;
    }
    try
    {
        cout<< A(5, 3) << endl;
    }
    catch(string s)
    {
        cerr<< s <<endl;
    }
    try
    {
        cout<< A(2, 6) << endl;
    }
    catch(string s)
    {
        cerr<< s <<endl;
    }
    return 0;
}

image.png

定义了一个Array类,这个类描述的是一个二维的数组,在类中我们先定义了一个默认构造函数,之后声明了一个带参数的构造函数“Array(int m, int n);”,所带的这两个参数分别是数组的两个维度的大小。
之后声明了一个函数调用操作符重载函数“int & operator()(int, int);”和“const int & operator()(int, int)const;”,同样的,因为只有常成员函数才能处理常对象,故依然在类中提供两个版本的函数调用操作符重载函数。
可以去看一下两个函数的函数定义,在它们的函数体中,我们先是做一个越界检测,当然对于二维数组而言,边界是有两个的,因此有两次边界检测的。如果没有越界则会返回对应的值。有了这两个函数调用操作符重载函数,我们就可以用A(i,j)的形式访问二维数组中的数据了。

当我们用A(i,j)的形式访问二维数组中的数据时,A(i,j)会调用类中的函数调用操作符重载函数,此时A(i,j)可以理解为:

    A.operator()(i, j);

主函数中异常捕获语句,我们先运行的是A(5, 3),故而是第一个边界越界了,因此先抛出“1 out of bounds!”的异常,而后又运行A(2, 6),此时为第二个边界越界,抛出“2 out of bounds!”的异常。


13.5 C++重载自增与自减操作符

自增“++”与自减“--”都是一元操作符,其前置和后置两种形式都可以被重载。有了前面介绍操作符重载的基础,我们就直接以示例的形式介绍自增与自减操作符的前置与后置重载方法。

#include <iostream>
#include <iomanip>
using namespace std;

class stopwatch
{
public:
    stopwatch(){ min = 0; sec = 0;}
    void setzero() { min = 0; sec = 0; }
    stopwatch run();               // 运行
    stopwatch operator++();        // ++ i
    stopwatch operator++(int);     // i ++
    friend ostream & operator<<( ostream &, const stopwatch &);
private:
    int min; //分钟
    int sec; //秒钟
};

stopwatch stopwatch::run()
{
    ++ sec;
    if( sec == 60 )
    {
        min ++;
        sec = 0;
    }
    return * this;
}

stopwatch stopwatch::operator++()
{
    return run();
}

stopwatch stopwatch::operator++(int n)
{
    stopwatch s = *this;
    run();
    return s;
}

ostream & operator<<( ostream & out, const stopwatch & s)
{
    out<< setfill('0')<< setw(2) << s.min
       << ":" <<setw(2) << s.sec;
    return out;
}

int main()
{
    stopwatch s1, s2;
    s1 = s2 ++;
    cout << " s1 "<< s1 <<endl;
    cout << " s2 "<< s2 <<endl;
    s1.setzero();
    s2.setzero();
    s1 = ++ s2;
    cout << " s1 "<< s1 <<endl;
    cout << " s2 "<< s2 <<endl;
    return 0;
}
image.png

定义了一个简单的秒表类,该类有两个私有成员变量min和sec,分别代表分钟和秒钟。在类中声明的成员函数setzero是用于秒表清零,run函数是用于描述秒针向前进一秒的动作,之后是三个操作符重载函数,前两个分别是重载自增操作符,最后一个是重载输出操作符。
先来看一下run函数的实现,run函数一开始让秒针自增,如果此时自增结果等于60了,则应该进位,分钟加1,秒针置零。
再来看一下operator++()函数的实现,该函数时实现自增的前置形式,因此直接返回run()函数运行结果即可。
对于operator++ ( int n )函数,这是实现自增的后置形式,自增的后置形式返回值是对象本身,但是之后再次使用该对象时,该对象自增了,因此在该函数的函数体中,先将对象保存,然后调用一次run函数,之后再将先前保存的对象返回,在这个函数中参数n是没有任何意义的,它的存在只是为了区分是前置还是后置形式。
最后我们还重载了输出操作符,以便于我们打印计时结果。

对照主函数来看程序运行结果,主函数一开始我们定义了两个对象s1和s2,第一次操作是s1 = s2 ++; 采用的是后置形式,这可以理解为s1 = s2 并且s2自增,输出结果是s1是处于置零状态,s2自增了一秒钟。之后两个对象都清零,清零后s1 = ++ s2; 这个可以理解为s2自增并将自增结果赋给s1,如此一来两个对象都自增一秒钟。

自减操作符的重载跟自增操作符类似,这里就不再赘述了。


13.6 C++重载转型操作符

重载转型操作符。转型操作符重载函数的声明语法如下:

 operator 类型名 ();

转型操作符重载函数有几点需要注意的:

  • 函数没有返回类型;
  • 虽然没有返回类型但是函数体中必须有return语句,其返回类型是由类型名来指定的;
  • 转型操作符重载函数只能以类的成员函数的形式进行重载,而不能以友元函数或顶层函数的形式进行重载。
#include <iostream>
using namespace std;

class clock
{
public:
    clock(){hour = min = ap = 0;}
    clock(int h, int m, int ap);
    operator int();
private:
    int hour;
    int min;
    int ap;  // 0表示am, 1表示pm
};

clock::clock(int h, int m, int ap)
{
    hour = h;
    min = m;
    this->ap = ap;
}

//转型操作符重载函数
clock::operator int()
{
    int time = hour;
    if(time == 12){
        time = 0;
    }
    if(ap == 1){
        time += 12;
    }
    time *= 100;
    time += min;
    return time;
}

int main()
{
    clock c(5,7,1);
    int time = c;
    cout<<time<<endl;
    return 0;
}

我们重载了一个时钟类clock,该类中我们声明了一个转型操作符重载函数,该函数可以将类类型的时间转换为一个整形,转换后的整数是军事时间。在主函数中我们定义了一个clock类的对象c,之后将其赋给一个整形变量time,因为我们定义了转型操作符重载函数,因此这一句话并没有出现语法错误。

转型操作符重载可以给程序带来一定的方便,但是建议还是谨慎使用。因为系统通常在需要的时候就会调用转型操作符重载函数,该函数的调用时隐式的,有时候会给程序带来一些意想不到的问题。


13.7 C++运算符重载注意事项

* 重载后运算符的含义应该符合原有用法习惯,重载应尽量保留运算符原有的特性。
* C++ 规定,运算符重载不改变运算符的优先级。
* 以下运算符不能被重载:.   .*   ::   ? :    sizeof。
* 重载运算符()、[]、->、或者赋值运算符=时,只能将它们重载为成员函数,不能重载为全局函数。

运算符重载的实质是将运算符重载为一个函数使用,运算符可以重载为全局函数。此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。运算符也可以重载为成员函数。此时函数的参数个数就是运算符的操作数个数减一,运算符的操作数有一个成为函数作用的对象,其余的成为函数的实参。

必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。

运算符可以重载为全局函数,然后声明为类的友元。
<<和>>是在 iostream 中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的。

类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象被自动转换为某种类型。
自增、自减运算符各有两种重载方式,用于区别前置用法和后置用法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279