c++ 拷贝构造函数与赋值运算符重载函数的区别是

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++ 拷贝构造函数与赋值运算符重载函数的区别是相关的知识,希望对你有一定的参考价值。

复制构造函数接受单个类类型引用形参,这个形参一般用const修饰。
class A
public:
A();;
A(const A&);
//.........
;
一般的类,编译器合成的构造函数就能完成必要的工作。担当类有一个数据成员是指针,或者成员表示在构造函数中分配的其他资源;在创建新对象时必须做一些特定工作。这两种情况必须定义复制构造函数。

复制操作符重载,由operator后面跟所定义的操作符符号,通过定义名为operator=函数来对赋值进行定义。该操作符函数有两个形参:第一个形参对应左边的操作数(隐式绑定到this指针了),第二个形参对应右操作数。返回类型应该与内置赋值运算返回的类型相同,内置类型的赋值运算返回对右操作数的引用,赋值操作符也返回对同一类型的引用。
class B

public:
B& operator=(const B &);
;

可以使用合成复制构造函数的类通常也可以使用合成赋值操作符。一般来说,如果类需要复制构造函数,也就需要赋值操作符。

OK……
参考技术A 简单的来说是,拷贝构造函数是用一个已存在的对象去构造一个不存在的对象(拷贝构造函数毕竟还是构造函数嘛),也就是初始化一个对象。而赋值运算符重载函数是用一个存在的对象去给另一个已存在并初始化过(即已经过构造函数的初始化了)的对象进行赋值。 它们定义上的区别,楼上的已经说过了。

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载、Const成员)详细解读

构造函数

什么是构造函数?

当建立一个对象时,通常最需要立即做的工作是初始化对象,如对数据成员赋初值等
构造函数就是用来在创造对象时初始化对象,为对象数据成员赋初始值
——初始化对象+申请资源

下面以创建Date对象举例说明:
创建Data对象的同时并给对象设置一个值

C++提供了构造函数(constructor)来处理对象的初始化问题构造函数是类的一种特殊成员函数,不需要人为调用,而是在建立对象时自动被执行。换言之,在建立对象时构造函数就被自动执行了,而且在上述代码中:

1.名字与类名相同,创建类类型对象时由编译器自动调用
2.在对象的声明周期只调用一次

构造函数的特点

构造函数的特性其主要特征如下:
1.名字与类名相同
2.无返回值
3.对象实例化时编译器会自动调用构造函数
4.构造函数可以重载


在上述代码中,带参构造函数和无参构造函数形成重载

如果我们在类中没有写一个构造函数,那么我们调用对象时,编译器自动生成一个默认构造函数。一旦用户在类中定义了一个构造函数,则这个默认构造函数将不会生成,也就是说首先会调用用户定义的构造函数。

在Data类中没有一个构造函数,当我们创建一个对象时,编译器会自动调用默认构造函数将a进行了初始化,只是a中的成员变量为随机值,使用无参的构造函数不需要()

类中无构造函数,调用系统默认构造函数,初始化后a的值为随机值,具体代码如下:

综上:我们建议定义出一个全缺省构造函数,因为它可以灵活应用,它既可以传参,也可以不需要传参也能够初始化对象。

析构函数

析构函数概念

用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特殊的成员函数——析构函数,用该函数去完成一些资源清理工作——释放资源

例如构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。
那么如何去声明和定于析构函数?

析构函数特性

析构函数是特殊的成员函数

其特征如下:
1.析构函数是在在类名加 ~
2.一个类只能有一个析构函数,如果类中没有定义一个析构函数,则系统会自动生成一个默认的析构函数
3.无参数无返回值。
4.对象声明周期结束时会编译器会自动调用析构函数。

将SeqList中的变量s进行初始化:

下面程序,我们看到编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

我们自己没有写析构函数,那么编译器会去调用系统默认的析构函数,而这个析构函数对内置类型不做处理,对自定义类型会去调用它自己的析构函数


注意:析构函数是无参数返回的函数且没有参数,一个类只有一个析构函数,不写的话则会生成默认析构函数

拷贝构造函数

拷贝构造函数的概念

拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)。

在下述代码中,d1调用拷贝构造函数将d的内容拷贝给自己,虽然没有定义拷贝构造函数,然而系统调用默认的拷贝构造函数。

拷贝构造函数的特征

拷贝构造函数也是特殊的成员函数,特征如下:

1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
3.若未显示定义,系统生成默认的拷贝构造函数 (默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝)

如果拷贝构造函数的参数为为传值,那么在传值的过程会去调用拷贝构造函数去拷贝一个临时变量,每一次调用都会去在调用拷贝构造函数,以此类推,则会引起无穷无尽的调用拷贝构造函数。所以拷贝构造函数必须传引用

data(data &a)为语法要求,不会为a开辟空间,仅当a的别名使用,不会触发其他函数调动
如果不加&,对象初始化对象就会调动拷贝构造函数,就得引发下一个a,这样下去就没完没了了,不允许产生无限递归的现象。

4.如果说编译器生成默认拷贝构造函数就可以实现拷贝的话,是不是自己就不用写了?对于下面这种类,程序运行是崩溃的,那么我们就需要用深拷贝来解决。

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型operator操作符
示例:

说白了运算符重载就是要给
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置类型+,不能改变其含义
*,::,sizeof,、以上5个运算符不能重载

运算符重载实际上就是给这个所谓的运算符赋一个其它的涵义
t4给t3赋值其实可以直接调动成员函数的方法,类似于t4.Assign(t3),t4.Assign(t3)与t4.operator=(t3)的结果是一致的。但是在C++中更想要简单一点(不希望调函数,比如t1+t2,我们不希望写成add(t1,t2),而是直接t1+t2),其实在这里,t4=t3 中的=已经不是一个“=”了,而是 operator= 但是这里的operator可以省略,所以说 这里的 = 就不是等号了,而是一个“函数”。

问题1:

上述代码中——Test * operator = (const Test &t) 这个const可不可以不要?,还有&
当然可以取消,取消不影响代码结果,但是为了程序的运行效率,取消&的话,会多调用一次拷贝构造函数,注意析构函数是从后往前析构也就是说加了&会提升程序的运行效率(少一次调用拷贝构造,当然也少一次析构,不开辟空间,不花费时间)具体示例如下:

因为引用的对象我们一般是不希望被改变的,因此加上const,if(this !=&t)是防止自己给自己赋值

对于上述代码,return *this返回后 this指针的值是存在的,所以上述Test后面可以加&进行引用,所以说能不能引用返回,就看函数结束后,被引用的对象还活着没有而且建议用引用,因为可以少调动一次拷贝构造函数。

综上:赋值运算符主要有四点:
1.参数类型
2.返回值
3.检测是否给自己赋值
4.返回*this
5.一个类如果没显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝

对于如下这么多对象,他们构造,拷贝构造,析构的顺序及对应的流程如下:

下面这种办法少产生一次拷贝构造的调用,就是说拷贝构造产生的无名临时变量,直接就是t4的值。而不是像上述代码一样多拷贝了一次,然后再将拷贝后的值给t4,这样增加了程序的运行效率

运算符重载就是给+重新定义一个新的功能
重载+

下面为运算符重载的相关代码;
// An highlighted block
class MyInt;
ostream& operator<<(ostream &out, const MyInt &t);

class MyInt

	friend ostream& operator<<(ostream &out, const MyInt &t);//声明友元函数

public:
	MyInt(int i = 0)
	
		m_i = i;

	

public:
	MyInt operator+(const MyInt &x)
	
     	return MyInt(m_i + x.m_i);
	
	MyInt operator-(const MyInt &x)
	
		return MyInt(m_i - x.m_i);
	
	MyInt operator*(const MyInt &x)
	
		return MyInt(m_i * x.m_i);
	
	MyInt operator/(const MyInt &x)
	
		return MyInt(m_i / x.m_i);
	
	MyInt operator%(const MyInt &x)
	
		return MyInt(m_i % x.m_i);
	


public:
	MyInt& operator+=(const MyInt &x)
	
		m_i += x.m_i;
		return *this;
	

	MyInt& operator-=(const MyInt &x);
	MyInt& operator*=(const MyInt &x);
	MyInt& operator/=(const MyInt &x);
	MyInt& operator%=(const MyInt &x);


public:
	//a > b  x.operator>(b)
	bool operator>(const MyInt &x)
	
		return m_i > x.m_i;
	
	bool operator<=(const MyInt &x)
	
		return !(*this > x);//当前对象比x大然后取反就是小于等于就是真
	
	bool operator<(const MyInt &x)
	
		return m_i < x.m_i;
	
	bool operator>=(const MyInt &x)
	
		return !(*this < x);
	
	bool operator==(const MyInt &x)
	
		return m_i == x.m_i;
	
	bool operator!=(const MyInt &x)
			
		return m_i != x.m_i;
	

public:
	MyInt& operator++() //ǰ++
	
		m_i++;
		return *this;
	
	MyInt operator++(int) //++
	
		MyInt tmp = *this;
		m_i++;
		return tmp;
	

private:
	int m_i;
;



ostream& operator<<(ostream &out, const MyInt &t)//友元函数实现

	out << t.m_i;
	return out;


void main()

	MyInt a = 10;
	MyInt b = 20;
	MyInt c;

	c = a + b;
	c = a - b;
	c = a * b;
	c = a / b;
	c = a % b;

	cout << "c = " << c << endl;
	a += b;

	if (a > b)
		cout << "a > b" << endl;
	else
	    cout << "a <= b" << endl;


	MyInt x = a++;
	MyInt y = a--;

定义复数类的运算符重载实现:


对于Complex y = c1+10来说可以,但是Complex y = 10+c1就不可以。c1是个对象,+是函数,函数要被对象才能驱动,c1+10 实际上是c1.operator。10不能构造成临时对象,10+的话就不匹配重载的+号了,而是想象成普通的运算符了。
当然如果非要Complex y = 10+c1成立的话,这里需要用到友元函数来操作,如下:

重载为友元函数,且友元函数必须给出成员的参数,成员函数和友元不一样,不需要加类的作用域限定符::

下面运算符输出流的重载,它通常作为某个类的友元函数出现

下面为C++输出的时候,cout不管任何情况就可以直接输出各种类型的结果,会自动匹配类型。
cout其实是ostream的一个对象(endl同样也是一个对象的概念)而且是一个全局对象,所以引入头文件后都可以使用。

ostream其实是这个东西的一个命名方式,是一个基本输出流对象 basic_ostream

表面上看起来它什么都不管,实际上背后可以生成各种类型的类型,相当于调用了不同的运算符重载,这些底层的代码我们平时是看不到的。

下面举相关个别运算符重载:


ostream是输出的一种类型,cout是out的对象,为什么out可以引用cout,因为他们两个是同一种类型,那为什么要重载为友元,是因为希望第一个参数就是cout,第二个参数才是对象,如果不重载友元,那么第一个参数只能用对象去驱动它。最后把cout返回,然后继续往后输出。

Const成员

将const修饰的类成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在函数成员函数中不能对类的任何成员进行修改。

const修饰的方法我们称之为常方法 ,如下代码我们在函数后面加了const实际上就是对内部的this指针变量加了限制不可改变。

普通的对象调动常方法是没有问题的,常对象调用普通方法就不行

那么什么时候写成常方法?
要修改数据成员的话则不能写成常方法,如果只为读而不改,则强烈建议写成常方法。c++采用最佳匹配原则,常方法和普通方法可以共存。

补充

六种类的默认成员函数:
构造函数,析构函数,拷贝构造函数,运算符重载之外的另外两种为:

普通对象取地址符,常对象取地址符

下面为六种类的默认函数的基本代码表示:

以上是关于c++ 拷贝构造函数与赋值运算符重载函数的区别是的主要内容,如果未能解决你的问题,请参考以下文章

C++类与对象(详解构造函数,析构函数,拷贝构造函数,赋值重载函数)

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类与对象第二篇:(构造函数析构函数拷贝构造运算符重载取地址及const取地址操作符重载)

C++类的成员函数:构造析构拷贝构造运算符重载