一、复制构造函数的定义

复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性。复制构造函数创建一个新的对象,作为另一个对象的拷贝。复制构造函数只含有一个形参,而且其形参为本类对象的引用。复制构造函数形如 X::X( X& ), 只有一个参数即对同类对象的引用,如果没有定义,那么编译器生成缺省复制构造函数。
复制构造函数的两种原型(prototypes),以类Date为例,Date的复制构造函数可以定义为如下形式:

Date(Date & ); 

或者

   Date( const Date & );   

不允许有形如 X::X( X )的构造函数,下面的形式是错误的:

 Date(Date);  // error C2652: “Date”: 非法的复制构造函数: 第一个参数不应是“Date”

作用:复制构造函数由编译器调用来完成一些基于同一类的其他对象的构件及初始化。

当我们设计一个类时,若缺省的复制构造函数和赋值操作行为不能满足我们的预期的话,我们就不得不声明和定义我们需要的这两个函数。

例如:

Example
class Namelist
{
public:
Namelist( )
{
size = ;
p = ;
}
Namelist( const string[ ], int );
//……
private:
int size;
string* p;
};
void Namelist :: set( const string& s, int i )
{
p[i] = s;
}
int main( )
{
//……
Namelist d1( list, );
Namelist d2( d1 );
d2.set( "Great Dane", );
//……
}

对象d1和d2共享了一个字符串数组,但这不是我们所希望的。 我们希望d1和d2拥有独立的字符串数组。

这里涉及到了一个深拷贝和浅拷贝的问题,可以参照这篇文章https://www.cnblogs.com/always-chang/p/6107437.html

编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针name拷贝后会出现两个指针指向同一个内存空间。

在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。

总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。

该为深拷贝后:

class Namelist
{
public:
Namelist( const Namelist& d )
{
p = ;
copyIntoP( d );
}
//……
private:
int size;
string* p;
void copyIntoP( const Namelist& );
};
void Namelist :: copyIntoP( const Namelist& d )
{
delete[ ] p;
if( d.p != )
{
p = new string[size = d.size];
for( int i = ; i < size; i++ )
p[i] = d.p[i];
}
else
{
p = ;
size = ;
}
}

如果程序员不提供一个复制构造函数,则编译器会提供一个。编译器版本的构造函数会将源对象中的每个数据成员原样拷贝给目标对像的相应数据成员。

#include <iostream>
using namespace std;
class Complex
{
public:
Complex(double r, double i)
{
real = r;
imag = i;
}
void show()
{
cout<<"real = "<<real<<" imag = "<<imag<<endl;
}
private :
double real, imag;
};
int main( )
{
Complex c1(,); //调用构造函数Complex(double r, double i)
Complex c2(c1); //调用缺省的复制构造函数,将 c2 初始化成和c1一样
c2.show();
return ;
}

二、复制构造函数的调用

复制构造函数在以下三种情况被调用:
(1)一个对象需要通过另外一个对象进行初始化,例如:

#include <iostream>
using namespace std;
class Complex
{
public:
Complex(double r, double i)
{
real = r;
imag = i;
}
Complex( Complex & c)
{
real = c.real;
imag = c.imag;
cout<<"copy constructor!"<<endl;
}
private :
double real, imag;
};
int main( )
{
Complex c1(,); //调用构造函数Complex(double r, double i)
Complex c2(c1); // 调用复制构造函数Complex( Complex & c)
Complex c3 = c1; // 调用复制构造函数Complex( Complex & c)
return ;
}

程序执行结果为:
copy constructor!
copy constructor!

(2)一个对象以值传递的方式传入函数体

函数的形参是类的对象,调用函数时,进行形参和实参的结合。
如果某函数有一个参数是类Complex的对象,那么该函数被调用时,类Complex的复制构造函数将被调用。

void func(Complex c) { };
int main( )
{
Complex c1(,);
func(c1); // Complex的复制构造函数被调用,生成形参传入函数
return ;
}

程序执行结果为:
copy constructor!

(3)一个对象以值传递的方式从函数返回
除了当对象传入函数的时候被隐式调用以外,复制构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。

Complex func()
{
Complex c1(,);
return c1; // Complex的复制构造函数被调用,函数返回时生成临时对象
};
int main( )
{
func();
return ;
}

程序执行结果为:
copy constructor!

注意:对象间用等号赋值并不导致复制构造函数被调用!C++中,当一个新对象创建时,会有初始化的操作,而赋值是用来修改一个已经存在的对象的值,此时没有任何新对象被创建。初始化出现在构造函数中,而赋值出现在operator=运算符函数中。编译器会区别这两种情况,赋值的时候调用重载的赋值运算符,初始化的时候调用复制构造函数。

#include <iostream>
using namespace std;
class CMyclass
{
public:
int n;
CMyclass( ) {};
CMyclass( CMyclass & c)
{
n = * c.n ;
}
};
void main( )
{
CMyclass c1,c2;
c1.n = ;
c2 = c1; // 对象间赋值
CMyclass c3(c1); // 调用复制构造函数
cout <<"c2.n=" << c2.n << endl;
cout <<"c3.n=" << c3.n << endl;
}

程序执行结果为:
    c2.n=5
    c3.n=10
上例中,执行c2 = c1时并没有调用复制构造函数,只是进行了下内存拷贝,因此c2.n的值为5。赋值操作是在两个已经存在的对象间进行的(c2和c1都是已经存在的对象)。而初始化是要创建一个新的对象,并且其初值来源于另一个已存在的对象。执行CMyclass c3(c1)时会调用复制构造函数,因此c3.n的值为10。

#include <iostream>
using namespace std;
class CMyclass
{
public:
CMyclass( ) {};
CMyclass( CMyclass & c)
{
cout << "copy constructor" << endl;
}
~CMyclass( )
{
cout << "destructor" << endl;
}
};
void fun(CMyclass obj_ )
{
cout << "fun" << endl;
}
CMyclass c;
CMyclass Test( )
{
cout << "test" << endl;
return c;
}
void main()
{
CMyclass c1;
fun(c1);
Test();
}

程序执行结果为:
copy constructor//传参
fun
destructor     //参数消亡
test
copy constructor//return
destructor    // 返回值临时对象消亡
destructor   // 局部变量消亡
destructor   // 全局变量消亡

三、私有的复制构造函数

在有些应用中,不允许对象间的复制操作,这可以通过将其复制构造函数声明为private,同时不为之提供定义来做到。

class A
{
public:
A ( ) {};
private:
A (A & ) {};
};
int main( )
{
A a1;
A a2(a1); // error C2248: “A::A”: 无法访问 private 成员(在“A”类中声明)
A a3 = a1; // error C2248: “A::A”: 无法访问 private 成员(在“A”类中声明)
return ;
}

如果一个类的复制构造函数是private,顶层函数(top-level functions)或是其它类中的成员 函数不能按值传递或是返回此类的对象,因为这需要调用复制构造函数。(If the copy constructor is private, top-level functions and methods in other classes cannot pass or return class objects by value precisely because this requires a call to the copy constructor.)在你不想对这个类的对象进行随意复制时,最好将复制构造函数声明为私有,同时最好也将operator=()也声明为私有(详见运算符重载)。
复制构造函数通常是在函数参数出现值传递时发生,而这种传值方式是不推荐的(推荐的是传递引用,尤其是对于类对象来说),所以可以声明一个空的私有的复制构造函数,这样当编译器试图使用复制构造函数时,就会报错,从而防止了值传递造成不可预知的后果。
例:

class A
{
public:
A ( ) {};
private:
A (A & ) {};
};
A a;
void fun1(A & obj ) { }
void fun2(A obj ) { }
A & fun3( )
{
return a;
}
A fun4()
{
return a; // error C2248: “A::A”: 无法访问 private 成员(在“A”类中声明)
}
int main( )
{
A a1, a2;
fun1(a1);
fun2(a1); // error C2248: “A::A”: 无法访问 private 成员(在“A”类中声明)
a2 = fun3( );
a2 = fun4( );
return ;
}

C++ 类 复制构造函数 The Copy Constructor的更多相关文章

  1. 构造函数语义学——Copy Constructor 篇

    构造函数语义学--Copy Constructor 篇 本文主要介绍<深度探索 C++对象模型>之<构造函数语义学>中的 Copy Constructor 构造函数的调用时机 ...

  2. 构造函数语义学——Copy Constructor 的构造操作

    前言 在三种情况下,会以一个 object 的内容作为另一个 class object 的初值: object明确初始化 class X{...}; X x; X xx = x; object 被当作 ...

  3. C++中复制构造函数被调用的三种情况

    C++中的构造函数 c++中的构造函数分为构造函数,和复制构造函数,相比于构造函数,复制构造函数使用更加方便,快捷.构造函数可以有多个,二复制构造函数只能有一个,因为复制构造函数的参数只能是当前类的一 ...

  4. Effective C++ 第0章 copy constructor和copy assignment operator

    拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个 ...

  5. copy constructor和copy assignment operator的区别

    拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个 ...

  6. 为my_string类创建复制构造函数copy constructor ,拷贝函数名和类同名

    为下面的my_string类创建一个复制构造函数,并将定义该类的代码提交. my_string类的定义: class my_string { char *s; public: my_string(ch ...

  7. 构造函数语义学之Copy Constructor构建操作(2)

    二.详述条件 3 和 4 那么好,我又要问大家了,条件1 和 2比较容易理解.因为member object或 base class 含有copy constructor.那么member objec ...

  8. [C++]复制构造函数、赋值操作符与隐式类类型转换

    问题:现有类A定义如下: class A{public:        A(int a)                            //构造函数        {              ...

  9. 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作

    C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中.决定一个copy constructor是 ...

随机推荐

  1. 初识Qt鼠标、键盘事件及定时器和随机数

    1.新建Qt Gui应用,项目名称为“myEvent”,基类名称为QWidget,类名称为Widget. 2.widget.h文件中添加以下代码,该段代码中包含了三个事件函数和一个槽函数 privat ...

  2. sencha 2.3中自己定义PullRefreshFn给PullRefresh加入下拉刷新事件

    Sencha removed the refreshFn from the pullrefresh plugin in ST 2.2. Here is an user extension with g ...

  3. 针对于网络安全领域中基于PCAP流量的数据集

    网络安全领域中基于PCAP流量的数据集 MAWI Working Group Traffic Archive URL:http://mawi.wide.ad.jp/mawi/ CIC dataset ...

  4. Datax3.0使用说明

    一.datax3.0介绍 1.DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDFS.Hive.ODPS.HBase.FTP等各种异构数据源之间稳 ...

  5. react router animation example

    https://github.com/reactjs/react-router/tree/80c71d57c936ed54babdde44309c01f6a4b56b77/examples/anima ...

  6. 20155222卢梓杰 课堂测试ch06补做

    20155222卢梓杰 课堂测试ch06补做 1.下面代码中,对数组x填充后,采用直接映射高速缓存,所有对x和y引用的命中率为() A . 1 B . 1/4 C . 1/2 D . 3/4 正确答案 ...

  7. 标准输入输出 sys.stdin与sys.stdin

    1.python中的标准输入输出 如果需要更好的控制输出,而print不能满足需求,input也不能 sys.stdout,sys.stdin,sys.stderr就是你需要的. 2.输入:sys.s ...

  8. 排序算法:快速排序解析及Python实现

    关键词:分而治之.递归.计算速度.基准值 1. 什么是分而治之? 1.1 分而治之(divide and conquer)一种递归式方法 1.2 找出基线条件,这种条件必须尽可能简单 1.3 不断将问 ...

  9. P3354 [IOI2005]Riv 河流

    树形dp,设f[i][j][k]表示第i个点的子树中选择j个点作为伐木场,而且k是建了伐木场的最浅的i的祖先的情况下,最小的收益. 这种题还要练一下,咕咕 然后转移可以n4方做. // luogu-j ...

  10. UWP 检测网络状态

    最近发现Community Toolkit有了网络辅助类,貌似很早就有了... 很不错,还是用.给大家分享一下. 1. 检测网络是否可用 2. 检测网络是否是计费模式? 3. 检测网络接入类型 4. ...