zoukankan      html  css  js  c++  java
  • c++ 操作符重载

     操作符函数的名字

         operator 后跟着操作符的符号(其间可以有空格)
     

    操作符函数的参数的个数

        如果操作符函数是非成员函数,参数个数就是操作数的个数;对于二元操作符,第一个参数为左操作数,第二个参数为右操作数。
    如果操作符函数为成员函数,参数个数比操作数个数少1;因为调用此操作符函数的对象默认成为此操作符的左操作数
     

    成员函数 vs. 非成员函数

      操作符函数既可以是成员函数,也可以是非成员函数,视情况而定。(下一章将会讲到)
     

    为什么使用操作符重载?

        对于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的class,对于用户自己定义的class,如果想支持基本操作,比如比较大小,判断是否相等,等等,则需要用户自己来定义关于这个操作符的具体实现。比如,判断两个人是否一样大,我们默认的规则是按照其年龄来比较,所以,在设计person 这个class的时候,我们需要考虑操作符==,而且,根据刚才的分析,比较的依据应该是age。那么为什么叫重载呢?这是因为,在编译器实现的时候,已经为我们提供了这个操作符的基本数据类型实现版本,但是现在他的操作数变成了用户定义的数据类型class,所以,需要用户自己来提供该参数版本的实现。好处如下:
     
     重载操作符所实现的功能完全可以用函数实现,但是使用操作符重载能使用户程序易于编写、阅读和维护。
     
      操作符被重载后,其原有的功能仍然保留,没有丧失或改变。
     
     通过操作符重载,扩大了C++已有操作符的作用范围,使之能用于类对象。操作符重载使C++的表达能力更强。
     
     

    如何声明一个重载的操作符?

     
    A:操作符重载实现为类成员函数
      重载的操作符在类体中被声明,声明方式如同普通成员函数一样,只不过他的名字包含关键字operator,以及紧跟其后的一个c++预定义的操作符。
      可以用如下的方式来声明一个预定义的==操作符:
     
    class person{
      private:
      int age;
       public:
       person(int a){
      this->age=a;
      }
      inline bool operator ==(const person &ps) const;
      };
    实现方式如下:
      inline bool person::operator==(const person &ps) const
      { if (this->age==ps.age)
       return true;
      return false;
      }
      调用方式如下:
       #include<iostream>
      using namespace std;
      int main()
      { person p1(10);
      person p2(20);
      if(p1==p2) cout<<”the age is equal!”<  return 0; 
      }

      这里,因为operator ==是class person的一个成员函数,所以对象p1,p2都可以调用该函数,上面的if语句中,相当于p1调用函数==,把p2作为该函数的一个参数传递给该函数,从而实现了两个对象的比较
      考虑如下的if语句:
       if(10==p1) cout<<”the age is equal!”<<endl;
      是否回正确执行呢?
      答案是不会的,因为只有左操作数是该类类型的对象的时,才会考虑作为类成员重载操作符。因为10不是person类型的对象,所以,不能调用classperson的操作符==。
      考虑如下if语句:
      if(person(10)==person(11))
       cout<<"ok"<<endl;
      是否能够正确执行呢?答案是可以,因为操作符两边均是无名对象

    重载的操作符并不要求两个操作数的类型一定相同。例如:我们可以为class person定义下标操作符,以表示该person和电话的对应关系:
    /*实现下标操作符*/
      #include
      #include
      using namespace std;
      class person
      { private:
       int tel;
      public:
      int & operator[](string const & nm) 
      {
       return tel;
      }
      int GetTel()
      {
      return tel;
      }
      };
      int main()
      {
      person p1;
      p1["suo"]=110;
      person p2;
      p2["rose"]=120;
      cout<  cout<  return 0;
      }

    对于重载为成员函数方式的操作符,隐式的this指针被作为该函数的第一个参数,来代表左操作数
      

    B:操作符重载实现为非类成员函数(全局函数)
      对于全局重载操作符,代表左操作数的参数必须被显式指定。例如:

     #include
      #include
      using namespace std;
      class person
      {
      public:
       int age;
      public:
      };

    /*在类的外部,不能访问该类私有数据,所以,要把
      age设置为public*/

      bool operator==(person const &p1 ,person const & p2)
      {
       if(p1.age==p2.age)
       return true;
       return false;
      }
      int main()
      {
      person rose;
      person jack;
      rose.age=18;
      jack.age=23;
      if(rose==jack)/*两个对象分别代表左右操作数*/ 
      cout<<"ok"<  return 0;
      }

    C:如何决定把一个操作符重载为类成员函数还是全局名字空间的成员呢?

    ①如果一个重载操作符是类成员,那么只有当与他一起使用的左操作数是该类的对象时,该操作符才会被调用。如果该操作符的左操作数必须是其他的类型,则操作符必须被重载为全局名字空间的成员。
      ②C++要求赋值=,下标[],调用(), 和成员指向-> 操作符必须被定义为类成员操作符。任何把这些操作符定义为名字空间成员的定义都会被标记为编译时刻错误。
      ③如果有一个操作数是类类型如string类的情形那么对于对称操作符比如等于操作符最好定义为全局名字空间成员。
      

    D:操作符重载为友元函数方式
       如果把操作符重载为友元函数方式,则在该函数的内部,可以直接访问授权类的私有数据成员,这是友元函数方式和全局名字空间方式的主要区别。
      

    E:怎样判断一个非类成员的操作符应该是类的友元还是应该使用成员访问函数呢?

       一般来说,类的实现者应该尽量使得名字空间函数和访问类内部表示的操作符的数目最小化。如果已经提供了访问成员函数并且它们具有等同的效率,那么最好是使用这些成员函数。但是如果类的实现者决定不为该类的某些私有成员提供访问成员函数而且名字空间操作符需要引用这些私有成员才能完成,它们的操作那么就必须使用友元机制。

     #include
      #include
      using namespace std;
      class person{
      public: 
       int age;
      public:
      };

    bool operator==(person const &p1 ,person const & p2)
      {
      if(p1.age==p2.age) return true;
      return false;
      }
      ostream operator<<(ostream &os,person const &p)
      {
      os<<"the person age is:"<  return os;
      }
      int main()
      {
      person rose;
      person jack;
      rose.age=18;
      jack.age=23;
      cout<  /*call ostream operator<<(ostream &os,person const &p) */
      cout<  return 0;
      }

    重载规则:

     重要的几点:

    1.C++不允许用户自己定义新的操作符,只能对已有的C++操作符进行重载
    2.C++中绝大部分的操作符允许重载,不能重载的操作符只有5个:
    .        (成员访问运算符)
    .*       (通过指向类成员的指针访问类成员运算符)
          (域运算符)
    sizeof  (长度运算符)
    ?:       (条件运算符)
     
    3.重载不能改变操作符所操作之操作数的个数。
    4.重载不能改变操作符的优先级别。
    5.重载不能改变操作符的结合性
     
    重载prefix和postfix
      “++”和“--”运算符有两种使用方式,前置自增运算符和后置自增运算符,它们的作用是不一样的,在重载时怎样区别这二者呢?
     
    C++约定: 在自增(自减)运算符重载函数中,增加一个int型形参,就是后置自增(自减)运算符函数。
     
     #include
      #include
      using namespace std;
      class person
      {
      private:
       int age;
      public:
       person(int a)
       {
       age=a;
       }
      person const operator++()/*prefix ++ */
      {
       this->age++;
       return *this;
      }
      person const operator++(int )/*postfix ++ */
      {
       person temp(1);
       temp=*this;
       this->age++;
       return temp; //返回的是增加前的对象
      }
      int GetAge()   {    return age;   }   };   int main()   {   person rose(10);   person jack(20);   person marry(22);   person tom(30);   jack=++rose;   marry= tom++;   cout<  cout<  return 0;   }

    转一篇文章:

      在句法上,重载函数是通过它们的参数类型的差异区分的,但是不管是前缀形式还是后缀形式的自增和自减都没有参数,我们到底该怎么区分它们呢?在开始时,C++在语法上面确实是存在这个问题的,程序员对此颇有微词。后来C++中加了一些特性来解决这个问题。

    C++规定后缀形式有一个int类型的参数,当函数被调用时,编译器传递一个0作为int参数的值给该函数。

    且看下面的小程序:

    #include <iostream>
    
    class MyInt{
    
        public:
    
        MyInt(int a):i(a) {   }
    
        MyInt& operator++();            // prefix ++
    
        const MyInt operator++(int);    // postfix ++
    
        MyInt& operator--();            // prefix --
    
        const MyInt operator--(int);    // postfix --
    
        friend std::ostream& operator<<(std::ostream&,const MyInt&);
    
        private:
    
        int i;
    
    };
    
    MyInt& MyInt::operator++()
    
    {
    
        this->i++;
    
        return *this;
    
    }
    
    const MyInt MyInt::operator++(int)
    
    {
    
        const MyInt temp = *this;
    
        ++(*this);
    
        return temp;
    
    }
    
    std::ostream& operator<<(std::ostream& out,const MyInt& t)
    
    {
    
        out << t.i ;
    
        return out;
    
    }
    
    int main()
    
    {
    
         MyInt a(0);
    
        
    
         a++;
    
         std::cout << a << std::endl; // i = 1,print 1
    
         ++a;
    
         std::cout << a << std::endl; // i = 2,print 2
    
         std::cout << a++ << std::endl; // i = 3,print 2
    
         std:: cout << ++a << std::endl; // i = 4,print 4
    
         return 0;
    
    }

    看上面的程序可以发现以下几点:

    1.       后缀形式的参数并没有被用到,它只是语法上的要求,为了区分而已;

    2.       后缀形式返回一个const对象;这样做是大有深意滴~~ 我们知道操作符重载本质上是一个函数而已,它应该和操作符原来意义、使用习惯相似,而对于int内置类型来说的话,i++++是有语法错误的,故为了保持一致,重载之后的后缀形式也应该是这样的。假如返回的不是const类型,那么对于

    MyInt t(1);

    t++++;   // t.operator++(0).operator++(0)

    这样的形式将是正确的,这显然不是我们期望的。另外,只要看上面的后缀形式的定义即可知道,这样写t只是增加了1而不是我们期望的2,为什么呢?因为第二次的调用是用第一次返回的对象来进行的,而不是用t,它违反了俺们程序员的直觉。因此,为了阻止这些行为,我们返回const对象。

    3.       我们应该尽可能调用前缀形式;为什么呢?看看后缀形式的定义就可以知道,我们定义了一个临时对象,临时对象的创建、析构还是很费时间的。而前缀形式则不一样,它的效率应该相对好一些。

    深入探究:

    http://courses.cms.caltech.edu/cs11/material/cpp/donnie/cpp-ops.html

    http://en.wikibooks.org/wiki/C++_Programming/Operators/Operator_Overloading

     2015-03-02:

    operator它有两种用法,一种是operator overloading(操作符重载),一种是operator casting(操作隐式转换)。

    operator casting
    C++可以通过operator 重载隐式转换,格式如下: operator 类型T (),如下所示
     
    class A
    {
    public:
    operator B* () { return this->b_;}
    operator const B* () const {return this->b_;} 
    operator B& () { return *this->b_;}
    operator const B& () const {return *this->b_;}

    private:
    B* b_;
    };

    A a;
    当if(a),编译时,其中它转换成if(a.operator B*()),其实也就是判断 if(a.b_)

    一篇文章:

    类型转换操作符(type conversion operator)是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。boost::ref和boost::cref就使用到了类型转换操作符。


    函数原型
    T1::operator T2() const;   //T1的成员函数,"(T2)a"类型转换

    1. 转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空;返回值是隐含的,返回值是与转换的类型相同的,即为上面原型中的T2;

    2. T2表示内置类型名(built-in type)、类类型名(class type)或由类型别名(typedef)定义的名字;对任何可作为函数返回类型的类型(除了 void 之外)都可以定义转换函数,一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的;

    3. 转换函数一般不应该改变被转换的对象,因此转换操作符通常应定义为 const 成员

    4. 支持继承,可以为虚函数

    5. 只要存在转换,编译器将在可以使用内置转换的地方自动调用它

    #include <iostream>
    using namespace std;
    class D{
    public:
        D(double d):
          d_(d){}
          operator int()const{
              cout<<"(int)d called!!"<<endl;
              return static_cast<int>(d_);
          }
    private:
        double d_;
    };
    
    int add(int a,int b){
        return a+b;
    }
    
    int main(){
        D d1=1.1;
        D d2=2.2;
        cout<<"add(d1,d2)="<<add(d1,d2)<<endl;
        return 0;
      
    }
    (int)d called!!
    (int)d called!!
    add(d1,d2)=3
    Press any key to continue

    类型转换构造函数(conversion constructor)

    先来说下类型转换构造函数:C++中的explicit用来修饰类的构造函数,表明该构造函数是显示的,既然有显示的,那么就有隐式的
    若果一个类的构造函数时一个单自变量的构造函数,所谓的单自变量是可能声明一个单一参数,也可能声明一个拥有多个参数,并且除了第一参数外都其他参数都有默认值
    这样的constructor称为单自变量constructor.
    若果类中有这样一个constructor那么在编译的时候编译器将会产生一个省却的操作:将该constructor参数对应 的 数据类型 的 数据转换为该类的对象
    class MyClass
    {
    public:
    MyClass( int num );
    }
    ....
    MyClass obj = 10; //ok,convert int to MyClass

    在上面的操作中编译器其实产生代码如下:
    Myclass temp(10);
    Myclass obj=temp;

    若果要避免编译器产生上诉的隐式转换,那么此时explicit将产生作用。
    explicit的作用:
    explicit关键字将作用在类的构造函数,被修饰的构造函数,将再不能发生隐式转换了,只能以显示的进行类型转换
    explicit 的注意:
    只能作用在类的内部的构造函数上
    只能作用在单自变量的构造函数上

  • 相关阅读:
    极大/小搜索,alpha/beta剪枝
    消息系统
    渲染主线程都在干什么
    好玩的虚拟CPU执行代码
    好玩的隐藏属性
    好玩的对象存储
    透视投影矩阵
    绕任意轴旋转
    视图变换
    正交投影矩阵
  • 原文地址:https://www.cnblogs.com/youxin/p/2542278.html
Copyright © 2011-2022 走看看