• C++-典型双、单目操作符重载,输入输出操作符重载,其他操作符重载及限制(day7)


     一、双目操作符重载

    1、运算类双目操作符(更多见昨天的笔记)

    昨天讲的L.operator#(R)是成员函数的形式,

    如:c1+c2=c1.operator+(c2);

    也可以被编译器处理为::operator(L,R)的全局函数的形式,该函数的返回值也是表达式的值。

    如:c1+c2=::operator(c1,c2);

    注意:

      编译器因为会提供默认的拷贝构造函数,所以在实现了+重载之后,可以直接使用+,实现+=重载

    注意:

      全局的重载不能和局部的重载同时存在

    class Complex{
    
    public:
    
      Complex(int r,int i):m_r(r),m_i(i){}
      
      const Complex operator+(const Comple& c)const{//将参数改为传引用,那么c2不能为const对象,因为一般引用不能引用常对象,解决方法是将参数改为万能的常引用
        return Complex(m_r+c.m_r,m_i+c.m_i);
      }
       //三个const的作用
      //修饰返回值,使返回值为右值
      //修饰右操作数,使右操作数可以是左值也可以是右值
      //修饰左操作数,可以是左值也可以是右值
      
    private:
    
      int m_r;
    
      int m_i;
      
      friend const Complex operator-(const Complex& l,const Complex& r);//声明友元函数operator-();
    
    
    };
    
    const Complex operator-(const Complex& l,const Complex& r){//将操作符重载定义为全局函数
      return Complex(l.m_r-r.m_r,l.m_i-r.m_i);
    }
    
     
    
    //主函数中
    
    Complex c1(1,2);//如果加上const修饰,那么操作符重载函数将不能得到调用,因为成员函数有this指针,this又没有被const修饰,即传到this的实参是常对象。this定义时要求不是常对象,扩大了操作范围
    
    //所以最好在操作符重载函数中加上常函数修饰限定
    
    Complex c2(3,4);//如果将参数改为传引用,那么c2不能为const对象,因为一般引用不能引用常对象,解决方法是将参数改为万能的常引用
    
    Complex c3=c1+c2;//通过操作重载可以实现自定义类型运算
    
    (c1+c2)=c3;//前面说自定义类型的这种写法会调用拷贝赋值,因为operator=可以由常对象来调用,这不符合我们基本类型的运算逻辑,解决方法是在返回前面加const
    复制代码
    注意:
      通friend关键字,可以把一个全局函数声明为某个类的友元,友元函数可以访问类中的任何成员
     
    2、赋值双目运算符重载:=、+=、-=
    关于赋值双目运算符限定:
      (1)左操作数必须是左值(不能是常量),右操作数既可以是左值也可以是右值
      (2)表达式的结果是一个左值,就是左操作数自身:如(a=10)=20;结果就是a=20;
      
    (1)拷贝赋值
      根据赋值双目运算符限定的两条,可以基本确定拷贝赋值函数的基本形式
    类名& operator=(const 类名(可以是和返回值不同的类)& r){}
    //返回值因为必须是左值,所以不能加const修饰,并且要返回调用自身对象的引用
    //不能是常函数,因为左操作数,即调用者数据会被更新

    (2)实现方法

    与运算类双目操作符重载的区别:

      (1)返回值为左操作数自身的引用

      (2)左操作数不能是右值

      (3)重载函数是非常函数(相对与成员函数实现形式而言)

    1)成员函数形式:L#R           L.operator#(R)

      

    2)全局函数形式:L#R          ::operator#(R)

    注意:

      operator=函数没有全局函数形式,因为没有定义拷贝赋值时,编译器会默认加上缺省的拷贝赋值函数,这会形成歧义。

    二、单目操作符重载 #O

    1、计算类弹幕操作符,-(负),~、!、&(取地址)

    int a =10;

    int b=-10;

    -a;//返回值是临时变量

    注意:

      (1)操作数可以是左值也可以右值

      (2)返回值是右值

      (3)实现方法

     成员函数形式:#O ->  O.operator#(void)

     全局函数形式:#O ->  ::operator#(O)

     例如有Integer类,operator-()函数重载形式如下:

    cosnt Integer operator-(void)const{

      return Integer(-m_i);//m_i为Integer的私有成员变量

    }

    也可以定义为全局函数。

    2、自增减运算符

    参数、返回值、调用对象的限定参考双目操作符重载笔记

    1)前自增减操作符

    操作数必须是左值,返回值就是操作数自身,他也是左值,实现方式也有两种

    成员函数形式:#O -->   O.operator#(void)

    全局函数形式:#O —> O.operator#(O)

    2)后自增减

    操作数必须是一个左值,表达式的结果是右值,返回值应该是操作数自增减前的一个副本,为了使返回值是一个右值,需加上cosnt修饰

    成员函数形式:#O -->   O.operator#(哑元)

    全局函数形式:#O —> O.operator#(O,哑元)

    3、插入和提取操作符的重载 <<  、>>

    在<iostream>中定义了输入输出流对象,所以一般不把输入输出运算符重载为成员函数:

    ostream:cout(左操作数是左值,有操作数可以是左值,也可以是右值)

    istream:cin(左右操作数都是左值)

    friend ostream$& operator<< (ostream& os,const RIGHT& right){}

     friend istream$& operator>> (ostream& is,RIGHT& right){}//右操作数必须是左值

     4、下标运算符"[]"重载

    功能:可以让一个对象当做数组一样的方式去使用

    1)操作数可以是左值,也可以是右值

    2)非常对象返回左值,常对象返回右值

    class Array{
    
    public:
    
      Array(size_t  size):m_data(new int[size]),m_size(size){}
    
      ~Array(void){
    
        delete[] m_data;
        m_data=NULL;
      }
    
      int& operator[](size_t i){
    
        return m_data[i];
      }
    
      int operator[](size_t i)const{//返回类型不是引用,为int型的临时变量
    
        return m_data[i];
      }
    
    private:
    
      int* m_data;//数组地址
    
      size_t m_size;//容器的大小 
    
    };
    
    
    
    int main(void){
    
      Array a(10);//左操作数为左值,重载函数返回值也为左值
    
      a[0]=11;//a.operator[](0)
    
      a[1]=12;
    
      a[9]=20;
    
    
    
      const Array& r=a;
      
      r[0]=21;//报错,因为调用的是常函数,返回右值,不能作为左值
      
    return 0; }

    5、函数操作符“()”

    功能:把一个对象当做函数去使用,可以进行重载

    class Square{
    
    public:
    
      double operator()(double x)const{
    
        return x*x;
      }
    
    };
    
    int main(){
    
      Square square;
    
      //square.operator()(13.0)
    
      cout<<square(13.0)<<endl;
    
      return 0;
    
    } 

    6、解引用(取目标)和间接成员访问操作符: *、->

    功能:将对象当做指针使用,用于实现智能指针

    class A{

    public:

      A(const string& str):m_str(str){}

      ~A(void){}

      string m_str;

    };

    int main(){

      A* pa=new A("hello world!");

      //...

      if("error"){

        return -1;//进入异常,pa将得不到释放

      }

      //...

      delete pa;

      return 0;

    }

    (1)智能指针

    class PA{

    public:

      PA(A* pa):m_pa(pa){}

      ~PA(void){

        if(m_pa){

          delete m_pa;//调用A对象的析构函数

          m_pa=NULL;

        }

    private:

      A* m_pa;

      }

    };

    int main(){

      PA* pa(new A("hello world!"));

      //...

      if("error"){

        return -1;//进入异常,pa在栈区,由}调用析构函数

      }

      //...

      

      return 0;

    }

      像上面pa这样,有时候需要在堆区分配内存,但是因为某些原因执行不到delete,这时候可以像上面一样使用智能指针来完成。即封装一个类的指针的类的对象,当它离开作用域时,其析构函数负责释放该该指针指向的动态内存,以免泄漏。

    (2)*、->操作符重载

      通过pa可以智能实现堆区A对象的释放,但是pa并不能像A的指针一样使用*、->操作符。为此,可以在PA类中实现这两个操作符的重载。

      A* operator-> (void)const{

        return m_pa;

      }

      

      A& operator-*(void)const{

        return *m_pa;

      }

      重载后,可以实现以下操作:

      

      cout<<(*pa).m_str<<pa->m_str<<ndl;

      //编译器解释:pa.operator->()->m_str;

      //pa.operator*().m_str;

    注意:

      上面为了解决内存泄漏问题,需要自己写一个智能指针,C++标准库提供一个叫auto_ptr的智能指针,包含头文件:#include<memory>它的用法如下:

      auto_ptr<A> pa2(new A("hello world"));

    7、类型转换操作符

    功能:类 类型到其他类型的转换

    通过类型转换构造函数,可以使实现:(1)基本类型到类类型转换(2)类类型到类类型的转换

    通过类型转换操作符,可以实现:(1)类类型到基本类型(2)类类型到类类型

    通过上面两种方式,无法实现基本类型到基本类型的转换

    1、类类型到基本类型

    operator 目标类型(void)const{...}

     前面讲到了通过单参数的构造函数,可以把基本类型隐式的转换为相应的类类型,反过来,可以通过操作符重载,把类类型转换为基本类型。例如有一个Integer类,m_i十一个私有的成员变量。

    operator int (void)const{//类型转换操作符函数,注意前面没有返回类型,这里面的int既是操作符,也是返回类型,是一种固定写法。

      return m_i;

    }

    这样就可以直接输出一个Interger对象,而不用再重载输入输出操作符。

    8、new、delete操作符

    static void* operator new(size_t size){}//注意只能声明为静态成员函数,因为使用new操作符之前,此时还没有对象被创建,需要使用类名来进行调用,如果不加,编译器会自动添加。通过new操作符重载,可以实现在不同存储器上分配内存

    static void operator delete(void* p){}

    class A{
    
    public:
    
      A(void){
    
        cout<<"A::A()"<<endl;
    
      }
    
      ~A(void){
    
        cout<<"A::~A()"<<endl;
    
      }
    
      static void* operator new(size_t size){
    
        cout <<"A::new"<<endl;
    
        void*pv =malloc(size);
    
        return pv;
    
      }
    
      static void operator delete(void* pv){
    
        coutt<<"A::delete"<<endl;
    
        free(p);
    
        p=NULL;
    
      }
    
    };

    注意:

      如果一个类中一个成员变量都没有,那么它的对象大小默认是一个字节,这是为了保存对象在内存上的唯一性

    int main(){
    
      A* pa=new A;
    
      //1)pa= (A*)A::operator new(sizeif(A));先分配内存
    
      //2)pa->A::A();再调用构造函数
    
      delete pa;
    
      //1)pa->A::A();
    
      //2)A::operator delete (void)
    
    }

    9、总结

    (1)不是所有操作符都能重载,下面的操作符不能重载

    作用域限定运算符“::”

    直接成员访问运算符“.”

    直接成员指针解引用运算符“.*”

    条件操作符“? :”

    字节长度操作符“sizeof”

    类型信息操作符“typeid”

    注意:关于sizeof

      sizeof(3+3.14);//8

      int a=0;

      sizeof(a=3+3.14);//4

      cout<<a<<endl;//0,sizeof不会计算表达式,只取表达式结果

    (2)如果操作符的所有操作数都是基本类型,编译器不允许重载,如:

    int operator+(int a,int b){
    
      return a-b;//error
    
    }

    (3)操作符的重载无法改变操作符的优先级

    (4)操作符重载无法改变操作数的个数,如:无法通过重载%来实现百分数的重载

    (5)无法通过操作符重载实现新的操作符,如:operator@,operator$等

     (6)部分操作符只能通过成员函数形式进行重载,不能通过友元形式

  • 相关阅读:
    java.lang.NoClassDefFoundError: org/activiti/validation/ProcessValidator
    java.lang.NoClassDefFoundError: org/activiti/image/ProcessDiagramGenerator
    14.10.1 InnoDB Disk I/O
    shiro权限验证标签
    14.9.4 COMPACT and REDUNDANT Row Formats
    Spring整合Shiro做权限控制模块详细案例分析
    14.9.2 Specifying the Row Format for a Table 指定 表的行格式
    全虚拟化与半虚拟化的实现方式
    全虚拟化与半虚拟化的实现方式
    查询方式的一般使用2
  • 原文地址:https://www.cnblogs.com/ptfe/p/11266326.html
Copyright © 2020-2023  润新知