【C++拷贝构造函数】动态分配与成员对象

系列文章目录

座右铭:人的一生这么长、你凭什么用短短的几年去衡量自己的一生!

个人主页:清灵白羽 漾情天殇_计算机底层原理,深度解析C++,自顶向下看Java-CSDN博客


目录

系列文章目录

一、拷贝构造函数是什么?

1、基本概念

2、触发时机

        1、作为函数参数

        2、作为函数返回值

        3、对象接收

3、参数类型

4、递归调用

 二、拷贝构造的深浅拷贝

 1.什么是深拷贝

        1、浅拷贝

        2、深拷贝

          3、成员对象与深浅拷贝

        4、引用与拷贝构造

 总结


一、拷贝构造函数是什么?

1、基本概念

        拷贝构造函数是构造函数的一种重载形式没有返回值,只有一个参数,用于利用已有的一个对象去创建另一个对象,拷贝构造当中还涉及到深拷贝和浅拷贝,相信这些概念大家已经都了解了,我这里就不做过多的讲解了,我今天结合代码专门来为大家展示一下拷贝构造函数的用法。

        首先请大家看一段代码:

#include"Yangon.h"
using namespace std;

class Member {
private:
	int value;
public:
	Member(int value):value(value) {
		cout << "Member()" << endl;
	}
	Member(const Member& source):value(source.value) {
		cout << "Member(const Member& source)" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member member;
public:
	MyClass(Member member) :member(member) {
		cout << "MyClass()" << endl;
	}
	MyClass(const MyClass& source) :member(source.member){
		cout << "MyClass(const MyClass& source)" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Member member(2024);
	cout << "==============" << endl;
	MyClass myClassObj(member);
	cout << "==============" << endl;
	return 0;
}

        这是这段代码最终的运行结果,针对这个结果我只提一个问题,请问这里的拷贝构造函数为什么会调用两次?其他的关于构造函数和析构函数调用次序的问题我在上一篇文章当中已经详细地解释过了,不了解的小伙伴可以看我之前的文章,这篇文章不做讲解。

【C++拷贝构造函数】动态分配与成员对象_第1张图片

        我直接说答案,因为MyClass类里面是有一个member成员的,我们利用已有的member对象为MyClass类对象当中的member成员赋值是需要调用一次拷贝构造函数的,而且最重要的是在这一行代码当中member是作为函数的一个参数的,这里传参也是需要一次拷贝构造函数的,编译器会利用拷贝构造函数生成一个临时对象传递给这个函数的参数,然后对MyClass类对象当中的member成员赋值又需要一次拷贝构造函数所以这里一共需要调用两次拷贝构造函数。

MyClass myClassObj(member);

2、触发时机

        1、作为函数参数

#include"Yangon.h"
using namespace std;
class MyClass {
private:
	int age;
	string name;
public:
	MyClass(int age, string name) :age(age), name(name) {

	}
	MyClass(const MyClass& source) :age(source.age), name(source.name) {

	}
	void PrintInfo() {
		cout << "age:" << this->age << endl;
		cout << "name:" << this->name << endl;
	}
};
void Func(MyClass myClassOBj) {
	myClassOBj.PrintInfo();
}
int main() {
	MyClass myClassObj(21, "张三");
	Func(myClassObj);
	return 0;
}

        2、作为函数返回值

class MyClass {
private:
	int age;
	string name;
public:
	MyClass(int age, string name) :age(age), name(name) {

	}
	MyClass(const MyClass& source) :age(source.age), name(source.name) {

	}
	void PrintInfo() {
		cout << "age:" << this->age << endl;
		cout << "name:" << this->name << endl;
	}
};
void Func(MyClass myClassOBj) {
	myClassOBj.PrintInfo();
}
MyClass Func2() {
	MyClass myClassObj(33, "李四");
	return myClassObj;
}
int main() {
	MyClass myClassObj(21, "张三");
	Func(myClassObj);
	MyClass myClassObj2 = Func2();
	Func(myClassObj2);
	return 0;
}

        3、对象接收

        当我们使用一个已有的对象去创建另外一个对象的时候,就会需要用到拷贝构造函数。

class MyClass {
private:
	int age;
	string name;
public:
	MyClass(int age, string name) :age(age), name(name) {

	}
	MyClass(const MyClass& source) :age(source.age), name(source.name) {

	}
	void PrintInfo() {
		cout << "age:" << this->age << endl;
		cout << "name:" << this->name << endl;
	}
};
void Func(MyClass myClassOBj) {
	myClassOBj.PrintInfo();
}
MyClass Func2() {
	MyClass myClassObj(33, "李四");
	return myClassObj;
}
int main() {
	MyClass myClassObj(21, "张三");
	Func(myClassObj);
	cout << "1=========" << endl;
	MyClass myClassObj2 = Func2();
	Func(myClassObj2);
	cout << "1=========" << endl;
	MyClass myClassObj3 = myClassObj;
	Func(myClassObj3);
	cout << "1=========" << endl;
	return 0;
}

【C++拷贝构造函数】动态分配与成员对象_第2张图片

3、参数类型

        构造函数的参数类型应该使用const MyClass& source 类型的,那么为什么要这么使用呢?主要还是为了防止一个对象对另外一个对象的修改,我们只是希望用一个已有的对象去构造另外一个对象但是我们并不希望使用一个已有的对象去修改另外一个对象。

class MyClass {
private:
	int age;
	string name;
public:
	MyClass(int age, string name) :age(age), name(name) {

	}
	MyClass(const MyClass& source) :age(source.age), name(source.name) {

	}
	void PrintInfo() {
		cout << "age:" << this->age << endl;
		cout << "name:" << this->name << endl;
	}
};

        我给大家写一段代码:

class MyClass {
private:
	int age;
	string name;
public:
	MyClass(int age, string name) :age(age), name(name) {

	}
	MyClass(MyClass& source) {
		source.age = 21;
		source.name = "张三";
	}
	void PrintInfo() {
		cout << "age:" << this->age << endl;
		cout << "name:" << this->name << endl;
	}
};
int main() {
	MyClass myClassObj(50, "李四");
	MyClass myClassObj2 = myClassObj;
	myClassObj.PrintInfo();
}

        大家可以看一下运行结果,我原本用Obj对象构造Obj2对象,Obj对象有自己的成员变量值,可是如果我们拷贝构造函数没有使用const修饰的话就意味着MyClassObj2对象可以任意修改MyClassObj对象,这样就乱套了,所以拷贝构造函数当中必须要使用const来进行修饰。【C++拷贝构造函数】动态分配与成员对象_第3张图片

4、递归调用

        为什么拷贝构造函数当中一定要使用引用成员进行传参呢?这里就是为了防止产生递归调用,因为如果我们这么定义的话:

class MyClass{
public:
    MyClass(const MyClass source){
        
    }
}

        我们要知道传值传参的话本身就需要调用拷贝构造函数,所以当我们调用拷贝构造函数的时候,拷贝构造函数需要传参、然后传参就需要调用拷贝构造函数,然后拷贝构造函数又需要传参、传参又需要调用拷贝构造函数,所以就会没完没了的一直递归调用下去、所以我们这里使用引用的方式就可以避免递归调用。


 二、拷贝构造的深浅拷贝

 1.什么是深拷贝

        深拷贝是指在进行时对象拷贝,不仅复制对象本身的数据,还复制了对象所指向的动态分配的内存,并且创建一个新的内存块存储相同的数据,这样原始对象和拷贝对象是完全独立的,这对其中一个对象的修改不会影响另外一个对象,在C++当中如果一个对象使用new关键字来分配内存的话,通常需要进行深拷贝。

        例如指针,如果一个类当中涉及到指针那么拷贝构造函数就必须使用深拷贝,如果是浅拷贝的话仅仅是简单地将指针的值复制给了另外一个指针,那么也就意味着两个指针的值相同所以两个指针指向了同样的一块区间,当这两个指针进行释放的时候,同一块区间被释放两次编译器就会报错,所以也就引入了深拷贝。

        1、浅拷贝

class ShallowCopy {
public:
    int* data;

    // 构造函数
    ShallowCopy(int value) : data(new int(value)) {}

    // 拷贝构造函数(浅拷贝)
    ShallowCopy(const ShallowCopy& source) : data(source.data) {}

    // 析构函数
    ~ShallowCopy() {
        delete data; // 这里会导致编译不通过,多个对象共享同一块内存
    }
};

int main() {
    ShallowCopy obj1(42);
    ShallowCopy obj2 = obj1; // 浅拷贝,两个对象共享相同的内存

    std::cout << *obj1.data << std::endl; // 输出 42
    std::cout << *obj2.data << std::endl; // 输出 42

    // 修改其中一个对象的数据
    *obj1.data = 99;

    // 由于浅拷贝,两个对象的数据都被修改
    std::cout << *obj1.data << std::endl; // 输出 99
    std::cout << *obj2.data << std::endl; // 输出 99

    return 0;
}

        2、深拷贝

class DeepCopy {
public:
    int* data;

    // 构造函数
    DeepCopy(int value) : data(new int(value)) {}

    // 拷贝构造函数(深拷贝)
    DeepCopy(const DeepCopy& source) : data(new int(*(source.data))) {}

    // 析构函数
    ~DeepCopy() {
        delete data; // 每个对象都有自己的数据副本,安全释放内存
    }
};

int main() {
    DeepCopy obj1(42);
    DeepCopy obj2 = obj1; // 深拷贝,每个对象有自己的数据副本

    std::cout << *obj1.data << std::endl; // 输出 42
    std::cout << *obj2.data << std::endl; // 输出 42

    // 修改其中一个对象的数据
    *obj1.data = 99;

    // 由于深拷贝,两个对象的数据独立
    std::cout << *obj1.data << std::endl; // 输出 99
    std::cout << *obj2.data << std::endl; // 输出 42

    return 0;
}

          两段代码的结果我已经为大家展示出来了,如果对于指针变量采用浅拷贝的话导致两个为指针指向同一块空间的话,修改其中一个也吧必然会修改另外一个。   

          3、成员对象与深浅拷贝

        这段代码为大家展示了当一个类当中包含成员对象的时候,深浅拷贝要如何进行。

class Member {
private:
	int* data;
public:
	Member(int* data) {
		this->data = new int(*data);
	}
	Member(const Member& source) {
		this->data = new int(*source.data);
	}
	~Member() {
		delete data;
	}
};
class MyClass {
private:
	Member member;
public:
	MyClass(Member member):member(member) {

	}
	MyClass(const MyClass& source) :member(source.member) {

	}
};
int main() {
	int value = 2025;
	int* ptr = &value;
	Member member(ptr);
	MyClass myClassObj(member);
    MyClass myClassObj2 = myClassObj;
	return 0;
}

        大家看完代码之后我主要提一个问题,请问MyClass对象调用拷贝构造函数的时候,因为MyClass对象内部包含成员对象,那么MyClass类当中需不需要专门针对成员对象调用一个深拷贝构造函数呢?因为大家知道这行代码其实主要是浅拷贝也就是值拷贝,直接将一个MyClass对象当中的member复制给另一个MyClass对象,那么这里需不需要定义一个深拷贝呢?答案是不需要!因为再拷贝构造函数当中的初始化列表当中MyClass对象会自动调用Member类对象的拷贝构造函数,只要Member类当中是深拷贝就可以了(前提是Member类当中有指针变量,否则内置类型成员不需要定义深拷贝)

MyClass(const MyClass& source) :member(source.member) {

	}

        4、引用与拷贝构造

        首先声明一点,引用成员是不需要定义深拷贝的,但是也要注意,如果类当中定义了引用成员那么再进行拷贝构造函数的时候采用值传递,将导致多个对象共同持有一个相同的引用,而且引用定义好之后也不可以进行修改,代码如下:

class Member {
public:
	int& data;
public:
	Member(int value) :data(value) {

	}
	Member(const Member& source) :data(source.data){

	}
};
int main() {
	Member member(2025);
	Member member2 = member;
	Member member3 = member2;
	cout << member.data << endl;
	cout << member2.data << endl;
	cout << member3.data << endl;

	return 0;
}

 总结

        拷贝构造函数当中的主要知识点就已经为大家介绍完了,这部分还有其它的一部分内容等到了后续的异常和C++11当中再详细为大家讲解。

你可能感兴趣的:(深度解析C++,c++)