名词解释:
位拷贝:位拷贝拷贝的是地址
值拷贝:拷贝的是内容
使用位拷贝在一些不涉及指针变化的简单操作中可以满足我们的需求,因为他们只需要简单的将成员变量的值简单的复制,这种我们也称之为:浅拷贝。假设我们现在需要用在新开辟一个空间,那么就不能简单的用浅拷贝来得到我们需要的结果,这种需求我们称之为:深拷贝。这样我们就需要自己写拷贝构造函数来实现深拷贝了。
浅拷贝
类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。当我们对一个类对象进行简单的类赋值的时候其实编译器执行的是类的拷贝构造函数。
我们可以通过下面的例子来看:
// kaobeigouzaofunc.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <string>
#include <iostream>
using namespace std;
class CExample {
public:
//构造函数
CExample(string strVal)
{
m_stra = strVal;
}
void SetVal(string strVal)
{
m_stra = strVal;
}
//一般函数
void Show ()
{
cout<<m_stra<<endl;
}
private:
string m_stra;
};
int _tmain(int argc, _TCHAR* argv[])
{
CExample A("sssssssssss");
A.Show();
A.SetVal("bbbbbbb");
CExample B = A; /// (1)
B.Show ();
getchar();
return 0;
}
我们可以通过设置断点查看汇编信息 在(1)设置断点。
通过汇编我们可以看到:在赋值的时候系统默认调用00411C00位置的函数指针,这个即为编译器为我们默认构造的拷贝构造函数,当然他只进行简单的位拷贝。
输出:可以看出上面B实例的strV也自动的拷贝了A实例当前的值 。
深度拷贝
如果你认为这样的拷贝就可以的话安枕无忧的话,那你就错鸟,下面我们通过一个例子来了解下深度拷贝的必要性了以及如何实现深度拷贝。
class CDepthExp
{
public:
CDepthExp()
{
m_pV=NULL;
m_ilen=0;
}
~CDepthExp()
{
if (m_pV )
{
delete m_pV;
m_pV=NULL;
}
}
void ShowVal()
{
if (m_pV)
{
cout << m_pV <<endl;
}
else
{
cout <<"m_pv is NULL" << endl;
}
}
void SetVal(const char *strVal,int ilen)
{
if (m_pV && m_ilen < ilen)
{
delete m_pV;
m_pV=NULL;
}
if (m_pV ==NULL)
{
m_pV =new char[ilen+1];
m_ilen=ilen+1;
}
strcpy(m_pV,strVal);
}
private:
char *m_pV;
int m_ilen;
};
int _tmain(int argc, _TCHAR* argv[])
{
CDepthExp A;
A.SetVal("123456",60);
A.ShowVal();
CDepthExp B = A;
B.ShowVal();
B.SetVal("abcdef",6);
A.ShowVal();
getchar();
return 0;
}
输出:
我们改变了实例B的值显示a的时候A也跟着变了,这就是位拷贝的特点了,拷贝地址,这里还将导致一个致命性的错误,当析构的时候由于m_pV已经在B中被释放了,但是又在A中Delete了一次,程序将出现错误!!
这里编译器为我们构建的拷贝构造函数如下:
CDepthExp(const CDepthExp& C)
{
m_pV = C.m_pV;
m_ilen= C.m_ilen;
}
为此我们可以自己构造自己的拷贝构造函数:
CDepthExp(const CDepthExp& C) :m_pV(NULL)
{
m_ilen =C.m_ilen;
m_pV =new char[m_ilen];
strcpy(m_pV,C.m_pV);
}
输出:
可以看出现在A的值并不会因B实例的改变而改变了。
拷贝构造函数的一些特点技巧:
对于一个类X, 如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.类中可以存在超过一个拷贝构造函数。
如果我们不希望一个类被拷贝构造那么我们可以通过如下的形式来阻止类的赋值,我们只需要在私有成员里面添加成员函数:
private:
CDepthExp(constCDepthExp& C) ;
声明但不实现,那么如果你对实例赋值的话 编译器将会报错来阻止你这种行为~。
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。 1). 一个对象以值传递的方式传入函数体(所以值传递很耗效率的哦) 2). 一个对象以值传递的方式从函数返回 3). 一个对象需要通过另外一个对象进行初始化
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会自动生成一个来进行对象之间非static成员的位拷贝(Bitwise Copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数,除非另外一个构造函数在类初始化或构造列表的时候被调用。
拷贝构造函数使程序更有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统会自动为你生成默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。