C++之复制控制
只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数叫做复制构造函数(有时也称为拷贝构造函数),例如:
class Person{
public:
Person();//默认构造函数
Person(const Person&);//复制构造函数
....................
};
与默认构造函数一样,复制构造函数可由编译器隐式调用,它主要用于以下几种情况:
(1)根据另一个同类型的对象显示或者隐式初始化一个对象
(2)复制一个对象,将它作为实参传给一个函数
(3)从函数返回时复制一个对象
(4)初始化顺序容器中的对象
(5)根据元素初始化式列表初始化数组元素
C++支持两种初始化形式:直接初始化和复制初始化,复制初始化使用=符号,而直接初始化将初始化式放在圆括号中,直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时的对象,然后用复制构造函数将那个临时的对象复制到正在创建的对象。例如:
string book="9-999-99999-100";
上式创建book时,编译器首先调用接受一个C风格字符串形参的string构造函数,创建一个临时的对象,然后编译器使用string复制构造函数将book初始化为那个临时对象的副本。
当形参为非引用类型的时候,将复制实参的值,类似的,以非引用类型作返回值时,将返回return语句中的值的副本,因此,当形参或者返回值为类类型时,将由复制构造函数进行复制;复制构造函数可以用于初始化顺序容器中的元素,例如:
vector<string> svec(5);
容器的这种构造方式使用了默认构造函数和复制构造函数。编译器首先使用string默认构造函数创建一个临时值svec,然后使用复制构造函数将临时值复制到svec的每个元素;如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素,不过,如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化开初始化每个元素。编译器根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:
string book[]={ string("0-201-201"),
string("0-201-202"),
string("0-201-203")
};
如果我们没有定义复制构造函数,编译器就会为我们合成一个,不过,与合成的默认构造函数不同,即使我们定义了其他构造函数,也会合成复制构造函数。合成复制构造函数将执行逐个成员初始化,完成对象之间的位拷贝(位拷贝又称浅拷贝),将新对象初始化为原对象的副本。所谓的“逐个成员”在,指的是编译器将现有对象的每个非static成员,依次复制到正在创建的对象。合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员的复制是个例外,虽然一般不能复制数组,但如果一个类具有数组成员,则合成复制构造函数将复制数组,而且合成复制构造函数将复制数组的没一个元素。
对于许多类来讲,合成复制构造函数只完成必要的工作,只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显示定义复制构造函数,也可以复制,这种情况叫做“浅拷贝”。不过,有些类必须对复制对象时发生的事情加以控制,这样的类经常有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源,而也有一些类在创建新对象时必须做一些特定工作,这些情况下,都必须定义复制构造函数,这种情况下叫“深拷贝”。
对于普通类型的对象来说,它们之间的复制是很简单的,例如:
int a=100;
int b=a;
而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。下面看一个类对象拷贝的简单例子。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int b)
{
a=b;
}
void Show ()
{
cout<<a<<endl;
}
private:
int a;
};
int main()
{
Test A(100);
Test B=A;
B.Show ();
return 0;
}
运行程序,屏幕输出100。从以上代码的运行结果可以看出,系统为对象B分配了内存并完成了与对象A的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
浅拷贝和深拷贝
在某些状况下,类内成员变量需要动态开辟堆内存,假如实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,假如B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:假如一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。下面举个深拷贝的例子。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
Test(const Test& C)
{
a=C.a;
str=new char[a]; //深拷贝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~Test()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
Test A(10,"Hello!");
Test B=A;
B.Show();
return 0;
}
因此,复制构造函数也叫做拷贝构造函数,它分为浅拷贝,一般情况下,浅拷贝就合成复制构造函数可以完成,另外一种是深拷贝,也就是需要我们显示定义复制构造函数的一种情况了。
如果有些类需要完全禁止复制,那么只需将复制构造函数声明为私有成员函数即可,否则如果不声明定义,编译器将会默认合成一个复制构造函数,照样可以完成浅拷贝的复制构造过程,如:
#include <iostream>
using namespace std;
class Obj
{
public:
Obj()
{
cout << "默认构造函数" << endl;
}
private:
Obj(const Obj &obj)
{
cout << "复制构造函数" << endl;
}
};
int main()
{
Obj obj1;
Obj obj2(obj1);
return 0;
}
编译报错:
error: 'CObj::CObj(const CObj&)' is private
这就是禁止复制的效果,正是这里想要的。
如果想要连友元函数和成员函数的复制也禁止,那么可以声明一个私有的复制构造函数但不对其定义。因为友元函数或成员函数可以访问到类的私有成员,所以当然能调用私有的复制构造函数,所有将复制构造函数声明为私有但不定义,就能避免友元或成员函数的调用。声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致链接失败。用户代码中的复制尝试将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。
#include <iostream>
using namespace std;
class Obj
{
public:
Obj()
{
cout << "默认构造函数" << endl;
}
friend void fri_copy();
private:
Obj(const Obj &obj);
};
void fri_copy()()
{
CObj obj1;
CObj obj2(obj1);
}
int main()
{
fri_copy();
return 0;
}
编译报错:
undefined reference to `CObj::CObj(CObj const&)'
这也正是这里需要的结果,禁止复制成功。
C++之复制控制的更多相关文章
- C++ 复制控制之复制构造函数
7月26日更新: 过了这么长的时间回过头来看,发现文章中有几个点说错(用红字标出): 构造函数不是只有唯一一个参数,它也可以是多参数形式,其第二参数及后继以一个默认值供应. 不是没有声明复制控制函数时 ...
- C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承
面向对象编程 --转换与继承.复制控制与继承 I.转换与继承 引言: 由于每一个派生类对象都包括一个基类部分,因此能够像使用基类对象一样在派生类对象上执行操作. 对于指针/引用,能够将派生类对象的指针 ...
- C++ Primer 随笔 Chapter 13 复制控制
1.复制控制包含的内容:复制构造函数.赋值操作符.析构函数 2.复制构造函数: a. 定义:只有单个形参,而且该形参是对本类类型的引用,这样的构造函数被成为复制构造函数 b. 适用情况: (1)根据一 ...
- C++Primer笔记之复制控制
复制控制这一节需要注意的地方不多,主要有以下几点: 1.定义自己的复制构造函数 什么时候需要定义自己的复制构造函数,而不用系统提供的,主要遵循以下的经验说明: 某些类必须对复制对象时发生的事情加以控制 ...
- 稍微深入点理解C++复制控制【转】
通过一个实例稍微深入理解C++复制控制过程,参考资料<C++ primer>,介绍点基本知识: 1.在C++中类通过特殊的成员函数:复制构造函数.赋值操作符和析构函数来控制复制.赋值和撤销 ...
- C++拾遗(六)——复制控制
年前忙了几天,到现在才算是有空休息下来.先祝大家新年快乐,心想事成:)我也会发笑脸o.o 这篇博文主要介绍定义一个类型的对象时的复制控制方式,这部分内容之前有一定的了解但又浅尝辄止,始终感觉没能找到要 ...
- C++继承与构造函数、复制控制
每个派生类对象由派生类中定义的(非static)成员加上一个或多个基类子对象构成,因此,当构造.复制.赋值和撤销派生类型对象时,也会构造.复制.赋值和撤销这些基类子对象. 构造函数和复制控制成员不能继 ...
- C++复制控制
1.复制构造函数可用于: (1)根据另一个同类型的对象显示或隐式初始化一个对象 string str1="test"; //隐式 string str2=str1; //显示 ...
- C++复制控制:拷贝构造函数
一.拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用.与默认构造函数一样 ,拷贝构造函数可由编译器隐式调用.拷贝构造函数应用的场合为: (1)根据另一个同类 ...
随机推荐
- Spring MVC之@RequestBody@ResponseBody详解
引言: 接上一篇文章讲述处理@RequestMapping的方法参数绑定之后,详细介绍下@RequestBody.@ResponseBody的具体用法和使用时机: 简介: @RequestBody 作 ...
- Winform程序部署方式总结一——ClickOnce发布
针对Winform程序,介绍两种常用打包方式:ClickOnce和Windows Installer 应用程序如下: 一.ClickOnce发布 1.找到需要发布的项目文件,右击,从弹出的快捷菜单中找 ...
- 【ADO.NET】ADO.NET知识点
ADO.NET 是一组向 .NET 程序员公开数据访问服务的类.提供了对各种关系数据.XML 和应用程序数据的访问. 所有的数据访问类位于System.Data.dll中.System.Data包含了 ...
- 详解利用ELK搭建Docker容器化应用日志中心
概述 应用一旦容器化以后,需要考虑的就是如何采集位于Docker容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 收集.本文即将阐述如何利用ELK日志中心来收集容器化应用 ...
- BZOJ1911:[Apio2010]特别行动队——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1911 又是一个显然的dp……好吧我懒得讲了. s[i]是战斗力前缀和. 我们仍然设k<j< ...
- BZOJ2038:[2009国家集训队]小Z的袜子——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=2038 Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找 ...
- BZOJ2434 [Noi2011]阿狸的打字机 【AC自动机 + fail树 + 树状数组】
2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MB Submit: 3610 Solved: 1960 [Submit][S ...
- 洛谷 P3620 [APIO/CTSC 2007]数据备份 解题报告
P3620 [APIO/CTSC 2007]数据备份 题目描述 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同 ...
- URAL.1033 Labyrinth (DFS)
URAL.1033 Labyrinth (DFS) 题意分析 WA了好几发,其实是个简单地DFS.意外发现这个俄国OJ,然后发现ACRUSH把这个OJ刷穿了. 代码总览 #include <io ...
- Install JDK In Ubuntu
安装Linux软件包管理器rpm apt install rpm 查看已安装的软件,如JDK rpm -qa|grep jdk #查询所有 找jdk 卸载已安装的软件 rpm -e nodeps 包名 ...