C++中的复制构造函数详解

目录
  • 复制构造函数
  • 复制构造函数的三种调用
  • 复制构造函数的禁用
  • 深拷贝与浅拷贝
  • 一定会生成默认复制构造函数吗?
  • 参考
  • 总结

普通变量的复制

有时我们会在定义一个变量的同时使用另一个变量来初始化它。

int a_variable=12;
int new_variable(a_variable);

通过已有的同类型变量来初始化自身很有用。
对自定义类型的对象是否可以通过一个存在的对象方便的复制呢?

复制构造函数

复制构造函数又叫做拷贝构造函数,它只有一个参数(既然需要复制,一个就够了,若传入两个相同对象则没有意义,若传入两个不同的对象,就没必要叫做复制构造函数了),参数类型为本类的引用。

如果程序员没有编写复制构造函数,编译器会自动生成复制构造函数,在复制构造函数中按照成员变量进行逐字节复制(初学可以这样理解,实际上,个别编译器并不总是会自动生成复制构造函数,它们可能采用直接将源对象的各个值复制到目标对象对应的成员变量上,后面会介绍这种情况)。

class MyDate
{
	int day_;
	int year_;
public:
	MyDate(int day, int year)
	{
		day_ = day;
		year_ = year;
	}
	MyDate(const MyDate& date)
	{
		day_ = date.day_;
		year_ = date.year_;
		cout << "Date类的复制构造函数执行了!" << endl;
	}
	~MyDate() {}
};
void test()
{
	MyDate date1(12, 2021);
	MyDate date2(date1);
}
int main()
{
	test();
	system("pause");
	return 0;
}

执行为:

对于MyDate(const MyDate& date)参数列表中的const,因为复制构造函数参数另一个对象引用,如果不加const修饰,在此复制构造函数中可能会改变原对象的内容,为了安全起见,应加尽加。

如果程序员编写了复制构造函数,则编译器就不会生成默认复制构造函数了(所以编写了复制构造函数之后就尽量在函数体内实现复制操作,不要定义了复制构造函数却不完全或不实现复制操作)。

另一方面,所有的构造函数(包括复制构造函数)、析构函数都无法从父类继承,只能自己实现。

构造函数如果只有一个参数且这个参数为本类对象,就会与复制构造函数起冲突。如图:

复制构造函数的三种调用

复制构造函数在以下3种情况下会被调用:

1.当使用一个A类型对象去初始化另一个A类型的对象时(刚创建好的,已创建好的不算),会调用复制构造函数。注意观察以下代码:

Date date1(12,2012);//创建一个date1对象
Date date2(date1);//调用复制构造函数
Date date3=date1;//也会调用复制构造 函数
date2=date1;//date2已存在,不会调用复制构造函数,会调用赋值=操作函数

可以看到复制构造函数只调用了两次。

2.我们都知道C++传参有传值和传引用(指针本质上是传值,传的是实参的地址)。如果函数参数是一个自定义对象,那么会调用该自定义对象的复制构造函数。

在传值的时候,编译器会开辟一个空间(创建了一个临时对象)存储实参的值(这个过程会将实参的各个值分配复制给临时对象),再将该值压入栈中。

//类声明略
void TestFunction(MyDate date)
{
	cout << "TestFunction()执行了!" << endl;
}
void test()
{
	MyDate date1(12, 2021);
	TestFunction(date1);
}
//main函数略

结果如下:

3.如果函数的返回值是类MyDate的对象,则函数返回时,会调用该对象的复制构造函数。

//类声明略
MyDate TestFunction2()
{
	MyDate date1(12, 2021);
	cout << &date1 << endl;
	return date1;
}
void test()
{
	cout << &TestFunction2() << endl;
}
//main函数省略

从复制构造函数内的输出被两个地址输出夹住即可看出在哪里调用了复制构造函数。

复制构造函数的禁用

如果不希望自定义类型的复制构造函数被调用。

仅仅不编写复制构造函数是不行的,编译器可能会自动生成默认的复制构造函数。应该使用private修饰复制构造函数(这时编译器就不会生成自动复制构造函数),此时不要实现这个复制构造函数,如下:

这样便既禁止了用户调用此复制构造函数,又禁止了用户通过其他成员函数或友元函数间接地调用它(如果我们仅仅把复制构造函数声明为private,声明并实现了复制构造函数,虽然避免了用户直接调用,但成员函数和友元函数还是可以调用,只有不实现它才能永绝后患)。

Bjarne Stroustrup认为如果你希望禁止某些操作,就把它定义为一个私有的成员函数即可。

深拷贝与浅拷贝

如果成员变量含有指针类型,默认复制构造函数并不会将指针指向的内存中的值进行赋值,仅仅将指针存储的值(也就是一个地址)复制了一次(与我们所希望的不一致)。这时两个指针指向了同一块内存空间,一旦一种一个指针所属的对象声明周期结束,会调用它自己的析构函数回收指针指向的内存空间。这时另一个指针遍指向了一个垃圾值,这个指针也变为了空悬指针。以上就是我们常提到的浅拷贝。

实际开发当中要竭力避免以上清情况的发生(当成员变量含有指针或动态分配内存等情况)。

深拷贝如下:

class MyDate
{
private:
	char* buffer_;
public:
	MyDate(const char *init);//实现略
	MyDate(const MyDate &date)
	{
		if(date.buffer_!=nullptr)
		{
			buffer_=new char[strlen(date.buffer_)+1];
			strcpy(buffer_,date.buffer_);
		}
		else
		{
			buffer=nullptr;
		}
	}
}

复制构造函数先检查date中的buffer_的字符串大小,然后分配此大小+1的内存给新创建的对象的buffer_(strlen函数不会计算'\0'字符),最后使用strcpy函数将date的buffer_指向的内存中的内容复制到新创建的对象的buffer_所指向的空间(strcpy函数会吧'\0'字符一并复制)。最后实现了两个指针指向了不同的存储空间(两个空间的内容相同)。

如果我们要编写需要字符的成员时,尽量使用string。它会像其他成员变量一样进行复制,因为string有自己的复制构造函数。

一定会生成默认复制构造函数吗?

我们前面提到如果程序员没有定义自己的复制构造函数,编译器会为我们生成一个默认复制构造函数。

实际上以下4中情况编译器会为我们生成默认复制构造函数。

1.没有为类编写复制构造函数,但该类含有自定义类型或string等类型作为成员变量时。

2.没有为类编写复制构造函数,但该类继承了一个含有复制构造函数的类时,编译器会生成默认复制构造函数,在该函数中调用父类的复制构造函数。

3.没有为类编写复制构造函数,但是该类定义了虚函数或者该类的父类定义了虚函数。,

4.没有为类编写复制构造函数,但是该类有虚基类。

参考

The C++ Programming Language (美) Bjarne Stroustrup

Thinking in C++ Volume One:Introduction toStandard C++ (美)Bruce Eckel

C++新经典 对象模型 王建伟

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 详解C++中对构造函数和赋值运算符的复制和移动操作

    复制构造函数和复制赋值运算符 从 C++ 11 中开始,该语言支持两种类型的分配:复制赋值和移动赋值. 在本文中,"赋值"意味着复制赋值,除非有其他显式声明. 赋值操作和初始化操作都会导致对象被复制. 赋值:在将一个对象的值赋给另一个对象时,第一个对象将复制到第二个对象中. 因此, Point a, b; ... a = b; 导致 b 的值被复制到 a 中. 初始化:在以下情况下将进行初始化:声明新对象.参数通过值传递给函数或值通过值从函数返回. 您可以为类类型的对象定义"

  • 详谈C++何时需要定义赋值/复制构造函数

    继承和动态内存分配 假设基类使用了动态内存分配,而且定义了析构函数.复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数.复制构造函数和赋值函数. 当基类和派生类采用动态内存分配时,派生类的析构函数.复制构造函数.赋值运算符都必须使用相应的基类方法来处理基类元素.这种要求是通过三种不同的方式来满足的.对于析构函数.这是自动完成的,也就是说在派生类的析构函数中无需显示调用基类的析构函数.对于构造函数,这是通过在初始化成员列表中调用基类的复制构造函数来完成

  • C++中复制构造函数和重载赋值操作符总结

    前言 这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容: 1.复制构造函数和重载赋值操作符的定义: 2.复制构造函数和重载赋值操作符的调用时机: 3.复制构造函数和重载赋值操作符的实现要点: 4.复制构造函数的一些细节. 复制构造函数和重载赋值操作符的定义 我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类: 复制代码 代码如下: class CTes

  • 关于C++复制构造函数的实现讲解

    复制构造函数是一种特殊的构造函数,有一般构造函数的特性.它的功能是用一个已知的对象来初始化一个被创建的同类对象.复制构造函数的参数传递方式必须按引用来进行传递,请看实例: #include <iostream> #include <cstring> using namespace std ; class Student { private : char name[8]; int age ; char sex ; int score ; public : void disp(); /

  • C++中的复制构造函数详解

    目录 复制构造函数 复制构造函数的三种调用 复制构造函数的禁用 深拷贝与浅拷贝 一定会生成默认复制构造函数吗? 参考 总结 普通变量的复制 有时我们会在定义一个变量的同时使用另一个变量来初始化它. int a_variable=12; int new_variable(a_variable); 通过已有的同类型变量来初始化自身很有用. 对自定义类型的对象是否可以通过一个存在的对象方便的复制呢? 复制构造函数 复制构造函数又叫做拷贝构造函数,它只有一个参数(既然需要复制,一个就够了,若传入两个相同

  • C++中的拷贝构造函数详解

    目录 C++拷贝构造函数(复制构造函数)详解 1) 为什么必须是当前类的引用呢? 2) 为什么是 const 引用呢? 默认拷贝构造函数 总结 C++拷贝构造函数(复制构造函数)详解 拷贝和复制是一个意思,对应的英文单词都是copy.对于计算机来说,拷贝是指用一份原有的.已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据.例如,将 Word 文档拷贝到U盘去复印店打印,将 D 盘的图片拷贝到桌面以方便浏览,将重要的文件上传到百度网盘以防止丢失等,都是「创建一份新数据」的意思. 在

  • java中深复制知识点详解

    在正式开始深复制的讲解之前,我们先来理解一下概念.假设一个物品需要批量生产,但是这个物品还配有赠品,生产的时候需要把赠品也列在计划内.所谓深复制的原理就是这样,我们不能只复制属性,包括引用之类的附带也需要被复制.下面小编就为大家带来深复制的两种不同方法. 1.序列化实现 如下为谷歌Gson序列化HashMap,实现深度复制的例子: public class CopyDeepMapTest { public static void main(String[] args) { HashMap<Int

  • C++中构造函数详解

    构造函数按参数为为:有参构造函数和无参构造函数 按类型分为:普通构造函数和拷贝构造函数 构造函数的三种调用方法:括号法,显示法,隐式转换法: //括号法 Person p1; //默认构造 无参构造 Person p2(13); //有参构造 Person p3(p2); //拷贝构造 //注意:使用无参构造时不要写括号.不然系统会认为该语句是函数声明. 例:Person p1(); //显示法 Person p1; Person p2 = Person(13);//有参构造 Person p3

  • javascript设计模式之对象工厂函数与构造函数详解

    下面通过文字详解加代码分析的方式给大家分享下javascript设计模式之对象工厂函数与构造函数的相关知识. 概述使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法.然而,除了这两种常用的对象创建方式,JavaScript还提供了其他方法创建对象.1).使用工厂函数创建对象我们可以编写一个函数,此函数的功能就是创建对象,可将其. 概述 使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法. 然而,除了这两种常用的对象创建方式,JavaScript还提

  • C++11智能指针中的 unique_ptr实例详解

    在前面一篇文章中,我们了解了 C++11 中引入的智能指针之一 shared_ptr 和 weak_ptr ,今天,我们来介绍一下另一种智能指针 unique_ptr . 往期文章参考: [C++11新特性] C++11 智能指针之shared_ptr [C++11新特性] C++11智能指针之weak_ptr unique_ptr介绍 unique是独特的.唯一的意思,故名思议,unique_ptr可以"独占"地拥有它所指向的对象,它提供一种严格意义上的所有权. 这一点和我们前面介绍

  • C++中inline用法案例详解

    1 引入inline关键字的原因 在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数,栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间.在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭. 下面我们来看一个例子: #include <stdio.h> #include<string.h> // 函数定义为in

  • C#中的Socket编程详解

    目录 一,网络基础 二,Socket 对象 SocketType ProtocolType AddressFamily 三,Bind() 绑定与 Connect() 连接 Bind() Connect() 四,Listen() 监听请求连接 和 Accept() 接收连接请求 Listen() Accept() 五,Receive() 与 Send() Receive() 参数 返回 Send() 六,释放资源 close() 七,IPAddress 和IPEndPoint IPAddress

  • C++ STL标准库std::vector扩容时进行深复制原因详解

    目录 引子 查找原因 解决方法 结论 引子 但是笔者却发现了一个奇怪的现象,std::vector扩容时,对其中的元素竟然进行的是深复制.请看示例代码: #include <iostream> #include <vector> struct Test { Test() {std::cout << "Test" << std::endl;} ~Test() {std::cout << "~Test" <

  • Java中自动生成构造方法详解

    Java中自动生成构造方法详解 每个类在没有声明构造方法的前提下,会自动生成一个不带参数的构造方法,如果类一但声明有构造方法,就不会产生了.证明如下: 例1: class person { person(){System.out.println("父类-person");} person(int z){} } class student extends person { // student(int x ,int y){super(8);} } class Rt { public st

随机推荐