C++操作符重载

1.输入和输出操作符

  对于输入和输出操作符,对其进行重载需要将操作符重载函数定义为非成员函数,否则操作符的左操作数只能是该类类型的对象。为了保证操作符重载函数能够正常访问类中定义的私有成员,需要将操作符重载函数定义为类的友元函数。下面是重载输入和输出操作符的一个简单例子。

#include <iostream>
using namespace std;
class point{
private:
    int x;
    int y;
public:
    point(int x,int y){
        this->x=x;
        this->y=y;
    }
    friend ostream &operator<<(ostream &os,const point &p);
    friend istream &operator>>(istream &is,point &p);
};
ostream & operator<<(ostream &os, const point &p) {
    os<<p.x<<" "<<p.y;
    return os;
}
istream & operator >>(istream &is,point &p){
    is>>p.x>>p.y;
    return is;
}
int main() {
    point * p = new point(1,2);
    cout<<*p<<endl;
    cin>>*p;
    cout<<*p<<endl;
    return 0;
}

运行截图:

2.算术操作符和关系操作符

   一般,将算术和关系操作符定义为非成员函数。如下定义了上述类point的加法操作符重载函数。

#include <iostream>
using namespace std;
class point{
private:
    int x;
    int y;
public:
    point(int x,int y){
        this->x=x;
        this->y=y;
    }
    friend ostream &operator<<(ostream &os,const point &p);
    friend istream &operator>>(istream &is,point &p);
    //定义了友元函数,用于重载加法运算符号
    friend point operator+(const point &p1,const point &p2);
};
ostream & operator<<(ostream &os, const point &p) {
    os<<p.x<<" "<<p.y;
    return os;
}
istream & operator >>(istream &is,point &p){
    is>>p.x>>p.y;
    return is;
}
//非成员函数,重载加法运算符
point operator+(const point &p1,const point &p2){
    auto p = new point(p1.x+p2.x,p1.y+p2.y);
    return *p;
}
int main() {
    //在堆区实例化类成员
    point * p1 = new point(1,2);
    //在栈区实例化类成员
    point p2(3,4);
    auto p = *p1 +p2;
    cout<<p<<endl;
    return 0;
}

  输出结果:

  4 6

3.赋值操作符

  对于赋值操作符的重载函数,必须要返回对*this的引用,返回赋值之后的对象。以下定义了类point的赋值重载函数。

#include <iostream>
using namespace std;
class point{
private:
    int x;
    int y;
public:
    point(int x,int y){
        this->x=x;
        this->y=y;
    }
    friend ostream &operator<<(ostream &os,const point &p);
    friend istream &operator>>(istream &is,point &p);
    //定义了成员函数,用于重载赋值操作符
    point &operator=(const point & p){
        x=p.x;
        y=p.y;
        return *this;
    }
};
ostream & operator<<(ostream &os, const point &p) {
    os<<p.x<<" "<<p.y;
    return os;
}
istream & operator >>(istream &is,point &p){
    is>>p.x>>p.y;
    return is;
}
int main() {
    //在堆区实例化类成员
    point * p1 = new point(1,2);
    point p2 = *p1;
    cout<<p2<<endl;
    return 0;
}
//输出 : 1  2

4.下标操作符

  定义下标操作符比较复杂,需要保证它在用作赋值的左右操作数的时候都能够表现正常。下标操作符出现在左边,必须生成左值,可以指定引用作为返回类型而作为左值。

  可以分为两种情况,const对象和非const对象来定义下标操作符重载函数。引用于const对象的时候,返回的值为const引用,不能被赋值。以下是一个简单的例子。

#include<iostream>
#include<vector>
using namespace std;
class fun {
private:
    vector <int> data;
public:
    void push(int x){
        data.push_back(x);
    }
    //可以用作左值
    int &operator[](const size_t index){
        return data[index];
    }
    //类的成员函数之后使用const进行修饰,表明这个函数不会对这个类对象的数据成员
    //(准确地说是非静态数据成员)作任何改变。
    //用于右值
    const int & operator[](const size_t index) const{
        return data[index];
    }
    void print(){
        for(auto a:data){
            cout<<a<<" "<<endl;
        }
    }
};
int main() {
    fun *test =new fun();
    for(int i=0;i<3;i++)
        test->push(i);
    //右值
    int value1 = (*test)[1];
    //左值
    (*test)[2]=-2;
    test->print();
    
    return 0;
}

输出:

0
1
-2

5.成员访问操作符重载

  如下,我们实现了一个简单的采用引用计数方式实现的智能指针类。指针指向的真实数据对象是类别Screen。类Screen中的数据成员x和y表示像素点的位置坐标。为了实现类Screen的智能管理,我们进一步定义了类Scrptr和ScreenPtr。类Scrptr中的数据成员分别是指向Screen对象的指针和Screen对象的引用计数,如果引用计数等于0,就销毁Screen对象。每引用一次Screen对象,就对引用计数的值加上1。类Scrptr指定类ScreenPtr为友元类,类ScreenPtr可以自由访问类ScrPtr中的私有成员。我们不通过类ScrPtr来直接访问类Screen,这是因为在下述情况下:

class ScrPtr{
public:
    //constructor use Screen*
    Scrptr(Screen * ptr){
        this->s_ptr=ptr;
        use =0;
    }
    //constructor use ScrPtr &
    Scrptr(ScrPtr & org){
        ++org.use;
        this->s_ptr =org.s_ptr;
        use =org.use;
    }
private:
    Screen * s_ptr;
    int use;
}
Screen  * screen = new Screen(1,2);
ScrPtr p1 (screen);
ScrPtr p2(p1);
ScrPtr p3(p1);
//在这种情况之下,如何更新p2的引用计数成为了问题
//可以在p1中将计数增量并且复制到p3,但是怎样更新p2中的计数

  p1、p2和p3的背后只有一个Screen对象,但是计数器的值却有两种,这样无法追踪真实的对象到底被引用了几次。这种解决方式无法跟踪对象的实际引用情况,没有实现对象和引用计数的绝对绑定。

  通过使用间接的友元类来访问类ScrPtr来间接的访问类Screen,能够解决上述问题,实现了对象和引用计数的绝对绑定,可以确定的追踪每一个对象的实际引用情况。

#include<iostream>
#include<vector>
using namespace std;
class Screen{
private:
     int x;
     int y;
public:
    Screen(int x, int y) : x(x), y(y) { }
    int getX() const {
        return x;
    }
    void setX(int x) {
        Screen::x = x;
    }
    int getY() const {
        return y;
    }
    void setY(int y) {
        Screen::y = y;
    }
    void print(){
        cout<<x<<endl<<y<<endl;
    }
};
class ScrPtr{
    friend class ScreenPtr;
    Screen *sp;
    size_t use;
    ScrPtr(Screen *p):sp(p),use(1){}
    ~ScrPtr(){delete sp;}
};
class ScreenPtr{
private:
    ScrPtr *ptr;
public:
    ScreenPtr(Screen *p):ptr(new ScrPtr(p)){}
    ScreenPtr(const ScreenPtr &orig):ptr(orig.ptr){++ptr->use;}
    ScreenPtr &operator=(const ScreenPtr & rhs) {
        //右值的引用计数加1
        ++rhs.ptr->use;
        //左值的引用计数减1
        delete this;
        //完成赋值操作
        ptr = rhs.ptr;
        //返回赋值之后对象的引用
        return *this;
    }
    Screen &operator*(){
        return *(ptr->sp);
    }
    Screen *operator->(){
        return ptr->sp;
    }
    const Screen &operator*() const{
        return *(ptr->sp);
    }
    const Screen *operator->() const{
        return ptr->sp;
    }
    ~ScreenPtr(){if(--ptr->use==0)
            delete ptr;
    }
};
int main() {
    vector<ScreenPtr *> v ;
    Screen *a = new Screen(1,2);
    Screen *b = new Screen(3,4);
    ScreenPtr *ptr_a = new ScreenPtr(a);
    ScreenPtr *ptr_b = new ScreenPtr(b);
    v.push_back(ptr_a);
    v.push_back(ptr_b);
    *ptr_a=*ptr_b;
    for(int i=0;i<v.size();i++){
        auto ptr =v[i];
        (*ptr)->print();
    }
}

   因为采取这种间接的方式访问Screen对象,通过操作符*与->并不能够直接访问类Screen,为此我们可以重载这两个成员访问操作符,调用真实的Screen对象。上述标红代码就调用了重载之后的成员访问操作符函数。

6.重载自增和自减操作符

  重载自增和自减操作符分为两种情况,分别是前缀操作符重载和后缀操作符重载,可以使用下述例子进行简要的说明。

  重点:对于后缀自增和自减操作符,编译器会自动传入一个int类型的数0作为参数进行调用,从而与前缀自增和自减操作符进行区分。

#include<iostream>
using namespace std;
class Value{
private:
    int a;
public:
    Value (int a){
        this->a =a ;
    }
    Value & operator ++ () {
        a++;
        return *this;
    }
    Value & operator ++ (int) {
        Value ret(a);
        ++a;
        return ret;
    }
    Value & operator -- (){
        a--;
        return *this;
    }
    Value &operator -- (int){
        Value ret(a);
        --a;
        return ret;
    }
    void print(){
        cout<<a<<endl;
    }
};
int main(){
    Value a(1);
    //后缀自增
    Value b = a++;
    a.print();
    b.print();
    //前缀自增
    b = ++a;
    a.print();
    b.print();
    return 0;
}

输出:

2
1
3
3

7.转换操作符重载

  转换操作符重载能够方便的利用类型转换来简化操作,如下所示:

#include<iostream>
using namespace std;
class SmallInt{
private:
    int value;
public:
    SmallInt(int value):value(value){
        if(value <0||value>256){
            throw out_of_range("bad smallint initializer!");
        }
    }
    //转换操作符重载函数无需指明返回值的类型
    operator int() const {
        return value;
    }
};
int main(){
    SmallInt a (100);
    //自动调用转换操作符,采用int数的方式输出SmallInt对象
    cout<<a<<endl;
    return 0;
}

  但是转换操作符重载可能会引起二义性问题。如下所示,我们如果同时定义了SmallInt类型和double和int之间的转换关系,在发生下述操作的时候,就会引起二义性问题:

#include<iostream>
using namespace std;
class SmallInt{
private:
    int value;
public:
    SmallInt(int value):value(value){
        if(value <0||value>256){
            throw out_of_range("bad smallint initializer!");
        }
    }
    //转换操作符重载函数无需指明返回值的类型
    operator int() const {
        return value;
    }
    operator double() const {
        return value;
    }
};
int main(){
    long double a =1.0;
    SmallInt b(1);
    cout<<a+b<<endl;
    return 0;
}

报错:

  error: use of overloaded operator '+' is ambiguous (with operand types 'long double' and 'SmallInt')

  错误在于从SmallInt转换成为long double类型是首先将SmallInt转换成int类型还是首先转换成double类型?这就存在歧义了!

参考:C++ Primer 4th

原文地址:https://www.cnblogs.com/zhoudayang/p/5499724.html