拷贝构造函数和重载运算符的理解

1.拷贝构造函数和构造函数的区别

  1. 定义上:构造函数主要是为了初始化对象,而拷贝构造对象是使用对象A来初始化对象B。
  2. 形参上:拷贝构造函数的参数必须为类的引用类型,而构造函数的参数为类的成员变量的类型。
  3. 实参上:调用函数时,拷贝构造函数的实参为某一个对象,例如Test t2(t1)。而构造函数的参数的类型和成员变量的类型一样,例如Test(1)。

2.拷贝构造函数在什么情况下会被调用

  1. 对象初始化对象时会被调用
  2. 调用函数时,形参的类型为非引用类型,即为类类型时。
  3. 函数的返回值为类类型时,(返回的对象为非引用类型时)。

3.运算符的重载

运算符的重载出现是有意义的,在c++中我们需要对自定义类型的变量进行运算 时,由于编译器只支持内置类型的运算符,例如我们可以计算 int a+b;而如果a 和 b 为类类型,就必须要重载运算符,我们可以将其理解为带有特殊名称的函数:函数名为operator 和重载的运算符符号构成。
它的定义为:

返回值 operator 操作符(参数列表)

3.1 赋值运算符的重载

Test& operator =(const Test &t)
{
	if(this !=&t)
		m_data =t.data;
	return *this;
}

类中编译器提供了默认的赋值重载函数。
需要注意的有以下几点:

  1. 参数为引用传递,对象为常对象,防止在内部修改t的值,而引用传递减少了一次拷贝和析构。
  2. 返回值为引用类型,因为这里返回的对象不是局部对象,所以可以使用引用传递,避免了无名对象的生成。同时也可以对对象进行连续赋值。
    例如:t3=t2=t1;t3.operator=(t2.operator=(t1)),如果返回值为void类型:无法将返回值当作下一次赋值的参数。

3.2 前置++和后置++运算符的重载

为了重载前置++和后置++,编译器通过形参类型来区别,前置++为 Test& operator++ ();前置++的返回类型为引用类型,这样做的原因式与内置类型的行为保持一致。前置++返回的总是被自增的对象本身。
而后置++为 Test operator++(int)。(许多人可能会觉得:为什么用int区别,编译器规定的,因为运算符重载函数只有返回值不同,是不能区分函数的,所以形参必须有所区别。因此一个无参数,一个形参的类型为int)因为后置++需要返回的是对象本身,因此使用该对象拷贝tmp对象,再将原本的成员进行自增,返回tmp对象。注意后置++不能以引用的形式返回,因为返回的是局部对象。
代码如下:

//前置++;++a;即a首先加1,再进行表达式的运算;
Test& operator++ ()
{
	m_data=m_data+1;
	return *this;
}
//后置++;a++;先进行表达式的运算,再自加1.a++的结果为a;
Test operator++ (int)
{
	Test tmp(*this);
	m_data =m_data +1;
	return tmp;
}
//下面和上面本质上是一样的,因为前置++已经写了,所以可以直接使用。
Test operator++ (int)
{
	Test tmp(*this);
	++(*this);
	return tmp;
}

3.3 输入输出运算符的重载

ostream& operator<<(ostream &out ,const Test &t)
{
	out<<t.m_data<<endl;
	return out;
}
istream& operator>>(istream &in, Test &t)
{
	in>>t.m_data>>endl;
	return in;
}

需要注意的有以下几点:

  1. 对于输出操作符来说, 第一个参数out为对cout(标准输出对象)的引用,该形参是一个引用类型,因为我们不能拷贝ostream对象(因为在c++定义的标准输入和输出流中,拷贝构造函数被放在了private 部分,只有声明,没有定义)
  2. 第二个形参为对输出类类型的引用,使用引用,可以减少一次拷贝构造函数的调用。可以添加const,因为输出的话一般不会改变该对象。
  3. 返回值必须为ostream引用,首先因为拷贝函数是私有的,如果返回的是类类型,那么无法生成无名临时对象。其次,可以实现连续的输出,对于该例子来说:cout<<t1<<t2;第一次赋值返回的为cout,它可以作为第二次赋值时的实参传递给out参数。如果返回值不是引用类型的话,只能实现cout<<t1,cout<<t2。//等价于operator(cout,t1)<<t2;
  4. 对于输入操作符来说:第二个形参是要读入到的对象的引用,它必须是非常量,因为这个数据是我们要输入的对象,所以它必须是可以修改的。

4.友元函数的出现

我们知道对于类来说,它具有自己的成员函数,成员函数的第一个参数为隐藏的this指针。而如果一个函数不属于该类的成员函数,而想访问该类中的private和protect成员,那么我们就需要声明该函数为该类的友元函数,它的声明位置在类中,声明方式为friend +函数的声明。声明之后,该函数即可以访问类中的所有的成员。

4.1 友元类

友元类:A类声明为B类的朋友,那么B中所有的成员函数和成员变量(私有还是公有),A均可以访问。友元函数不具备传递性:A是B的朋友,但B并不是A的朋友,如果想要相互访问彼此的成员,那么B必须也要声明为A的友元才可以。
同时,需要注意的是友元关系不能传递,即A是B的友元,B是C的友元,但不能说明A是C的友元。
一般来说:如果我们需要互相访问彼此的私有成员函数,最好将类的声明和定义分别来写,即头文件中进行类的声明,而源文件中进行类中成员函数的定义。

5.浅拷贝和深拷贝的问题

对于一个对象来说,编译器提供了6种默认的函数,分别是构造函数,拷贝构造函数,赋值运算符,析构函数,取地址运算符,const取地址操作符
编译器提供的默认的拷贝构造函数和赋值运算符是按照成员进行拷贝和赋值的,也就是所谓的浅拷贝。
如下图所示:Test t2=t1;我们会创建一个对象t2,t2里面的成员为m_data,将t1里面的5赋值给t2里面的m_data,即完成了初始化。同理赋值也是一样的,这就是系统默认构造函数干的事情!既然编译器已经帮助我们完成了拷贝和赋值,那么我们为什么还要显示的定义自己的拷贝构造和赋值函数呢。
在这里插入图片描述

考虑下面这种情况:t1的数据成员为指针,我们利用new 动态开辟了一块内存空间,存放字符串hello,c++,将地址返回给指针m-data;由于是在堆上开辟的空间,在主函数运行结束之后,我们需要调用析构函数(即delete 来释放堆空间)。
当我们使用系统默认的拷贝构造函数时,只是拷贝了这个地址空间,由于同一块空间不能被释放两次,在调用析构函数时,程序就会发生崩溃。
因此,我们不能只拷贝指针的指向,如图片3所示,还需要申请一块一模一样大小的空间,来存放该字符串。
图片1
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值