C++ 类的默认成员函数- 拷贝构造函数

 个人主页:Jason_from_China-CSDN博客

所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客

所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客

 概念概述

  • 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解一下即可。其次就是 C++11 以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习。

包括

 拷贝构造函数

概念概述

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

同时C++规定了,这里是规定,传值传参必须调用拷贝构造

拷贝构造函数的特点

  1. 拷贝构造函数是构造函数的一个重载。
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  3. C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
  4. 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
  5. 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的拷贝构造完成的值拷贝 / 浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝 (对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显式实现 MyQueue 的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
  6. 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名 (引用),没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回

拷贝函数的使用

拷贝构造的实现其实是很简单,这里先举出实例讲解,之后会进行解释,主要就是传参的时候,如果传递的是传值传参,那么需要用引用进行接收,不然会导致死循环从而导致崩溃

下面的的代码里面首先我们实现声明和实现的分离

//.h
//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//.cpp
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();
	return 0;
}

运行代码,可以清楚的看见,两个拷贝构造都实现成功,这两种形式,看看你喜欢哪一种拷贝构造实现形式

拷贝构造的过程

//.h

//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}


//.cpp
void Func(Date& d)
{
	cout << "拷贝构造函数的调用" << endl;
	d.print();
}
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);
	return 0;
}

这里我们观察拷贝构造是如何完成的

第一步,类类型的,传值传参

第二步,返回函数

全过程图解

在这个例子中,void Func(Date& d)中的&可以写也可以不写,但加上引用可以避免不必要的拷贝构造函数调用,提高效率。

如果写成void Func(Date d),在调用Func函数时,会调用拷贝构造函数将实参对象拷贝一份传递给函数参数d,可能会有一定的性能开销,尤其是当Date类比较复杂时。

如果写成void Func(Date& d),则是通过引用传递参数,不会调用拷贝构造函数,只是传递了对象的引用,效率更高。综上所述,从性能角度考虑,建议加上引用符号&。

所以可以进行与优化,我们函数依旧采取引用接收,减少拷贝

拷贝构造函数的原理解释

首先我们需要明确一点就是,C++在传值传参的时候会调用拷贝构造

什么是传值传参,顾名思义肯定是传递数值。

如果我们传值传参,但是接收的话不采取引用接收,就会导致一直情况

拷贝构造和指针

class MyClass
{
public:
	MyClass();
	MyClass(MyClass* M);
	//打印函数的实现
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
//构造函数
MyClass::MyClass()
{
	_year = 1999;
	_month = 2;
	_day = 28;
	cout << "MyClass构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现
MyClass::MyClass(MyClass* M)
{
	_year = M->_year;
	_month = M->_month;
	_day = M->_day;
}

int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);

	//拷贝构造和指针的联合实现
	MyClass M1;
	MyClass M2 = M1;
	M2.print();
	return 0;
}

拷贝构造函数需要加上conts和不加const的区别

这里是有一点难度的

接下来 我们给出一个代码,但是这个代码是一个典型的错误代码:此时我们发现是报错的

这里解决报错的原因需要在拷贝构造函数上面加上const,因为这里存在权限放大的行为,之前我们就说过权限可以缩小,但是不能放大。

这里就产生了权限放大的问题,所以我们需要解决权限放大的问题

接下来我们上正确的代码

class MyClass
{
public:
	MyClass(MyClass* M);
	MyClass(int year, int month, int day);
	MyClass(const MyClass& M);
	//打印函数的实现
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
//构造函数
MyClass::MyClass(int year = 1999, int month = 2, int day = 28)
{
	_year = year;
	_month = month;
	_day = day;
	cout << "MyClass构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现,这里就会导致这里只是一个普通的构造函数
//MyClass::MyClass(MyClass* M)
//{
//	_year = M->_year;
//	_month = M->_month;
//	_day = M->_day;
//}
//拷贝构造的实现
MyClass::MyClass(const MyClass& M)
{
	_year = M._year;
	_month = M._month;
	_day = M._day;
}


//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(const Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
//Date::Date(Date* const this, Date& d)
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

void Func(Date& d)
{
	cout << "拷贝构造函数的调用" << endl;
	d.print();
}

Date F()
{
	Date d1;
	return d1;
}

MyClass f()
{
	MyClass M1(12, 12, 12);
	return M1;
}
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);

	拷贝构造和指针的联合实现
	MyClass M1(2000, 1, 1);//初始化2000
	MyClass M2;//默认初始化 1999
	MyClass M3 = M1;
	//M2.print();
	M3.print();

	//const在拷贝构造函数里面的使用
	cout << "拷贝构造const的实现"<<endl;
	MyClass M4 = f();
	Date d4 = F();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值