拷贝构造函数

2019-11-13  本文已影响0人  带带吴腾跃

https://www.jianshu.com/p/fe1059ab07d2

此文章仅用作自身复习使用
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中的之前已有的对象来初始化新的对象。
以下几种情况会调用拷贝构造函数:

如果类中没有定义拷贝构造函数,系统会默认生成一个。如果带有指针变量,并有动态内存分配,则它必须要有一个拷贝构造函数。

拷贝构造函数常见形式如下:

classname(const classname & obj)
{
    函数内容;
}

实例:

#include <iostream>
using namespace std;

class Line
{
private:
    int *ptr;
public:
    int getLength();
    Line(int len); // 普通构造函数    
    Line(const Line &obj);// 拷贝构造函数
    ~Line();
};

// 构造函数定义
Line::Line(int len)
{
    cout << "调用构造函数." << endl;
    ptr = new int; // 为指针分配一块内存
    *ptr = len; // 给指针指向的内存块赋值
}

// 拷贝构造函数定义
Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数." << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝已有对象的值

}

// 析构函数定义
Line::~Line()
{
    cout << "释放内存." << endl;
    delete ptr; // 释放ptr指向的那块内存
}

// 普通成员函数定义
int Line::getLength()
{
    return *ptr;
}

void display(Line obj)
{
    cout << "line值为:" << obj.getLength() << endl;
}

int main()
{
    Line line(10);
    display(line);
    return 0;
}

修改main函数,使用已有对象来创建新对象,调用拷贝构造函数。

int main()
{
    Line line(10);
    display(line);
    Line line2 = line;
    display(line2);
    return 0;
}

以下几种情况会调用拷贝构造函数:

class A
{
private:
    int a;
public:
    A(int b) // 默认构造函数
    {
        a = b;
    }
    A(const A &sameclass) // 拷贝构造函数
    {
        a = sameclass.a;
    }
    ~A(); // 析构函数
    void show()
    {
        cout << "a = " << a << endl;
    }
};

void a_function(A a)
{
    cout << "test A." << endl;
}
A a_obj(99);
a_function(a_obj);

上述代码中属于类A的对象a_obj以值传递的方式传入a_function函数中,会先产生一个临时变量temp,然后调用拷贝构造函数将a_obj的值赋给temp,就等于A temp(a_obj),直到a_function函数执行完毕后析构掉temp对象。

A a_function()
{
    A temp(3);
    return temp;
}
int main()
{
    a_function();
    return 0;
}

上述代码中先产生一个临时变量xxx,然后调用拷贝构造函数把temp的值赋给xxx,整个过程类似于A xxx(temp); 函数执行到最后先析构temp局部变量,等a_function执行完毕后再析构掉xxx对象。
注 因为声明函数时为A a_function(),所以返回类型为A,而不是A的引用,所以是以值的类型返回。

默认拷贝构造函数

很多时候我们并没有定义拷贝构造函数,但传递对象参数或者从函数返回对象都能很好的进行,这是因为编译器会自动给我们生成一个默认拷贝构造函数。仅仅用于老对象的数据成员的值来对新对象的数据成员赋值。形式如下:

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

但有些情况如果自己不定义拷贝构造函数而使用系统默认拷贝构造函数可能会出错。比如:

class Rect
{
private:
    int width;
    int length;
    static int count;
public:
    Rect()
    {
        count ++;
    }
    ~Rect()
    {
        couunt --;
    }
    static int getCount()
    {
        return count;
    }
};

Rect rect1;
cout << " The count of Rect is :" << Rect::getCount() << endl;
Rect rect2(rect1); // 新建一个rect2
cout << " The count of Rect is :" << Rect::getCount() << endl;
image.png
我们明明又创建了一个rect2,但结果还是显示此时只有1个Rect类的对象。
上述代码对Rect类加入类一个静态变量用来计数(注:同一个类所有的对象公用一个静态变量,所以可以用来计数)。按照代码,此时应该有两个对象存在,但是实际程序运行时输出的都是1,反应出只有1个对象。此外,在销毁对象时,由于会调用销毁两个对象,类的析构函数也会调用两次,此时计数器将变为负数。默认的拷贝构造函数没有处理静态数据成员。,出现这个问题的根本原因是在于赋值对象时候,计数器没有递增,我们重新编写拷贝构造函数。
{
   width = r.width;
   length = r.length;
   count ++ ; // 加上对静态成员的操作
}

浅拷贝

浅拷贝指的就是在对象复制过程中,只是简单的将对象的值赋给另一个对象的数据成员。比如说默认拷贝构造函数。但如果对象中存在类动态成员,那么浅拷贝就会出现问题。

class A
{
private:
    int width;
    int length;
    int *p;
public:
    A() // 默认构造函数
    {
        p = new int(100);
    }
    ~A() // 析构函数
    { 
        if (p!=NULL)
        {
            delete p;
        }
    }
};
A a1;
A a2 = a1;

上述代码运行结束之前,会出现一个错误,原因是在于复制对象时,对于动态分配的内容没有进行正确的操作。

防止默认拷贝构造函数的发生

我们可以声明拷贝构造函数时将它声明为似有成员变量,这样由于拷贝函数为私有成员,所以如果用户按值传递或者函数返回该类对象,将得到一个编译错误,从而避免按值传递或者返回类对象。

private:
    A(const &b)
    {
        width = b.width;
    }

一些其他关于拷贝构造函数的知识

class &x
const class &x
volatile class &x
const volatile class &x
class X
{
public:
    X(const X &x ); // const的拷贝构造函数
    X(X &x); // 非const的拷贝构造函数
};
上一篇 下一篇

猜你喜欢

热点阅读