[ C++ ] — 拷贝构造函数(复制构造函数)

拷贝构造函数

拷贝构造函数就是用 同一类型的对象复制成员值来初始化对象(当出现类的 “=” 赋值时,就会调用拷贝构造函数)
简单来说,拷贝构造函数就是来复制对象的

  • 默认拷贝构造函数:如果类中没有定义拷贝构造函数,编译器会自行定义一个。
  • 如果类成员都是简单类型(如标量值),则编译器生成的复制构造函数已足够,无需定义自己的类型。
  • 如果类需要更复杂的初始化,则需要实现自定义复制构造函数。例如,如果类成员是指针,则需要定义复制构造函数以分配新内存,并复制另一个对象中的值。
  • 拷贝构造函数可以有其它的参数,只要其它参数都有默认值
  • 拷贝构造函数中可以访问同类型参数对象的私有数据成员,因为C++的限定符是限定类的,不是限定对象的,只要是类型相同就能相互访问。两个是同类型的,因此可以直接访问,但是需要指定一下是哪个对象。
classname(const classname& obj){
	//拷贝构造函数主体
	//可访问obj的私有成员
}

例如

classname a;
classname b = a; //调用了拷贝构造函数

这里调用的是b的拷贝构造函数,而参数obj是a

浅拷贝与深拷贝

  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,进行拷贝操作
    [ C++ ] — 拷贝构造函数(复制构造函数)_第1张图片

1) 浅拷贝

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。

class Box {
private:
    int var;
public:
    Box(int temp) {
        var = temp;
    }

    int getValue() {
        return var;
    }
};

int main()
{
    Box a(100);
    Box b = a; //调用了拷贝构造函数
    cout << a.getValue() << endl;
    cout << b.getValue() << endl;
    return 0;
}

输出:

100
100

此时,调用的是默认的拷贝构造函数

class Box {
private:
    int var;
public:
    Box(int temp) {
        var = temp;
    }
    Box(const Box& obj) {
        cout<<"I'm a Copy Constructor"<<endl;
    }
    int getValue() {
        return var;
    }
};

int main()
{
    Box a(100);
    Box b = a;//调用了拷贝构造函数
    cout << a.getValue() << endl;
    cout << b.getValue() << endl;
    return 0;
}

而当我们加入自己定义的拷贝构造函数时,输出如下。

I'm a Copy Constructor
100
-858993460

此时,就不再调用默认拷贝构造函数,因此b中的var值并没有初始化

2)深拷贝

以上情况类成员都是简单类型,如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数

class Box {
private:
    int* ptr;
public:
    Box(int height) { //简单的构造函数
        cout << "调用构造函数" << endl;
        ptr = new int; //分配内存
        *ptr = height;
    }
    Box(const Box& obj) { //拷贝构造函数
        cout << "调用拷贝构造函数" << endl;
        ptr = new int;
        *ptr = *obj.ptr;
    }
    ~Box(){ //析构函数
        cout << "释放内存" << endl;
        delete ptr;
    }
    void show() {
        cout << "Height: " << *ptr << endl;
    }
};

int main()
{
    Box a(10); //调用构造函数
    Box b = a; //调用拷贝构造函数
    a.show();
    b.show();
    return 0;
}

输出:

调用构造函数
调用拷贝构造函数
Height: 10
Height: 10
释放内存
释放内存

若仅使用默认拷贝构造函数,则系统会报错

class Box {
private:
    int* ptr;
public:
    Box(int height) { //简单的构造函数
        cout << "调用构造函数" << endl;
        ptr = new int; //分配内存
        *ptr = height;
    }
    ~Box(){ //析构函数
        cout << "释放内存" << endl;
        delete ptr;
    }
    void show() {
        cout << "Height: " << *ptr << endl;
    }
};

int main()
{
    Box a(10); //调用构造函数
    Box b = a; //调用拷贝构造函数
    a.show();
    b.show();
    return 0;
}

[ C++ ] — 拷贝构造函数(复制构造函数)_第2张图片
因为浅拷贝仅将成员的值进行复制,即b.ptr=a.ptr,也就是两个指针指向同一个空间。在销毁对象时,两个对象的析构函数对同一个内存空间释放两次,所以会出现错误。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值

3)调用拷贝构造函数的地方

  • 进行类对象之间的赋值操作
  • 作为函数参数
  • 作为函数返回对象

a)类对象之间的赋值操作时

Box a;
  • 非引用类型的赋值操作: Box b=a ,会调用拷贝构造函数需要注意是否需要进行深拷贝
  • 引用类型赋值:Box& b = a,此时不会调用拷贝构造函数

b)作为函数参数

  • 按值传递:void func(Box b),调用复制构造函数,按参数的值构造一个临时对象,这个临时对象仅仅在函数执行是存在,函数执行结束之后调用析构函数。
  • 引用传递: void func(Box& b)不会调用任何构造函数。

c)作为函数返回对象

  • 返回一个值:Box func(),调用复制构造函数,返回时按参数的值构造一个临时对象,这个临时对象在函数返回后调用析构函数。当临时对象完成复制构造后,就不需要它了,会析构这个对象。最好能将临时对象的资源直接转移给构造的对象,可以使用移动构造函数。见[ C++ ] — 右值引用和移动构造函数
class Box {
public:
	Box();
    Box(const Box& obj) {
        cout << "I'm a Copy Constructor" << endl;
    }
   
};
Box func() {
    Box temp;
    return temp;
}

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

输出

I'm a Copy Constructor
  • 返回一个引用Box& func()不调用构造函数。
Box func() {
    Box temp;
    return temp;
}

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

无输出

然而

int main()
{
	Box a;
    a=func();
    return 0;
}

此时输出

I'm a Copy Constructor

复制构造发生在a=func();

4)防止默认拷贝发生

如果不想用户

  • 进行类对象之间的赋值操作(用已有对象初始化新建对象)
  • 将类对象作为函数参数
  • 将类对象作为函数返回对象

则可以声明一个私有拷贝构造函数,甚至不用定义它,当用户试图这么做时会报错。

class Box {
private:
    Box(const Box& obj);
    ...
    };
void f1(Box box);

int main(){
Box a;
Box b=a;  //报错
Myfunction(a); //报错
}

5)常见问题

1、 为什么拷贝构造函数必须用引用传递,而不是值传递?
防止无限递归。classA(const classname& obj){ } 因为当使用值传递时,首先要生成obj的一个副本。而生成副本的过程还需要调用拷贝构造函数,因此这个过程会无限递归下去。

2、参数传递过程?

与内置数据类型的不同:

值传递

  • 内置数据类型传递时,直接将值拷贝给形参
  • 而类类型传递时,首先需要调用该类的拷贝构造函数

引用传递

  • 无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值,不会有拷贝构造函数的调用(对于类类型)!

3、 一个类中可以有多个拷贝构造函数吗?
可以

class Box{
public:
	Box(const Box& ); //const的拷贝构造函数
	Box(Box&); //非const的拷贝构造函数
};

注意,如果一个类中只存在一个参数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化.

4、下列哪些是拷贝构造函数

	Box(Box& other); // Avoid if possible--allows modification of other.
    Box(const Box& other);
    Box(volatile Box& other);
    Box(volatile const Box& other);
    // Additional parameters OK if they have default values
    Box(Box& other, int i = 42, string label = "Box");
    Box(other);

除了最后一个其他全是。参数中带该类类型的引用参数的构造函数就是。
拷贝构造函数可以有其它的参数,只要其它参数都有默认值

参考:
彻底讲明白浅拷贝与深拷贝
c++拷贝构造函数详解
菜鸟教程——C++拷贝构造函数
微软C++文档

你可能感兴趣的:(C++,c++,开发语言,后端)