一、复制构造函数的定义

复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性。复制构造函数创建一个新的对象,作为另一个对象的拷贝。复制构造函数只含有一个形参,而且其形参为本类对象的引用。复制构造函数形如 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. List集合的子类ArrayList和LinkedList

    一: 我们常用对集合的操作,查询.增删等操作. 由于集合的存储的方式的不同,导致有些集合查询快但是增删慢.有些集合增删快.但是查询慢. ArrayList:由于ArrayList存储的方式为数组形式. ...

  2. 解决:在pom.xml处理添加testng依赖之外,需对testng进行关联

    问题描述:当maven项目中下载了testng包,在调用后,执行maven test,未执行testng.xml中指定的测试类. 解决:在pom.xml处理添加testng依赖之外,需对testng进 ...

  3. zdump 命令查看时区和夏令时

    zdump -v 时区名称1. 查看中国PRC时区的2007年的时区规则# zdump -v PRC | grep 2009PRC Mon Sep 17 06:03:55 2007 UTC = Mon ...

  4. 【Dubbo源码阅读系列】之 Dubbo XML 配置加载

    今天我们来谈谈 Dubbo XML 配置相关内容.关于这部分内容我打算分为以下几个部分进行介绍: Dubbo XML Spring 自定义 XML 标签解析 Dubbo 自定义 XML 标签解析 Du ...

  5. php如何实现统计一个数字在排序数组中出现的次数(代码)

    统计一个数字在排序数组中出现的次数. 博客 www.51msk.cn 1.有序的数组查找,使用二分法2.二分法查找第一次出现的位置,二分法查找最后一次出现的位置,end - start +1 left ...

  6. Fiddler设置代理后,iphone手机无法访问网络的全面解决办法

    好久不抓手机包,都有些忘记了,电脑上软件都在,原本是Fiddler4,现在已自动升级到V5. 入主题吧,首先Fiddler设置好代理,然后手机安装证书好了,设置手机代理,结果iphone无法访问网络, ...

  7. 3832: [Poi2014]Rally

    3832: [Poi2014]Rally 链接 分析: 首先可以考虑删除掉一个点后,计算最长路. 设$f[i]$表示从起点到i的最长路,$g[i]$表示从i出发到终点的最长路.那么经过一条边的最长路就 ...

  8. SpringCloud-微服务网关ZUUL(六)

    前言:前面说过,由于微服务过多,可能某一个小业务就需要调各种微服务的接口,不可避免的就会需要负载均衡和反向代理了,以确保ui不直接与所有的微服务接口接触,所以我们需要使用一个组件来做分发,跨域等各种请 ...

  9. 使用SDNN (space displacement neural network)进行多字体手写识别

    手写单字体的识别,在看过卷积神经网络的mnist例子之后,很容易实现,那么如何实现多字体的同时识别呢? 如下图 LeCun大神所用的是SDNN space displacement neural ne ...

  10. Codeforces 937 D. Sleepy Game(DFS 判断环)

    题目链接: Sleepy Game 题意: Petya and Vasya 在玩移动旗子的游戏, 谁不能移动就输了. Vasya在订移动计划的时候睡着了, 然后Petya 就想趁着Vasya睡着的时候 ...