同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或者拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员(只有一分拷贝),在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。
class Object: { int value; public: Object(){} //缺省构造函数 Object(int x = 0):value(x){} //普通构造函数 ~Object(){} //缺省析构函数 //拷贝构造函数 Object(const Object & obj):value(obj.value) { cout<<"Create Cpoy"<<endl; } }; int main() { Object obj(10); Object obj(obja); }
如果将拷贝构造函数中的引用符号去掉,会出现什么问题?
——死递归
,因为调动拷贝构造函数时,拷贝构造中还有一个obj
,会反复调动行成死递归。
辨析下列程序一共生成了几个对象?
Object fun(Object obj)//3 objx拷贝构造obj { int val = obj.Getvalue; Object obja(val);//4 return obja;// } int main() { Object objx(0);//1 Object objy(0);//2 objy = fun(objx); return 0; }
fun函数快结束时,返回obja
时,并不是直接将obja
传给objy
,而是利用一个将亡值(临时量),此时需要调动拷贝构造函数在该临时空间构建第五个对象。
不难看出,上述程序在调用过程中生成了五个对象,对于空间和资源的利用较大,不太理想,那么我们如何修改程序,使得产生最少的对象却又能够到达同样的效果呢?
Object fun(const Object &obj)//3 objx拷贝构造obj { int val = obj.Value() + 10; Object obja(val);//4 return obja;// } int main() { Object objx(0);//1 Object objy(0);//2 objy = fun(objx); return 0; }
图示为各对象的存储空间和关系分布:
问题:为什么obja
将亡值对象构建在了主函数的栈帧空间中,而不是fun函数的栈帧空间中?——(构建在调用者空间中)
因为主函数是调用者,其调用了fun,若构建在fun的栈帧空间中,fun函数结束,就无法得到该对象了
若将上述函数修改为以引用的形式返回?会有哪些变化?想一想之前学过的以引用返回的坏处。
Object & fun(const Object &obj)
此时,由于返回的是一个引用,所以不需要中间变量,故直接将obja
的地址出传递给Eax寄存器,但是传递之后,fun函数结束,那么其中空间的对象会自动调动析构函数销毁自身,并将所在的栈空间回收系统,此时,主函数通过Eax内的值解引用,并不一定会正确访问到所期望的结果,这取决于这块空间是否在这段时间内受到其他资源的利用和侵扰。
所以,尽量不要使用引用返回,一定要万分谨慎。如果一定要以引用返回,还是那句话,此时该对象的生存期不应受到函数生存期的影响。
总结:以引用返回和不以引用返回的区别:
运算符的重载实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数,通常为类的成员函数。
定义运算符重载函数的一般格式:
返回值类型 类名::operator重载的运算符(参数表) {……}
operator是关键字,它与重载的运算符一起构成函数名。
今天对于该问题的学习,我们以下面的类为例:
class Int { private: int value; public: Int(int x = 0):valie(x){} Int(const Int &it):value(it.value){} ~Int(){} }
我们先来写一个加法函数:Add
int main() { Int a(10),b(10); Int c; c = a.Add(b); //c.value = a.value + b.value; // c = Add(&a,b); }
第一种:这种写法创建了3个对象,x(拷贝构造),tmp,临时对象(返回给c)
Int Add(Int x) //Int Add(Int * const this ,Int x) { int val = this->value + x.value; Int tmp(val); return tmp; }
第二种:添加引用,只创建了一个临时对象
Int Add(Int &x) //Int Add(Int * const this ,Int &x) { int val = this->value + x.value; return Int(val); }
第三种:为避免修改x或this对象的属性值,为了避免这种错误,我们常常将此类方法写为常方法
Int Add(Int &x) //Int Add(Int * const this ,Int &x) { x.value += this->value;//改了x //this->value += x.value;//改了this(b) return Int(x.value); }
Int Add( const Int &x) const //Int Add( const Int * const this ,Int &x)
改法如下:
Int Add( const Int &x) const { int val = this->value + x.value; return Int(val); }
函数名Add被改为+号是不被允许的,因此我们需要如下的运算符重载函数:
Int operator+(const Int &x) const //Int operator+(const Int * const this, const Int &x) const { int val = this->value + x.value; return Int(val); }
编译过程:
c = a+ b; c = a.operator+(b); c = operator+(&a,b);
减法,乘法很好改写,只有除法需要处理一下:
Int operator/( const Int &x) const { if(x.value == 0)exit(EXIT_FAILURE); int val = this->value / x.value; return Int(val); }
C++中禁止重载4个的运算符:
下面进一步完善Add函数,将“对象+对象”扩展到“对象+ 变量”和“变量 + 对象”
1.对象+变量
Int operator+(const int x) const { int val = this->value + x; return Int(val); //return *this + Int(x); //Int(x)将构建一个对象 //这一句可以将其转化为对象+对象,只是Int(x)没有姓名,调试到此处会转调对象+对象 }
思考:对于内置类型变量x是否需要添加引用?
答:
不需要,因为加了引用后,从引用的本质上来看,反而增加了对内存的访问次数。
2.变量+对象
我们常会写成这样:
Int operator+(const int x , const Int &it)
但由于参数过多,编译不会通过,运算符重载函数不允许参数过多,这里有三个参数。
因此不能将这个函数设置成类的成员函数,而需要将其设计成全局函数。
当改为全局函数时,就没了this指针。
Int operator+(const int x,const Int &it) { return it + x; //变量+对象——》对象+变量-》对象+对象 }
下面我们学完了一些基本的函数,其实任何一个类型,在C++中系统默认添加6个缺省函数
class Empty { public: //构造函数 Empty(){} //析构函数 ~Empty(){} //拷贝构造函数 Empty(const Empty & e){} //赋值 Empty & operator=(const Empty &x) { return *this; } //取地址符(普通对象) Empty * operator&() {return this;} //重载取地址符(常对象) const Empty * operator&() const {return this;} }
在C11标准中,变成了8个缺省,添加了如下两个(后面会谈到):
//移动构造函数 Empty(Empty &&x){} //移动赋值 Empty & operator=(Empty &&x)
赋值函数:
void operator=(const Int &it) { this->value = it.value; }
我们可以从函数的本质上看出这个函数不能完成连续赋值,obja = objb = objc
operator=(objc)
operator=(&objb,objc)
因为从右往左赋值时,不能将无类型赋值给一个对象。
改写为:
Object & operator=(const Object &it) { if(this != &it)//c = c这种情况需要特殊处理这里的&不是引用,是取地址符 { this->value = it.value; } return *this; //c = a = b; //c.operator=(a.operator=(b)); //operator=(&c,operator=(&a,b)); }
*this
代表主函数中的一个对象,不受该函数的影响,因此可以以引用返回,由此也不会产生其他对象用来过渡