ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++(5)——拷贝构造函数,运算符重载

2022-01-18 22:58:14  阅读:105  来源: 互联网

标签:const 函数 Int Object value 运算符 C++ operator 构造函数


拷贝构造函数

同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或者拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员(只有一分拷贝),在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。

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,而是利用一个将亡值(临时量),此时需要调动拷贝构造函数在该临时空间构建第五个对象。
不难看出,上述程序在调用过程中生成了五个对象,对于空间和资源的利用较大,不太理想,那么我们如何修改程序,使得产生最少的对象却又能够到达同样的效果呢?

  • 形参添加引用,减少一次构造函数的调动,为了防止对obj的修改,添加const(此时不需要形参去改变实参)
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内的值解引用,并不一定会正确访问到所期望的结果,这取决于这块空间是否在这段时间内受到其他资源的利用和侵扰。
所以,尽量不要使用引用返回,一定要万分谨慎。如果一定要以引用返回,还是那句话,此时该对象的生存期不应受到函数生存期的影响。

总结:以引用返回和不以引用返回的区别:

  • 以引用返回,将亡值对象构建在被调用函数的栈帧空间中,在该对象调用析构函数后,已死亡的对象被访问,主函数的使用会受到影响
  • 不以引用返回,将亡值对象构建在主函数的栈帧空间中,使用过后再析构,较为安全。
  • (这里还衍生出一个问题在VS2019中,变量的地址在不同次的运行时是不一样的,这也就更加保证了程序的安全,这与win10兼容,正因为这一机制有时候并不会干预以引用返回的地址空间,所以在这类编译器中可以正常运行,但实际上,以引用返回的对象已经死亡,即便可行,我们仍然不推荐这种写法,在VC6.0中每次的地址一样,是因为会覆盖之前使用的空间。)

运算符重载

运算符的重载实际是一种特殊的函数重载,必须定义一个函数,并告诉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

  • obja = objb.operator=(objc)
  • obja = 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代表主函数中的一个对象,不受该函数的影响,因此可以以引用返回,由此也不会产生其他对象用来过渡

标签:const,函数,Int,Object,value,运算符,C++,operator,构造函数
来源: https://blog.csdn.net/qq_44824574/article/details/122538412

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有