运算符重载



接触C++只有3个月,只是写给自己看的啦 0 0.


OOP 第二次读书报告


1)你可以重载运算符为成员函数和非成员函数,应该选择哪个?

2)重载运算符为成员函数和非成员函数的特点?

3)如何理解“逐个成员赋值memberwise assignment”(与“逐个字符赋值bitwise assignment”相对应)?


1

      ·单目运算符最好重载为类的成员函数,双目运算符最好重载为类的非成员函数:

 

const complex operator +(const complex &r,const complex &s);
//双目运算符重载为非成员函数(全局函数)

friend const complex operator +(const complx &r,const complex &s);
//双目运算符重载为非成员函数(友元函数) 
const complex operator -()const;
//单目运算符重载为成员函数


  例外:

  不要重载为非成员函数

 

  当然,单目运算符也可以重载为类的非成员函数,双目运算符也可以重载为类的成员函数:

 

const complex operator +(const complex &x)const;
//双目运算符重载为成员函数

const complex operator -(const complex &x);
//单目运算符重载为非成员函数(全局函数)

friend const complex operator -(const complex &x); 
//单目运算符重载为成员函数(友元函数)

 

     ·如果第一个运算符不是该类的一个对象,而是其它类的对象或者普通变量等,那么不能使用重载为成员函数,因为重载为成员函数有一个隐藏的this指针,第一个参数默认为该类的对象:

 

const complex operator +(int r,const complex &s);
//第一个参数不是此类的对象,重载为非成员函数(全局函数)

friend const complex operator -(int r,const complex &s);
//第一个参数不是此类的对象,重载为非成员函数(友元函数)


    ·考虑不同参数顺序,使用友元非成员函数:

 

friend const complex operator -(const complex &r,int s);
friend const complex operator -(int r,const complex &s);
   

    ·如果运算操作需要修改运算数自身,那么最好使用成员函数:

 

const Integer& operator++();
//前缀运算符 

    ·操作数希望存在隐式类型转化,使用非成员函数:

 

const Integer operator +(const Integer &r,const Integer &s);
Integer x(3),y(7),z;
z = x + y;//ok
z = x +3;//ok
z = 3 + y;//ok
z = 3 + 7;//ok

     如果使用成员函数:

 

const Integer operator +(const Integer &x)const;
Integer x(3),y(7),z;
z = x + y; //ok
z = x + 3; //ok
z = 3 + y; //error


  对于z = x + 3,编译器用构造函数把3构造成int对象。

  对于z = 3 + y, 编译器从左到右读取,看到3的时候就确定用整数的加减,再发现右边的不是整数,所以需要把对象变成整数的手段,但是对象里没有这个手段。

 

     · 类型转换函数定义为成员函数:

 

operator int();

    

 

2

  成员函数:

 

  有一个隐藏的参数,this指针。对于一元运算符,不需要参数;对于二元运算符,只需要that参数,以此类推。

这样也就限制了第一个参数必须为类的对象,对于整数类,z = 3 + y 就是错误的。

 

complex x(1,2), y(3,5), z;
z = x + y;
//等价于x.operator+(y) 

   其中左边的算子决定使用哪个重载的+

 

  非成员函数:

 

  不存在隐藏的this指针,需要把所有运算符按照顺序传入。

  在开发类时使用非成员函数进行运算符重载,一般将其设置为友元函数。

  在已有类上,如果有办法接触到其成员,也就可以在不修改的前提下利用非成员函数添加运算符重载,对第一个参数的限制也就降低。但是在一定程度上也破坏了C++的封装性。

 

complex x(1,2), y(3,5), z;
z = x + y;
//等价于operator+(x,y)

3

 

  当你去调一个参数是对象本身的函数时,在这个时候,就会发生拷贝构造。

  但是在写这种拷贝构造函数的时候,传参数的时候却应该传入对象的引用,如果这里传的不是引用而是对象本身,那么在函数接收参数的时候(入栈过程),会有产生一个临时对象来接收这个参数,这个过程又是一次拷贝构造,因此函数反复调用自己,形成死循环。所以必须传入引用。

 

  如果定义了自己拷贝构造函数:

<span style="font-family:宋体;font-size: 10.5pt;">   complex::complex(const complex &x); </span>


  那么在执行下述代码时:

complex b(1,2);
complex a = b;
//等价于complex a(b); 

  就会调用上面自己的拷贝构造函数。

  这就叫做memberwise copy.

 

  如果你没有写那个自定义的拷贝构造函数,系统会为你提供一个拷贝构造函数。发生的过程是先用b的构造函数去构造一个b对象,然后再按字节复制给a对象。(直接将一段内存上的数据拷贝到另一段内存上,类似于memcpy

  这就叫做bitwise copy.

 

  按字节拷贝存在这些问题:

  1.对于指针直接拷贝,导致两个指针指向同一块内存,这样如果我们同时调用它们,就会让同一块内存析构两次。我们所希望的是拷贝后的指针指向新的那块内存。

  2.如果拷贝构造函数内部还存在其它对象,那么就会继续调用拷贝构造函数,不断递归,但是对于按字节拷贝来说,就没有这种操作方式。

 

  如果你没有将其写成初始化形式,而是写成了赋值形式:

 

complex b(1,2);
complex a;
a = b;


  在读到complex a这一行时,没有接收到任何参数,调用的是a的缺省构造函数,下一行a = b调用的将是自己的赋值重载函数。

如果没有自己的赋值重载函数,将发生bitwise copy.

 

以下是一个测试代码:

 

#include<iostream>
using namespace std;
class complex{
	int r;
	int i;
public:
	complex& operator =(const complex &x){
		cout<<"complex& operator ="<<endl;
		if(this!=&x){
			this->r = x.r;
		    this->i = x.i;
		}
		return *this;
	}
	
	friend ostream& operator <<(ostream& out, const complex &x){
		out<<x.r<<" + "<<x.i<<"i"<<endl;
	}
	complex(int x,int y):r(x),i(y) { cout<<"common constructor"<<endl; };
	complex(){ cout<<"default constructor"<<endl; };
	complex(const complex &x){
		cout<<"copy constructor"<<endl;
		if(this!=&x){
			this->r = x.r;
			this->i = x.i;
		}	
	}
};
int main(){
	complex b(1,2);
	cout<<"b = "<<b;
	complex a = b;
	cout<<"a = "<<a;
	complex c;
	c = b;
	cout<<"c = "<<c;
}

运行结果:


   运算符重载_第1张图片

    

先发生b的普通构造,再是a的拷贝构造,最后是c的缺省构造与赋值。


注释掉拷贝构造函数:

   

   

   不发生拷贝构造,但a依旧得到了值。

 

  注释掉赋值的重载:

   

   

    不发生重载,但c依旧得到了值。




你可能感兴趣的:(运算符重载)