接触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; }
运行结果:
先发生b的普通构造,再是a的拷贝构造,最后是c的缺省构造与赋值。
注释掉拷贝构造函数:
不发生拷贝构造,但a依旧得到了值。
注释掉赋值的重载:
不发生重载,但c依旧得到了值。