C++primer 第十三章 复制控制

一、复制构造函数

1、定义:

具有单个形参,形参是对该类类型的引用,通常形参是const引用。

例子:

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;
    B.Show ();
    return 0;
}
 

2、应用场景

当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用复制构造函数:

  • 一个对象以值传递的方式传入函数体
  • 一个对象以值传递的方式从函数返回
  • 一个对象需要通过另外一个对象进行初始化。
  • 初始化顺序容器中的元素。    
  • 根据元素初始化列表初始化数组元素。 

3、vector<int> v1(42);是正确的,vector<int> v2 = 42;  是不正确的。从这个可以得出什么推断?

vector<int> v1(42);这里的42不是用来构造临时对象的,是42个0,临时对象是0构造的。

vector<int> v2 = 42;它分两步,先调用vector类的构造函数,将42隐式转化为vector对象,然后调用复制构造函数,问题就是第一步这个构造函数是声明为explicit的,从而禁止了从int到vector的隐式转换,错误就出在这第一步了。

赋值操作符=的通常定义是将右操作数的副本保存到左操作数,返回左操作数的引用,在复制右操作数到左操作数时,是调用复制构造函数实现的。

参考资料:http://bbs.csdn.net/topics/380188830

4、必须定义复制构造函数

当类中的数据成员需要动态分配存储空间时,不可以依赖默认的复制构造函数。在需要时(包括这种对象要赋值、这种对象作为函数参数要传递、函数返回值为这种对象等情况),要考虑到自定义复制构造函数。另外,复制构造函数一经定义,赋值运算也按新定义的复制构造函数执行。

a、类中有一个数据成员是指针或者数据成员表示在构造函数中分配的其他资源

b、类在创建新对象时必须要做一些特定的工作

例如

//例程2
class Douary
{
public:
 Douary(int m, int n);//构造函数:用于建立动态数组存放m行n列的二维数组(矩阵)元素,并将该数组元素初始化为0
 ~Douary(); //析构函数:用于释放动态数组所占用的存储空间
 int *Array;       //Array 为动态数组指针。
 int row;          //row  为二维数组的行数。
 int col;          //col   为二维数组的列数。
};

这种情况下没有定义复制构造函数,如果定义Douary c1,c2;不能令c2=c1;因为c1有动态分配的资源(指针)。然而若定义复制构造函数如下,则上述复制则成立。

Douary::Douary(const Douary &d)
{
 row=d.row;
 col=d.col;
 Array = new int[row*col];
 for(int i=0; i<row; ++i)
  for(int j=0; j<col; ++j)
   Array[i*col+j]=d.Array[i*col+j];
}

参考文献:http://blog.csdn.net/sxhelijian/article/details/7466483

5、禁止复制

为了禁止复制,可以仅声明不定义复制构造函数,并且将作用域设为private

6、初始化形式有两种:

a、复制初始化:用等号形式,例如string null="99";是首先将c风格字符串99转换成一个临时string类型的变量,然后赋值给左值。因此对于像IO类型的对象,因为不能进行复制,所以不能用作左值。

b、直接初始化:用()形式

vector<string> svec(5);同时应用了默认构造函数和复制构造函数,首先调用string默认构造函数创建临时变量,然后将其复制到容器元素中

二、赋值操作符

如果需要复制构造函数,几乎肯定需要赋值操作符。

由于对象内包含指针,将造成不良后果:为了避免内存泄露,指针成员将释放指针所指向的空间,以便接受新的指针值,这正是由赋值运算符的特征所决定的。但如果是"x=x"即自己给自己赋值,会出现什么情况呢?x将释放分配给自己的内存,然后,从赋值运算符右边指向的内存中复制值时,发现值不见了。这句话可能有问题。

赋值运算符(operator=)复制构造函数:A(A& a){}都是用已存在A的对象来创建另一个对象B。不同之处在于:赋值运算符处理两个已有对象,即赋值前B应该是存在的;复制构造函数是生成一个全新的对象,即调用复制构造函数之前B不存在。 虽然有时运行通过,但还是有潜在危险。  
exp:
       CTemp B(A);       //复制构造函数,C++风格的初始化
       CTemp B=A;       //仍然是复制构造函数,不过这种风格只是为了与C兼容,与上面的效果一样
       在这之前B不存在,或者说还未构造好。

       CTemp B;
       B = A;                //赋值运算符
       在这之前B已经通过默认构造函数构造完成。

三、析构函数

1、定义:

当对象超出作用域或者动态分配的对象被删除时,将自动应用析构函数。不管是否定义了自己的析构函数,编译器都自动执行非static数据成员的析构函数。

 2、三法则:

如果需要析构函数,则会同时需要复制构造函数、赋值操作符

3、用途

析构函数函数并不仅限用来释放资源,一般而言,析构函数可以执行任意操作。

4、合成析构函数

合成析构函数是编译器自动合成的,按照创建对象时的逆序撤销每个非static成员

5、如何编写

析构函数没有返回值、没有形参,因此不能重载;

与复制构造函数和赋值操作符的区别是,即使我们自己定义了析构函数,合成析构函数仍然运行;因此只需要释放动态分配参数即可。

四、处理指针成员

A、智能指针

1、定义:

class U_ptr

{

private:

friend class H;

int *ip;

size_t use;

U_ptr(int *p):ip(p),use(1){}

~U_ptr(){delete ip;}

};

2、H的成员函数,在复制构造和赋值符号操作时,要将右值得智能指针计数器加一,赋值符号操作时,左值减一。

3、作用

由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,用智能指针便可以有效缓解这类问题。

4、类型及用法

包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost:: intrusive_ptr

参考文献:http://mxdxm.iteye.com/blog/763601

B、定义值类型

 赋值构造函数将不再复制指针,而是分配一个新的int对象,并初始化该对象以保存与复制对象相同的值,每个对象都保存属于自己的int值得不同副本,因为每个对象保存自己的副本,所以析构函数将无条件删除指针。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值