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)根据另一个同类 ...
随机推荐
- [OS] 进程间通信--管道
管道是单向的.先进先出的.无结构的.固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起.写进程在管道的尾端写入数据,读进程在管道的首端读出数据.数据读出后将从管道中移走,其它读进 ...
- 【hdu4734】F(x) 数位dp
题目描述 对于一个非负整数 $x=\overline{a_na_{n-1}...a_2a_1}$ ,设 $F(x)=a_n·2^{n-1}+a_{n-1}·2^{n-2}+...+a_2·2^1+ ...
- 【bzoj4196】[Noi2015]软件包管理器 树链剖分+线段树
题目描述 Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个 ...
- robot framework连接Oracle错误:ORA-12504: TNS:listener was not given the SERVICE_NAME in CONNECT_DATA
在使用robot framework的关键字Connect to Database Using Custom params连接Oracle数据库: Connect to Database Using ...
- 锁-lock,信号量4
1. 全局解释器锁,保证同一时间只有一个线程在执行,但是由于它是把数据copy成了两份,所以 只有全局解释器锁的时候,数据加减照样出错了. 2.用户态的锁,保证同一时间,只有一个线程在真真正正地修改数 ...
- 【题解】CF#24 D-Broken Robots
在某次考试的时候用过的办法,懒人必备……[笑哭] 一个非常显然的 dp,我们用 \(f[i][j]\) 表示第 \(i\) 行第 \(j\) 列的格子走到最后一排的期望步数转移即为 \(f[i][j] ...
- POJ2945:Find the Clones——题解
http://poj.org/problem?id=2945 还是trie树……对于结束标记累加并且开个数组记录一下即可. #include<cstdio> #include<cst ...
- 51NOD 1565:模糊搜索——题解
http://www.51nod.com/onlineJudge/questionCode.html#problemId=1565¬iceId=445588 有两个基因串S和T,他们只包 ...
- 洛谷 P1363 幻想迷宫 解题报告
P1363 幻想迷宫 题目描述 背景 Background (喵星人LHX和WD同心协力击退了汪星人的入侵,不幸的是,汪星人撤退之前给它们制造了一片幻象迷宫.) WD:呜呜,肿么办啊-- LHX:mo ...
- LOJ 模拟赛
1.LOJ 507 接竹竿 link dp[i]表示前i个的最大分数,所以dp[i]=max(dp[i-1],dp[j-1]+sum[i]-sum[j-1]) (color i ==color j ...