您的位置:首页 > 编程语言 > C语言/C++

c++_7: 拷贝构造函数和赋值构造函数

2015-12-13 22:57 507 查看
参考: /article/1415258.html

拷贝构造函数

一般变量的赋值

int a = 100;
int b = a;


拷贝构造函数的过程

#include <iostream>
using namespace std;

class CExample {
private:
int a;
public:
//构造函数
CExample(int b)
{ a = b;}

//拷贝构造函数
CExample(const CExample& C)
{
a = C.a;
}

//一般函数
void Show ()
{
cout<<a<<endl;
}
};

int main()
{
CExample A(100);
CExample B = A; // CExample B(A); 也是一样的
B.Show ();
return 0;
}


CExample(const CExample& C) 就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。

拷贝构造函数的调用时机

在C++中,下面三种对象需要调用拷贝构造函数!

对象以值传递的方式传入函数参数

class CExample
{
private:
int a;

public:
//构造函数
CExample(int b)
{
a = b;
cout<<"creat: "<<a<<endl;
}

//拷贝构造
CExample(const CExample& C)
{
a = C.a;
cout<<"copy"<<endl;
}

//析构函数
~CExample()
{
cout<< "delete: "<<a<<endl;
}

void Show ()
{
cout<<a<<endl;
}
};

//全局函数,传入的是对象
void g_Fun(CExample C)
{
cout<<"test"<<endl;
}

int main()
{
CExample test(1);
//传入对象
g_Fun(test);

return 0;
}


(1).test对象传入形参时,会先会产生一个临时变量,就叫 C 吧。

(2).然后调用拷贝构造函数把test的值给C。 整个这两个步骤有点像:CExample C(test);

(3).等g_Fun()执行完后, 析构掉 C 对象。

对象以值传递的方式从函数返回

class CExample
{
private:
int a;

public:
//构造函数
CExample(int b)
{
a = b;
}

//拷贝构造
CExample(const CExample& C)
{
a = C.a;
cout<<"copy"<<endl;
}

void Show ()
{
cout<<a<<endl;
}
};

//全局函数
CExample g_Fun()
{
CExample temp(0);
return temp;
}

int main()
{
g_Fun();
return 0;
}


(1). 先会产生一个临时变量,就叫XXXX吧。

(2). 然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像:CExample XXXX(temp);

(3). 在函数执行到最后先析构temp局部变量。

(4). 等g_Fun()执行完后再析构掉XXXX对象。

对象需要通过另外一个对象进行初始化;

CExample A(100);
CExample B = A;
// CExample B(A);


默认拷贝构造函数

“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值

Rect::Rect(const Rect& r)
{
width = r.width;
height = r.height;
}


默认拷贝构造函数的问题

class Rect
{
public:
Rect()      // 构造函数,计数器加1
{
count++;
}
~Rect()     // 析构函数,计数器减1
{
count--;
}
static int getCount()       // 返回计数器的值
{
return count;
}
private:
int width;
int height;
static int count;       // 一静态成员做为计数器
};

int Rect::count = 0;        // 初始化计数器

int main()
{
Rect rect1;
cout<<"The count of Rect: "<<Rect::getCount()<<endl;

Rect rect2(rect1);   // 使用rect1复制rect2,此时应该有两个对象
cout<<"The count of Rect: "<<Rect::getCount()<<endl;

return 0;
}


复制对象时,计数器没有递增,

//改进后的代码
class Rect
{
public:
Rect()      // 构造函数,计数器加1
{
count++;
}
Rect(const Rect& r)   // 拷贝构造函数
{
width = r.width;
height = r.height;
count++;          // 计数器加1
}
~Rect()     // 析构函数,计数器减1
{
count--;
}
static int getCount()   // 返回计数器的值
{
return count;
}
private:
int width;
int height;
static int count;       // 一静态成员做为计数器
};


浅拷贝

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。若开辟了新的内存空间(new),则需要深拷贝。

class Rect
{
public:
Rect()      // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
~Rect()     // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p;     // 一指针成员
};

int main()
{
Rect rect1;
Rect rect2(rect1);   // 复制对象
return 0;
}


在运行定义rect1对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:



在使用rect1复制rect2时,由于执行的是浅拷贝,只是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,如下图所示:



当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”

深拷贝

class Rect
{
public:
Rect()      // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
Rect(const Rect& r)
{
width = r.width;
height = r.height;
p = new int;    // 为新对象重新动态分配空间
*p = *(r.p);
}
~Rect()     // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p;     // 一指针成员
};




此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

赋值构造函数

/article/5144042.html

赋值构造函数是将一个参数对象中私有成员赋给一个已经在内存中占据内存的对象的私有成员,赋值构造函数被赋值的对象必须已经在内存中,否则调用的将是拷贝构造函数,当然赋值构造函数也有深拷贝和浅拷贝的问题。

#include <iostream>
#include <string.h>
using namespace std;

class A
{
private:
int* n;

public:
A()
{
n = new int[10];
n[0] = 1;
cout<<"constructor is called\n";
}

A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归
{
n = new int[10];
memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存
cout<<"copy constructor is called\n";
}

A& operator=(const A& a)  //记住形参和返回值一定要是引用类型,否则传参和返回时会自动调用拷贝构造函数
{
if(this == &a)     //为什么需要进行自我赋值判断呢?因为下面要进行释放n的操作,如果是自我赋值,而没有进行判断的话,那么就会出现讲一个释放了的内存赋给一个指针
return *this;
if(n != NULL)
{
delete n;
n == NULL;   //记住释放完内存将指针赋为NULL
}

n = new int[10];
memcpy(n, a.n, 10);
cout<<"assign constructor is called\n";
return *this;
}

~A()
{
cout<<"destructor is called\n";
delete n;
     n = NULL;  //记住释放完内存将指针赋为NULL
}

void get()
{
cout<<"n[0]: "<<n[0]<<endl;
}
};

int main()
{
A* a = new A();
A* b =  new A();

*b = *a;

delete a;

b->get();
return 0;
}


虚析构函数

基类指针删除派生类的对象

/article/1360374.html

class ClxBase
{
public:
ClxBase() {};
virtual ~ClxBase() {};

virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

class ClxDerived : public ClxBase
{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };

void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};


ClxBase *pTest = new ClxDerived;
pTest->DoSomething();
delete pTest;


结果

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!


如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:

Do something in class ClxDerived!


类ClxDerived的析构函数根本没有被调用

结论

为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: