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)根据另一个同类 ...
随机推荐
- Android基础------SQLite数据库(二)
1.操作SQLite数据库 1.1 execSQL() 可以执行insert.delete.update和CREATE TABLE之类有更改行为的SQL语句 1.2 rawQuery() 可以执行se ...
- 解决编写 xml 没有代码提示
有时候在编写 struts.xml 会没有代码提示,一般是因为没有联网导致的,或者之前配置过 dtd 文件 url,但是文件路径之后被修改了. 解决方案有: 让电脑联网 修改 dtd 的本地路径以及 ...
- Go语言【第十篇】:Go数据结构之:指针
Go语言指针 Go语言中指针是很容易学习的,Go语言中使用指针可以更简单的执行一些任务.我们都知道变量是一种使用方便的占位符,用于引用计算机内存地址.Go语言的取地址符是 &,放到一个变量前使 ...
- 推荐算法相关总结表(包括DM)
推荐算法总结表 表1 推荐算法分类 个性化推荐算法分类 启发式算法 基于模型 基于内容 TF-IDF 聚类 最大熵 相似度度量 贝叶斯分类 决策树 神经网络 专家系统 知识推理 协同过滤 K近邻 聚类 ...
- 【题解】Atcoder ARC#94 F-Normalization
再次膜拜此强题!神级性质之不可能发现系列收藏++:首先,对于长度<=3的情况,我们采取爆搜答案(代码当中是打表).对于长度>=4的情况,则有如下几条玄妙的性质: 首先我们将 a, b, c ...
- BZOJ1047:[HAOI2007]理想的正方形——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1047 https://www.luogu.org/problemnew/show/P2216#sub ...
- SQL_MODE
一 声明 标红部分为重点了解 原文:https://segmentfault.com/a/1190000005936172 二 SQL_MODE参数值 官方手册专门有一节介绍 https://dev. ...
- Apache 403 错误解决方法-让别人可以访问你的服务器(转)
有一次做好了一个效果放在自己电脑的服务器上,让同学查看(同处于校园网中),却不知apache一直显示403 错误,对方没有权限访问,我知道这应该是配置文件httpd.conf中的问题,网上搜了一下其他 ...
- 在Linux系统的服务器上使用Memtester进行内存压力测试
最近要测试一台机器的整体性能情况,就在google搜索一番,发现这个一个小工具,说是可以进行内存的压力测试,Memtester主要是捕获内存错误和一直处于很高或者很低的坏位, 其测试的主要项目有随机值 ...
- 获取文件名称 basename 用法
<?phpheader('Content-type:text/html;charset=utf8'); $str = '2390230.png';$str_arr = explode('.',$ ...