C++超详细讲解拷贝构造函数

 更新时间:2022年06月02日 08:54:33   作者:iheal  
我们经常会用一个变量去初始化一个同类型的变量,那么对于自定义的类型也应该有类似的操作,那么创建对象时如何使用一个已经存在的对象去创建另一个与之相同的对象呢

构造函数

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

拷贝构造函数是构造函数的一个重载,因此显式的定义了拷贝构造,那么编译器也不再默认生成构造函数。

特征

拷贝构造也是一个特殊的成员函数

特征如下:

  • 拷贝构造是构造函数的一个重载;
  • 拷贝构造的参数只有一个并且类型必须是该类的引用,而不是使用传值调用,否则会无限递归;
  • 若没有显式定义拷贝构造函数,编译器会自己生成一个默认拷贝构造,默认的拷贝构造函数对象按按内存存储和字节序完成拷贝,也叫浅拷贝;
class Date
{
public:
	Date(int year, int month, int day)
		:
		_year(year),
		_month(month),
		_day(day)
	{}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2001, 7, 28);
	Date d2(d1);
	d1.Display();
	d2.Display();
	return 0;
}

输出:

2001-7-28

2001-7-28

对于那些直接管理着内存资源的类(含有指针变量),那么简单的值拷贝还顶得住吗?显然顶不住啊。

通过图示说明:

两个string类的对象指向了同一块空间,这不就乱套了吗,如果其中一个对象通过指针改变了指向内存的数据,那么另一个对象也会受到影响,这是我们不愿发生的,我们希望每个对象都能独立运作。

下面这个程序会崩溃

class String
{
public:
	String(const char* str = "songxin")
	{
		cout << "String(const char* str = \"songxin\")" << endl;
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
		_str = nullptr;
	}
private:
	char* _str;
};
int main()
{
	String s1;
	String s2(s1);
	return 0;
}

原因是两个string类的成员指针都指向一块内存,而它们又分别调用了一次析构函数,相当于对同一块内存空间释放了两次,程序崩溃。

因此对于这种情况的对象,我们就不能再使用编译器生成的默认拷贝构造了,而只能自己去显式的定义拷贝构造并且要实现深拷贝。

编译器生成的拷贝构造

编译器默认生成的拷贝构造会做些什么呢?

  • 对于内置类型成员

​ 完成值拷贝;

  • 对于自定义类型成员

调用成员的拷贝构造;

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:
		_hour(hour),
		_minute(minute),
		_second(second)
	{}
	Time(Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
Time top(0, 1, 1);
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1, Time& t = top)
		:
		_year(year),
		_month(month),
		_day(day),
		_t(t)
	{}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Time t(1, 1, 1);
	Date d1(2001, 7, 28,t);
	Date d2(d1);
	return 0;
}

如果默认生成的拷贝构造没有调用Time类成员的拷贝构造,那么d2的_t的值应该是(_hour = 0, _minute = 0, _second = 0),而这里最终的结果是d2中的_t和d2中的_t值相同。

这里Date类中自动生成的拷贝构造函数的内置类型会进行字节序拷贝,而对于自定义类型_t调用了Time的拷贝构造函数。

拷贝构造的初始化列表

拷贝构造是构造函数的一个重载,因此拷贝构造函数也是有初始化列表的,所以也建议在初始化列表阶段完成对对象的初始化,养成良好习惯。

可以不显式定义拷贝构造函数的情况

  • 成员变量没有指针;
  • 成员有指针,但并没有管理内存资源;

显式定义拷贝构造的误区

之前一直存在这个误区:

我们都知道,编译器生成的构造函数在初始化列表会调用成员的构造函数,而我们显式去定义构造函数时,即使我们不写也会在初始化列表去调用自定义类型成员的构造函数。

通过类比,我就犯了一个低级错误:

就是既然编译器生成的拷贝构造可以在初始化列表自动调用自定义成员的拷贝构造,那么我们显式定义的拷贝构造即使不写,也会在初始化列表自动去调用自定义成员的拷贝构造。

于是我写出了如下代码:

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:
		_hour(hour),
		_minute(minute),
		_second(second)
	{}
	Time(Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
Time top(2, 2, 2);
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1, Time& t = top)
		:
		_year(year),
		_month(month),
		_day(day),
		_t(t)
	{}
	Date(Date& d)//显式定义了拷贝构造
	{
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Time t(1, 1, 1);
	Date d1(2001, 7, 28,t);
	Date d2(d1);
	return 0;
}

通过监视窗口查看d2调用拷贝构造后的值:

并没有拷贝成功。

我只顾着类比它们的功能,可我恰恰忽略了拷贝构造也是一种构造函数啊,那么自然的初始化列表也是和普通构造一样,会去调用自定义类的构造函数,不处理内置类型。只不过编译器生成的是经过处理的构造函数达到了拷贝的效果。(太傻逼了这错误)

结论

拷贝构造函数是构造函数的一种,它也有初始化列表,如果是编译器生成的拷贝构造,它会对内置类型做字节序拷贝,对自定义类型成员会调用自定义成员的拷贝构造。

可如果是我们显式定义出的拷贝构造,它也是有初始化列表的,但是它的初始化列表可不会去调用成员的拷贝构造奥,而是和普通构造函数一样,对于内置类型成员不去初始化值,对于自定义类型成员调用自定义成员的构造函数而不是拷贝构造函数。

到此这篇关于C++超详细讲解拷贝构造函数的文章就介绍到这了,更多相关C++拷贝构造函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • c++ Bellman-Ford算法的具体实现

    c++ Bellman-Ford算法的具体实现

    Bellman-Ford算法用于解决有边数限制的最短路问题,且可以应对有负边权的图,本文主要介绍了c++ Bellman-Ford算法的具体实现,感兴趣的可以了解一下
    2021-06-06
  • C语言完整特性详情

    C语言完整特性详情

    这篇文章主要介绍了C# 10的相关资料方法,感兴趣的朋友可以参考下文
    2021-08-08
  • 解析C++浮点数无效值的定义与无效值判定的小结

    解析C++浮点数无效值的定义与无效值判定的小结

    本篇文章是对C++中浮点数无效值的定义与无效值的判定进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C语言实现中缀表达式转换为后缀表达式

    C语言实现中缀表达式转换为后缀表达式

    这篇文章主要为大家详细介绍了C语言实现中缀表达式转换为后缀表达式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • C++内存管理详细解析

    C++内存管理详细解析

    这篇文章主要给大家分享的是C++内存管理的详细内容学习,下面文章围绕C++内存管理的相关资料展开具体学习内容,需要的朋友可以参考一下,希望对你有所帮助
    2021-11-11
  • C++深入分析回顾函数重载

    C++深入分析回顾函数重载

    C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading),借助重载,一个函数名可以有多种用途
    2022-06-06
  • C++ 实现求最大公约数和最小公倍数

    C++ 实现求最大公约数和最小公倍数

    这篇文章主要介绍了c++ 实现求最大公约数和最小公倍数的相关资料,需要的朋友可以参考下
    2017-05-05
  • 基于Qt制作一个定时关机的小程序

    基于Qt制作一个定时关机的小程序

    这篇文章主要为大家详细介绍了如何基于Qt制作一个有趣的定时关机的小程序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-12-12
  • 浅谈C++为什么非要引入那几种类型转换

    浅谈C++为什么非要引入那几种类型转换

    这篇文章主要介绍了C++为什么非要引入那几种类型转换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • C语言排序之 堆排序

    C语言排序之 堆排序

    这篇文章主要介绍了C语言排序之堆排序,文章基于C语言的相关资料展开详细内容,具有一定的参考资料,需要的小伙伴可以参考一下
    2022-04-04

最新评论