运算符重载

来源:互联网 发布:淘宝中老年秋冬季服装 编辑:程序博客网 时间:2024/05/16 09:07

一、规则

1. 不可重载对内部类型进行操作的运算符

2. 不可创建新的运算符

3. 必须符合运算符的语言语法

4. 四个不变

(1)操作数个数

(2)优先级

(3)结合性

(4)语法结构

 

5.可重载的运算符:

+

-

*

/

%

^

&

|

~

!

,

=

<

>

<=

>=

++

--

<<

>>

==

!=

&&

||

+=

-=

/=

%=

^=

&=

|=

*=

<<=

>>=

[]

()

->

->*

new

new []

delete

delete []

注释:”=”有关运算符不可被继承。

6.不可重载的运算符:     

   .

  . *

::

?

sizeof

7.重载操作符必须具有一个类类型操作数:重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。

       用于内置类型的操作符,其含义不能改变。例如,内置的整型加号操作符不能重定义:

     // error: cannot redefine built-in operator for ints

     int operator+(int, int);

8. 实质:运算符重载实际是一个函数,所以运算符的重载实际上是函数的重载。

9  当运算符重载函数的形式参数类型全部为内部类型的时候,将不能重载。

10.除了函数调用操作符 operator() 之外,重载操作符时使用默认实参是非法的

.不再具备短路求值特性:重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、逻辑 OR和逗号操作符的操作数求值。在 && 和 || 的重载版本中,两个操作数都要进行求值,而且对操作数的求值顺序不做规定。因此,重载 &&|| 或逗号操作符不是一种好的做法。

二、重载运算符的两种形式:

1、成员函数(非静态成员函数)

2、友元函数。

这两种形式都可访问类中的私有成员。 

(1)运算符重载函数作为类的友元函数的形式:

  class 类名

  {

    friend 返回类型 operator运算符(形参表);

  }

  类外定义格式:

  返回类型 operator运算符(参数表)

  {

    函数体

}

友元函数(非类成员,无this指针)重载双目运算符(有两个操作数,通常在运算符的左右两则),参数表中的个数为两个。若是重载单目运算符(只有一个操作数),则参数表中只有一参数。

 

(2)运算符重载函数作为类的成员函数的形式:

  class 类名

  {

    返回类型 operator 运算符(形参表);

  }

   类外定义格式:

  返回类型 类名:: operator 运算符(形参表)

  {

    函数体;

  }

对于成员函数(无this指针)重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数。同样的是重载,为什么和友元函数在参数的个数上会有所区别的。

 

区别、优缺点、 限制、什么时候用哪个比较合适:

限制:成员函数不可重载: >>,<<

  友元函数不可重载:"=","+="..,"[ ]" "( )" "-> ",类类型转换重载函数

 

(1)一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

(4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。

(5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。

(6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一 个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部 类型的对象,该运算符函数必须作为一个友元函数来实现。

(例:34.5+c1)(因为this指针只能作为第一个操作数)

(7) 当需要重载运算符具有可交换性时,选择重载为友元函数
例:给复数运算符重载的四则运算符。复数由实部和虚部构造,定义一个复数类,再在类中重载复数四则运算的运算符。

三、使用重载操作符:

A.使用重载操作符的方式,与内置类型操作数上使用操作符的方式一样。假定 item1 和 item2 是 Sales_item 对象,可以打印它们的和,就像打印两个 int 的和一样:

     cout << item1 + item2 << endl;

    B.也可以像调用普通函数一样调用重载操作符函数,指定函数并传递适当类型适当数目的形参:

     // equivalent direct call to nonmember operator function

     cout << operator+(item1, item2) << endl;

    C.调用成员操作符函数与调用任意其他函数是一样的:指定运行函数的对象,然后使用点或箭头操作符获取希望调用的函数,同时传递所需数目和类型的实参。对于二元成员操作符函数的情况,我们必须传递一个操作数:

     item1 += item2;            // expression based "call"

     item1.operator+=(item2);   // equivalent call to member operator function

四、重载操作符的设计

  1.不要重载具有内置含义的操作符:

A.赋值操作符、取地址操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本,编译器就自己定义以下这些操作符。

合成赋值操作符(第 13.2 节)进行逐个成员赋值:使用成员自己的赋值:使用成员自己的赋值操作依次对每个成员进行赋值。

B.默认情况下,取地址操作符(&)和逗号操作符(,)在类类型对象上的执行,与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址,逗号操作符从左至右计算每个表达式的值,并返回最右边操作数的值。

C.内置逻辑与(&&)和逻辑或(||)操作符使用短路求值(第 5.2 节)。如果重新定义该操作符,将失去操作符的短路求值特征。

通过为给定类类型的操作数重定义操作符,可以改变这些操作符的含义。 重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。

.大多数操作符对类对象没有意义:除非提供了重载定义,赋值、取地址和逗号操作符对于类类型操作数没有意义。设计类的时候,应该确定要支持哪些操作符。

.复合赋值操作符:如果一个类有算术操作符或位操作符,那么,提供相应的复合赋值操作符一般是个好的做法。例如,Sales_item 类定义了 操作符,逻辑上,它也应该定义 +=

.相等和关系操作符:

A.将要用作关联容器键类型的类应定义 操作符。关联容器默认使用键类型的 操作符。即使该类型将只存储在顺序容器中,类通常也应该定义相等(==)和小于(<)操作符,理由是许多算法假定这个操作符存在。例如 sort 算法使用 操作符,而 find 算法使用 == 操作符。

B.如果类定义了相等操作符,它也应该定义不等操作符 !=。类用户会假设如果可以进行相等比较,则也可以进行不等比较。同样的规则也应用于其他关系操作符。如果类定义了 <,则它可能应该定义全部的四个关系操作符(>>=<<=)。

 

.审慎使用操作符重载:

A当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用。使用重载操作符而不是创造命名操作,可以令程序更自然、更直观,而滥用操作符重载使得我们的类难以理解。

B.在实践中很少发生明显的操作符重载滥用。例如,不负责任的程序员可能会定义 operator+ 来执行减法。更常见但仍不可取的是,改变操作符的正常含义以强行适应给定类型。操作符应该只用于对用户而言无二义的操作。在这里所谓有二义的操作符,就是指具有多个不同解释的操作符。

C.当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常也比用操作符更好。如果不是普通操作,没有必要为简洁而使用操作符。

 

.选择成员或非成员实现:为类设计重载操作符的时候,必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下,程序员没有选择,操作符必须是成员;在另一些情况下,有些经验原则可指导我们做出决定。下面是一些指导原则,有助于决定将操作符设置为类成员还是普通非成员函数.

A.赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。

B.改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。

C.对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

 

 

单目运算符重载:++,-- ......

例 ++

成员函数重载++

类内声明

前置:

返回值类型 operator++();

后置:

返回值类型 operator++int);

类外定义

前置:

返回值类型 类型::operator++()

后置:

返回值类型 类型::operator++int

 

 

前置:++

1

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

 Point operator++();//成员函数重载单目运算符++

 void showPoint();

 };

 

 Point Point::operator++()//成员运算符重载函数

 {

 ++x;  //等价于++this->x;

 ++y;  //等价于++this->y;

 return *this;

 }

当改为:

 Point Point::operator++()//成员运算符重载函数

 {

 x++;  //等价于this->x++;

 y++;  //等价于this->y++;

 return *this;

 }

结果一样!

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10);

 ++point;//operator++(point)

     point.showPoint();//输出坐标值

     

 return 0;

 }

运行结果(11,11)

 

 

(2

#include <iostream>

class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

void operator++();//成员函数重载单目运算符++

void showPoint();

 };

 

void Point::operator++()//成员运算符重载函数

 {

 ++x;

 ++y;

 }

//若改为Point &Point::operator++()//成员运算符重载函数

 {

Point t(*this);

++x;

++y;

return t;

 }

结果一样,因为tthis指针指向同一块堆! 

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10);

 ++point;//operator++(point)

     point.showPoint();//输出坐标值

     

 return 0;

 }

运行结果(11,11)

 

3

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

 Point(){};

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

Point operator++();//成员函数重载单目运算符++

void showPoint();

 };

 

Point Point::operator++()//成员运算符重载函数

 {

  Point point1;   //这里创建一个局部对象

  point.x=++x;  //并把自加后的值赋给局部

  point.y=++y;

return point1;      //产生临时变量(局部变量调用拷贝构造函数将值复制给临时变量)

// 局部变量在重载函数结束后马上消失。

//拷贝构造函数完成后临时变量才消失。

                //运算符重载函数为值返回的时候会产生临时变量,临时变量与局部变量result的复制会调用拷贝构造函数,临时变量的生命周期是在拷贝构造函数运行完成后才结束,但如果运算符重载函数返回的是引用,那么不会产生临时变量,而局部变量result的生命周期在运算符重载函数退出后立即消失,它的生命周期要比临时变量短,所以当外部对象获取返回值的内存地址所存储的值的时候,获得是一个已经失去效果的内存地址中的值,在这里的值返回与引用返回的对比,证明了临时变量的生命周期比局部变量的生命周期稍长。  

Point &Point::operator++()//成员运算符重载函数

 {

Point t(*this);

++x;

++y;

return t;

 }

//当函数返回引用类型时,没有复制返回值,相反,返回的是对象本身。函数执行完毕时,将释放分配给局部对象的存储空间。此时对局部对象的引用就会指向不确定的内存!

 }

 

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10);

 ++point;//operator++(point)  //返回的是临时变量

     point.showPoint();//输出坐标值

     

 return 0;

 }

运行结果(11,11)

 

但事实上如果我们的Point类有两个个带参的构造函数,那我们的程序就可创建一个无名的临时对象以简化我们的代码:

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

 Point(){};

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

Point operator++();//成员函数重载单目运算符++

void showPoint();

 };

 

Point Point::operator++()//成员运算符重载函数

 {

  

return Point(x++,y++); 

//创建一个临时Point对象并返回它

//这里的用法就是所谓的返回值优化用法,当编译器看到如此使用时,知

//道对创建的对象没有其他需求,只是返回,直接把对象创建在外部返回值

//的内存单元,只是调用普通的构造函数,(并没有创建真正的局部对象)

//且不会调用析构函数(推测是编译器对其进行回收),这样比创建一个临时

//的局部变量,然后返回它更节省资源

 

 }

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10);

 ++point;//operator++(point)

     point.showPoint();//输出坐标值

     

 return 0;

 }

运行结果(11,11)

 

 

 

无论上面那种方法,我们都需要创建一个对象,而这将调用构造函数,来为该对象开辟内存空间,有时候销毁该对象又要调用析构函数释放内存,因此我们每创建一次对象,系统都要牺牲一定的速度和内存空间。那么可不可以不创建临时对象呢,当然可以。我们知道this指针就是当前对象的标签,那么我们只要将当前对象的成员变量自加,然后返回this指针指向的当前对象即可:

Point Point::operator++()//成员运算符重载函数

 {

++x;

++y;

    return *this;

 }

但其实这个函数还存在一个问题,就是上述我们定义的函数是按值返回的,这将创建一个*this的临时副本,为了避免创建这个临时副本,我们最好将operator++()的返回值定义为按别名返回,并且由于我们不能执行++++i这样的操作,因此我们最好还将它的返回值定义为常量

Point const &Point::operator++()//成员运算符重载函数

 {

++x;

++y;

    return *this;

 }

 

 

4

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

Point operator++();//成员函数重载单目运算符++

void showPoint();

 };

 

Point Point::operator++()//成员运算符重载函数

 {

 ++x;

 ++y;

return *this;

 }

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10);

 ++(++point);//operator++(point)

     point.showPoint();//输出坐标值

     

 return 0;

 }

运行结果(11,11)

 

然而:

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

Point &operator++();//成员函数重载单目运算符++

void showPoint();

 };

 

Point &Point::operator++()//成员运算符重载函数

 {

 ++x;

 ++y;

return *this;

 }

//若改为Point &Point::operator++()//成员运算符重载函数

 {

Point t(*this);

++x;

++y;

return t;

 }

答案将是(11,11)

 

//因为this指针作用域是在对象被析构之后,而 t是一个临时对象!拷贝构造函数结束后消失

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10);

 ++(++point);//operator++(point)

     point.showPoint();//输出坐标值

     

 return 0;

 }

运行结果(12,12)

解释://递增运算符是单目运算符,使用返回引用的运算符重载函数道理就在于它需要改变自身。
       //在前面我们学习引用的单元中我们知道,返回引用的函数是可以作为左值参与运算的,这一点也符合单目运算符的特点。  
        //如果把该函数改成返回值,而不是返回引用的话就破坏了单目预算改变自身的特点,程序中的++(++point)运算结束后输出(1111,会发现对象point只做了一次递增运算,原因在于,当函数是值返回状态的时候括号内的++point返回的不是point本身而是临时变量,用临时变量参与括号外的++运算,当然point的值也就只改变了一次。  

后置 ++

1

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

 Point(){}

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

 Point operator++(int);//成员函数重载单目运算符++

 void showPoint();

 };

 

 Point Point::operator++(int)//成员运算符重载函数

 {

 Point temp; 

 temp.x=x++;//等价于++this->x;

 temp.y=y++; //等价于++this->y;

 return temp;

 }

结果为(10,10)

当改为 

Point Point::operator++(int)//成员运算符重载函数

 {

 Point temp; 

 temp.x=++x;//等价于++this->x;

 temp.y=++y; //等价于++this->y;

 return temp;

 }

结果为(11,11)

 

注:用this指针无法重载后置运算符!结果将会是

 Point Point::operator++(int)//成员运算符重载函数

 {

x++;

y++;

return *this;

 }

结果为(11,11)

 

 

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10),point1(0,0);

 point1=point++;//operator++(point)

     point1.showPoint();//输出坐标值

     

 return 0;

 }

友元(全局)函数重载++

类内声明

前置:

Friend 返回值类型 operator++(类名 &对象名)

后置

friend 返回值类型 operator++(类名 &对象名,int

类外定义

类外定义

前置:

返回值类型 operator++类名 &对象名

后置:

返回值类型 operator++类名 &对象名int

与成员函数对比,由于友元函数非成员函数无this

 

前置

1

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

 Point(){}

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

 friend Point operator++(Point &p);//成员函数重载单目运算符++

 void showPoint();

 };

 

 Point operator++(Point &p)//成员运算符重载函数

 {

 

++p.x;

++p.y;

return p;

 }

结果(11,11)

若改为

 Point operator++(Point p)//成员运算符重载函数

 {

    Point temp;

temp.x=++p.x;

temp.y=++p.y;

return temp;

 }

结果一样。

若改为

 Point operator++(Point p)//成员运算符重载函数

 {

 

++p.x;

++p.y;

return p;

 }

 结果一样。  因为返回值p赋值给point1

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10),point1(0,0);

 point1=++point;//operator++(point)

     point1.showPoint();//输出坐标值

     

 return 0;

 }

 

然而

#include <iostream>

 class Point//坐标类

 {

 private:

 int x;

 int y;

 public:

 Point(){}

     Point(int x,int y)

     {

 this->x=x;

 this->y=y;

     }

 friend Point operator++(Point p);//成员函数重载单目运算符++

 void showPoint();

 };

 

 Point operator++(Point p)//成员运算符重载函数

 {

 

++p.x;

++p.y;

return p;

 }

结果(10,10)

若改为

 Point operator++(Point &p)//成员运算符重载函数

 {

 

++p.x;

++p.y;

return p;

 }

结果(11,11)

 

 

 void Point::showPoint()

 {

     std::cout<<"("<<x<<","<<y<<")"<<std::endl;

 }

 

 int main()

 {

     Point point(10,10),point1(0,0);

 ++point;//operator++(point)

     point.showPoint();//输出坐标值

     

 return 0;

 }

后置:略。

 

如果有哪里写得不好的,或者哪里错的,愿大家直言。谢谢

0 0
原创粉丝点击