zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    而立之年的程序员创业者,写给不甘平凡的自己和80、90后!
    无焦虑,不成长!三大方法让你走出焦虑!
    [Chat]实战:仿网易云课堂微信小程序开发核心技术剖析和经验分享
    [干货教程]仿网易云课堂微信小程序开发实战经验
    微信公众号支付之坑:调用支付jsapi缺少参数 timeStamp等错误解决方法
    微信支付之扫码支付开发:我遇到的坑及解决办法(附:Ecshop 微信支付插件)
    OpenCV 4.3 编译和配置
    OpenCV 之 基本绘图
    OpenCV 之 空间滤波
    Qt 地址薄 (二) 添加地址
  • 原文地址:https://www.cnblogs.com/zhoudayang/p/5499724.html
Copyright © 2011-2022 走看看