C++拷贝构造

发布于:2025-07-18 ⋅ 阅读:(15) ⋅ 点赞:(0)

转换构造

一个类中构造函数可以重载,允许多个存在。如果构造函数只有一个参数且非当前类对时,可以将其他类型自动转换为当前类类型,这个过程为隐式类型转换。如下示例中的三个构造函数都可以发生隐式类型转换,在使用等号初始化或赋值时自动转换。
这样的可以发生隐式类型转换的构造函数可以称之为:转换构造函数。

class CTest {
    CTest(int a) {} // 转换构造函数
    /*
    CTest(int a, int b = 0) {} // 转换构造函数
    CTest(int a = 0, int b = 0) {} // 转换构造函数
    explicit CTest(int a = 0, int b = 0) {} // 禁止发生隐式类型转换
    CTest(int a, int b) {} // 不是转换构造函数
    */
};
CTest tst(10); // 调用带参数的构造
CTest tst2 = 20; // 合法操作 发生隐式类型转换,将int类型转换为CTest类型
tst2 = 30; // 合法操作 发生隐式类型转换

 

  • 转换构造函数:只定义了一个接受一个 int 参数的构造函数,允许隐式类型转换。
  • 取消了其他构造函数的注释后,下面是解释:
    • CTest(int a, int b = 0)可以通过一个参数隐式转换,第二个参数使用默认值。
    • CTest(int a = 0, int b = 0)同样可以进行隐式转换,但两个参数都有默认值。
    • explicit CTest(int a = 0, int b = 0)禁止隐式类型转换。只有在显式调用构造函数时才能构造对象。
    • CTest(int a, int b)此构造函数需要两个参数,因此不能用来进行隐式转换。
    • CTest tst(10);这是合法的,调用了 CTest(int a) 构造函数,创建一个 CTest 对象 tst
    • CTest tst2 = 20;这里发生了隐式类型转换,20 被转换为 CTest 类型,调用了 CTest(int a) 构造函数来初始化 tst2。这是合法的,因为你定义了一个可以接受单个 int 参数的构造函数。
    • tst2 = 30;这是合法的,30 被隐式转换为 CTest 类型,赋值给 tst2。同样使用了 CTest(int a) 构造函数。

注意:如果是多个参数且无默认值时,则不能自动隐式类型转换。如果想要避免隐式类型转换,在构造函数前加上 关键字:explicit。

拷贝构造函数

拷贝构造函数:是众多构造函数中的一种,空类中编译期会默认提供的一个函数,它
与默认的无参构造并存,函数名为当前类名,参数为当前类对象的引用(const 类名&
类对象),函数体代码不为空,代码为形参对象中的成员属性依次给this对象成员属性
初始化,一旦我们手动重构了拷贝构造,编译期就不会提供默认的了。当然也不会存
在默认的无参构造了。当用一个类对象给类的另一个对象初始化时,会调用拷贝构造函数。

对上述话以此拆分讲述

参数为当前类对象的引用。与默认无参构造不同,其函数体代码一般不为空,操作为:参数中对象成员依次给this对象成员进行初始化。

class CTest {
		int m_a;
//默认的拷贝构造函数一般长这个样子,对于初始化代码下面两种写法都可以
		CTest(const CTest & tst):m_a(tst.m_a) {//初始化参数列表
//this->m_a = tst.m_a;//或者再构造体内
		}
};

 上述代码解析:拷贝构造函数的主要作用是通过已存在的对象来初始化一个新对象。在这个例子中,定义的拷贝构造函数接受一个 CTest 类型的常量引用参数 tst,并使用这个对象的 m_a 成员变量来初始化新对象的 m_a 成员变量。


当我们手动重构拷贝构造函数时,编译器就不会提供默认的拷贝构造函数了,当然也不会存在默认的无参构造了。
当用一个类对象给类的另一个对象初始化时,会调用拷贝构造函数。 

class CTest {
		CTest();
		CTest(const CTest &tst);
};
CTest tst1; //调用无参构造
CTest tst2(tst1); //调用拷贝构造

 默认的拷贝构造函数:是一个浅拷贝
浅拷贝需注意的问题:当类中存在指针成员且指向了一个具体的空间,拷贝构造函数
只是将两个指针里存储的地址进行一个值传递,并不会处理指针指向的空间。这样就
导致了多个对象里的指针指向了同一个空间,那么会导致以下两个问题:

  1. 数据一致性问题

    • 当一个对象通过指针修改了其指向的内存空间的内容时,其他对象访问同一内存空间时会得到已修改的值。这种情况下,修改的意图和结果可能不符合预期。如果多个对象共享同一数据,可能会导致意外的副作用。
  2. 内存管理问题

    • 如果对象的析构函数试图释放new出来的空间,释放指针所指向的内存空间,而多个对象指向同一地址,将导致后续的对象尝试释放已经被释放的内存,从而引发未定义行为(比如崩溃或内存泄漏)。

 

 解决浅拷贝的问题

解决办法: 深拷贝,它并不是一个固定的写法,而是一个解决的办法:即在拷贝构造时,如果参数对象中的指针成员指向了一个内存空间,那么在重构拷贝构造时,需要为当前this对象中指针成员额外开辟新的内存空间,并初始化对应的值。

即:深拷贝会为每个对象的指针成员分配新的内存空间,并复制指针指向的内容。

class CTest {
		int *m_p;
		CTest(const CTest & tst) {
			//深拷贝
			if (tst.m_p)
				m_p = new int(*tst.m_p);
			else
				m_p = nullptr;
		}
};

 在某些情况下,可以使用指针或引用可以避免对象的值传递,也避免了浅拷贝问题。

void fun(CTest tst); //避免值传递
void fun(CTest & tst);// or void fun(CTest *ptst);

 例子:

class MyClass {
private:
    int* data;
public:
    // 构造函数
    MyClass(int value) {
        data = new int(value);
    }

    // 拷贝构造函数
    MyClass(const MyClass& other) {
        data = new int(*other.data); // 深拷贝
    }

    // 析构函数
    ~MyClass() {
        delete data; // 释放内存
    }

    // 赋值操作符重载(如果需要)
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            delete data; // 先释放原有内存
            data = new int(*other.data); // 深拷贝
        }
        return *this;
    }
};

赋值函数operator=

operator=与拷贝构造的区别:

拷贝构造发生在构造对象的时候,operator=是赋值。


空类中会默认提供一个 operator=函数,函数和返回值 为当前类对象的引用,函数体代码不为空,形参对象中的成员属性依次给this对象成员属性赋值,用当前类对象给另一个类对象进行赋值操作。如果手动重构了operator=,编译器就不会提供默认的了。

class CTest {
	public:
		int m_a;
		CTest& operator=(const CTest& tst) {
			this->m_a = tst.m_a;
		}
};
CTest tst1;
CTest tst2;
tst2 = tst1; //匹配operator =

operator=函数 默认也是一个浅拷贝,也会出现浅拷贝问题。解决办法:深拷贝 。

int * m_p;
CTest& operator=(const CTest& tst) {
	if(this != &tst) { //判断是否自己给自己赋值
		this->m_a = tst.m_a;
		if (tst.m_p) {
			if (this->m_p) {
				*this->m_p = *tst.m_p;
			} else {
				this->m_p = new int(*tst.m_p);
			}
		} else {
			if (this->m_p) {
				delete this->m_p;
			}
			this->m_p = nullptr;
		}
	}
}

空类中存在的默认的函数4个:

  1. 默认无参数构造
  2. 默认的拷贝构造
  3. 默认的operator=
  4. 默认析构函数

网站公告

今日签到

点亮在社区的每一天
去签到