内置类型和类类型复制控制的方方面面



1、前言



复制控制指的是通过复制的手段来控制数据的成员对象,其实在我们开发C++程序的时候,很经常用到复制控制,在我们没有意识的情况下,系统使用的是默认的复制控制。由编译器生成的复制控制相关函数是非常简练的,因此,有时候使用系统为我们生成的复制控制函数是不能满足我们的需求的,所以我们有必要了解复制控制的方方面面,用于更好的控制数据对象的生成,销毁,赋值。赋值控制主要有三个方面:

①、复制构造函数:复制构造函数指的是只带有一个本类型的const引用对象的构造函数,当定义一个同类型的对象的时候,将显示调用复制构造函数。当我们调用函数的时候,传递的实参也是通过复制构造函数传递的,但是它是隐式的。这就能理解为什么不支持复制操作的对象,比如流对象是不可以使用复制传递的方式传递参数了。当函数返回结果的时候,同样也是使用复制构造函数进行的。

②、赋值操作:赋值时指将右操作数赋值给左操作数,默认情况下,系统会给我们默认定义,即使用复制构造函数给对象赋值。如果我们需要控制这个赋值过程,就需要重载赋值操作符了。

③、析构函数:即当一个对象使用完毕之后,需要执行的操作。默认情况下,编译器会自动执行所有非static对象的资源。如果我们 需要详细控制对象的释放,就需要重写构造函数。


2、复制构造函数



复制构造函数指的是只含有一个本类类型对象的const引用形参的构造函数。它的主要作用是:

①、将另一个同类型的对象显示或者隐式的复制给另外一个同类型的对象。

②、复制一个对象,将它作为一个实参传递给函数。

③、返回一个对象,将它作为返回值返回。

直接初始化使用()初始化,复制初始化使用=。比如:

string s("sss");
string s1=s;


上述方式s是直接初始化的,s1的过程则是,先调用默认构造函数创建s1,然后将s通过复制构造函数创建一个副本,复制给s1。


2.1 系统提供的复制构造函数


和默认构造函数不一样,即使我们定义了其它构造函数,系统也会为我们合成一个复制构造函数,合成的复制构造函数的行为是,将对象的数据成员的副本逐个传递给正在创建的对象的数据成员。除了static成员变量之外,其它的变量如果是内置类型,则赋值副本,如果是类类型,则使用类类型的复制构造函数进行复制。


2.2定义自己的复制构造函数


除了接受系统合成的复制构造函数之外,我们也可以自己写复制构造函数。如下:


class Person
{
public :
Person(const Person &person);
//......
}


什么情况下需要自己写复制构造函数呢?因为系统合成的复制构造函数是逐一进行赋值的,除此之外不包含任何操作,因此,如果类含有指针类型的数据成员,或者含有需要分配资源的数据成员,就需要重写复制构造函数进行特殊处理。


2.3 禁止复制

前面我们知道即使在我们没有显示重写复制构造函数的情况下,系统也会合成一个默认的构造函数。但有些时候,复制的成本过大,我们并不希望类的对象可以被复制,那么要怎么禁止呢?

我们可以声明一个复制构造函数,但是不要放在public区间,而是放在private之间,这样编译器将会拒绝任何该类的复制请求。如果我们需要为某个类开放复制功能,可以使用友元修饰符进行声明。


3 赋值操作符


和复制构造函数一样,编译器会我们合成一个赋值操作,它的行为也是逐一进行赋值。如果我们需要修改这一行为,就需要自己写赋值操作符了。赋值操作符是由operator=作为函数名,并且具有返回值和参数的。参数的数目和操作符的操作数相关。比如:


class Person
{
public :
Person& operator=(const Person &);
//.......
}

可以发现赋值操作符做的工作和复制构造函数特别相似,因此,如果一个类需要重写复制构造函数那么一般而言,他也需要重写赋值构造函数。



4 析构函数

析构函数的作用是用于释放类对象持有的资源,比如类里面持有的文件资源等等。看如下代码:

Person *p=new Person;
Person person(*p);
delete(p);

假设上述代码包含在一个代码快里面,当超出作用域的时候,系统会自动自行persond的析构函数,而p的析构函数则不会执行。析构函数自动运行有两个原则:

①、类的对象超出作用域的时候会调用析构函数

②、指针和类的引用超出作用域不会调用析构函数

即动态分配的的对象,我们必须显式释放它,比如上面如果不调用delete的话,就会造成内存泄漏,因为p永远占据着内存空间。对于容器类型和内置数组的时候,也需要delete释放内存,但是这些容器的释放顺序是从结尾到开头的,即先释放最后一个元素,再到头一个元素。



Person *p=new Person[10];
vector<Person> vect(p,p+10);
delete []p;

这样就可以删除一个数组对象。


除了系统合成的析构函数外,我们也可以自己写析构函数:

class Person
{
public :
~Person(){}
//.......
} 

和复制构造函数不一样的地方在于,无论你是否有写析构函数,系统都会执行默认合成的构造函数。比如person对象的释放,先执行你写的构造函数然后调用合成的构造函数。

什么情况下应该重写析构函数呢?当需要重写默认构造函数和赋值操作符的时候,肯定就需要重写默认构造函数,因为您需要处理资源的释放。



最后提一点,类中包含指针成员对象的时候,切记要处理好。因为复制构造函数复制的是指针的值,即指向一个对象的地址,这样两个指针会一起指向同一个对象,很容易在一个指针修改了对象的情况下,另一个指针却不知道。更严重的是,一个指针将其释放了,造成悬垂指针,另一个指针却不知道,仍在使用,会导致崩溃。


---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767-----------





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值