127-C++学习第五弹(引用,构造,移动构造,移动赋值)

构造函数的特点:自动会被调动,自己不能主动调动构造函数
构造函数是拿一种整型数据来初始化对象,也就是内置类型
拷贝构造函数是拿一个对象去初始化另一个对象空间,存活在1.实参对象和形参对象结合时,2.return 返回一个对象时

Int fun(Int x)
{
	int a=x.Value();
	Int tmp(a);
	return tmp;
}

int main()
{
	Int a(10);
	Int b;
	b=fun(a);

	return 0:
}

当它一个实参对象,初始化一个形参对象时,将调动拷贝构造函数,当return tmp; 我们要返回一个临时对象,我们要调动拷贝构造函数,构造一个临时对象,作为返回。因为无法将tmp直接给给b对象,如果直接把tmp给给b对象,将会导致tmp在两个函数内都可以生存,这和我们局部对象只能在函数里有效当函数无效冲突了,在这里,有一个概念:将亡值。返回tmp时,我们将会在主函数的栈帧中构建一个临时对象,当把这个临时对象给b赋值完成之后,这个临时对象的生存期也就到了,我们把这个对象称之为将亡值

析构函数特点:把一个对象,释放它所在的资源
缺省函数:如果你没有写构造函数,系统会给你缺省构造函数(只是创建对象,无法对对象本身进行初始化,数据是随机值),拷贝构造函数(把对象地址抓住,把将要构建的对象的地址抓住,按位进行拷贝)

Int& operator=(const Int& it)
{
}//a=b

缺省构造函数:把b的地址抓住,把a的地址抓住,把b的内容按一个字节一个字节地拷贝给b,按位拷贝

有的时候缺省拷贝构造函数可以满足我们的要求,有的时候不能满足要求,如果没有申请资源,可以满足我们的要求,如果要申请资源,那么就不能满足我们的要求

Int& operator=(const Int& it)
	{
		if (this != &it)//防止自己给自己赋值,a=a
		{
		value = it.value;
		}
		cout << this << " = " << &it << endl;
	return *this;
}//a=b

这里的赋值语句返回的是类型的引用,为什么在这里要返回自身类型的引用?a=b; 返回this指针,this指针指向a,*this就是a对象本身,把自己本身以引用的形式返回,当函数死亡,this指针也死亡,this所指之物a对象不受这个函数的影响,当这个函数结束,a对象依然存在,所以可以引用返回。我们运算符的重载,希望重载的运算符的能力接近于它对于内置类型所具有的能力。为了实现a=b=c,连续赋值的能力 。 a=operator=(&b,c); b调动赋值语句,把b的地址传递进去,作为成员函数,含有this,拿c去初始it,it是c的别名,this指向b的地址,返回的 *this,就是b对象,把b对象赋值给a

Int a(a); 编译器编译时会给你报错,因为你a还没构造完,怎么可以给自己构建呢?

void SetValue(int x) { value = x; }
int GetValue() const { return value; }

int& Value() { return value; }

int& Value() { return value; } 既可以取值又可以赋值

int main()
{
	Int a(10);
	Int x=a.Value();
	a.Value()=100;//把100赋值给Value()的返回值,这个返回值Value对象也就是a的别名 被系统解释成a.value=100;
	return 0;
}

对于普通对象可以去调用它
如果const Int a(10); 变成常对象,说明a里面属性值不可改变,当调动Value以普通引用返回,可以改变value值,产生矛盾,语法出现二义性。
这种函数一般是成对出现

const int& Value() { return value; }
const Int a(10); 

常对象调用常对象方法
普通对象调用普通方法

int funa()
{
	int tmp = 10;
	return tmp;
}
int main()
{
	int x=0;
	x=funa();
	cout<<x<<endl;
	return 0;
}

funa()创建一个整型变量,return tmp,构建一个临时变量作为过渡,给x
当我们程序开始执行时,执行主函数,编译器发现是内置类型,给主函数分配一个栈帧,给了一个x变量,调动funa,在funa分配一个栈帧,定义一个tmp=10;return tmp; 把tmp的值10放在eax,然后funa的return tmp;意味着结束了,分配给的栈帧就结束了,返回到主函数的时候,再通过寄存器eax获取的值给给x
在这里插入图片描述

如果引用接收 int &x=funa(); 是有问题的,引用相当于别名,系统编译的时候,把引用编译成一种指针,当我们调动funa的时候,定义了一个tmp=10;return tmp;我们在寄存器eax,放的是tmp的整型值,这个引用,想着去指向eax或者这个10,是不允许的,将亡值的概念(不能取地址)

 const int &x=funa();  

上面这个方法是可以的,万能引用。虽然说返回的值到达eax
相当于执行
int tmp = eax;
const int &x = tmp;
这里引用的是主函数的一块内存空间

下面这个方法也可以!右值引用将亡值

int&& x = funa();
// int tmp = eax;
// int &&x = tmp;

这个将亡值生存期变得和x一样

下面这段代码非常重要

int& fun()
{
	int x = 10;
	return x;
}

int main()
{
	int a=fun();
	int&b=fun();
	cout<<a<<endl;//打印出10
	cout<<b<<endl;//打印出随机值
	return 0;
}

但是这段代码是错误的!
生存期的问题
如果想以引用的方式返回某个值,此数据或者此变量或者此对象的生存期不地受于fun函数的限制,也就是你引用返回时,当fun这个函数结束时,x这个变量的生存期并没有到期,x仍然要活着。要想把某个变量返回它的地址,或返回它的引用时,这个变量的生存期不受此函数的影响
此例子中,返回x,x的生存期受这个fun函数的影响,fun()结束,x的生存期也就结束。当程序运行中,主函数运行,给主函数分配一个栈帧,定义一个变量a,此时a还没有值,调动fun(),给fun分配一个空间,定义x=10;返回x,以引用返回,引用本质被系统解释成地址,eax存放x的地址,return x;结束,分配给fun的栈帧就销毁了,eax扔指向这个空间,虽说这个空间已经失效了,a是整型变量,不可能存放eax,这一步系统解释:a=*eax;这里有一个问题:这个空间受不受侵扰?如果不受侵扰可以读出10,如果受侵扰不能读出10,这里的b是一个引用,实际上b是一个指针,返回的是引用,x的地址,b指向x的地址,打印a时,a已经获取值,打印b时,是以指向去打印,这里的cout本身是输出函数(开辟栈帧,覆盖原本fun的函数的空间),侵扰这一块内存区域,所以打印的是随机值。
单线程赋值时没有被干扰
多线程一定错误赋值有可能被打断
在这里插入图片描述
打印的时候,已经把10拷贝给a,侵扰它,a已经是10,b并没有拷贝这个值

Int fun()
{
	Int a(10);
	return a;
}
int main()
{
	Int x=fun();//OK
}

当我们调动fun();产生一个对象a,返回的时候,产生一个临时对象a,函数fun结束,a对象消亡,把临时对象a拷贝构造x

Int &x=fun();//error

错误原因:临时对象a是将亡值,不能引用一个将亡值
当函数结束之后,这个临时对象会被析构掉,语法规则不允许

const Int &x=fun();//ok

常引用,引用它构造的对象 this引用到这个主函数的临时对象,this不能改变它

Int&&x=fun();//ok

右值引用 引用将亡值
在这里插入图片描述
在这里插入图片描述
系统有缺省的赋值语句
在这里插入图片描述
在赋值的时候,系统的默认赋值语句是把a的地址,b的地址抓住,把a的值拷贝进来,打印的是10,但是不对。
因为建立a的时候,调用赋值语句的时候,会把原来fun的空间再一次重新洗牌,a的值被覆盖掉。
在这里插入图片描述
打印的仍然是随机值
引用返回的对象已经消亡了

class String
{
	char* str;
public:
	String(const char* s = nullptr)
	{
		if (s != nullptr)
		{
			int len = strlen(s);
			str = new char[len + 1];
			strcpy_s(str, len + 1, s);
		}
		else
		{
			str = new char[1];
			*str = '\0';
		}
	}

	~String()
	{
		if (str != nullptr)
		{
			delete[]str;
		}
		str = nullptr;
	}
};

系统默认的缺省的拷贝构造函数
在这里插入图片描述

String(const String& st)//浅拷贝
	{
		str = st.str;
	}

自己写的深拷贝
在这里插入图片描述

String(const String& st)//深拷贝
	{
		int len = strlen(st.str);
		str = new char[len + 1];
		strcpy_s(str, len + 1, st.str);
	}

在这里插入图片描述
当拿s1给s2赋值时,缺省的赋值函数,把s1的str值给s2的str,导致s2的str直接指过来。这样有什么缺陷呢?
1.内存泄漏2.当析构s2对象,程序崩溃,二次释放

	String& operator=(const String& st)
	{
		str = st.str;
		return *this;//按位赋值,缺省构造
	}

自己写的赋值函数

String& operator=(const String& st)
	{
		if (this != &st)
		{
			delete[]str;
			int len = strlen(st.str);
			str = new char[len + 1];
			strcpy_s(str, len + 1, st.str);
		}
		return *this;
	}

在这里插入图片描述

主函数分配一个s1(未构建),str随机值,当我们去调用fun(),分配一个栈帧,s,str,从堆区分配一个空间,把yhpinghello拷贝进去指向它,普通的返回,按值返回,构建一个临时对象,调动拷贝构造函数,临时对象str也指向一个堆区空间yhpinghello,临时对象构建完成后,函数要结束,s的生存期到了,调用析构函数释放空间,堆区不存在,返回到主函数时,这个临时对象要去构建s1,开辟空间,把yhpinghello拷贝进来,然后临时对象生存期到了,析构函数释放掉,此方法创建多个对象,效率低
在这里插入图片描述

移动构造

系统的移动构造

	String(String&& s)
	{
		str = s.str;
		//delete[]s.str;
		s.str = nullptr;
	}

返回一个s,就是要返回一个将亡值,系统将s初始化给String(String&& s)的s,调动移动构造,把资源移动
在这里插入图片描述
把s对象资源转移给s1,把s.str置为空
把局部对象的资源转移给需要它的
在这里插入图片描述
这两个写法不一样
第一个写法是拿返回的对象构建s1,调动移动构造,把s的资源给s1
第二个写法不会调动移动构造,构造函数是创建对象,拷贝构造函数也是创建对象,移动构造也是创建对象,但是s2这个对象是已经存在的,调动移动赋值函数

String& operator=(String&& s)
	{
		if (this != &s)
		{
			delete[]str;
			str = s.str;
			s.str = nullptr;
		}
		return *this;
	}

原始方法
在这里插入图片描述
如果有移动构造,就方便
在这里插入图片描述
等价

return s;拿移动构造构建临时对象,临时对象的str指针指向s对象的yhpinghello并且把s的str置为空,当这个函数构建完成之后,销毁s对象,不会被yphinghello释放,返回主函数,将亡值调动移动赋值语句,这时候s就是移动赋值的别名
在这里插入图片描述
这个方法new空间只new一次
将返回值s直接构造s1(移动构建)
在这里插入图片描述

移动构造函数,移动赋值函数系统有,但是如果要用,要自己去实现

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值