小甲鱼-C++快速入门笔记(37)之副本构造器

本文深入探讨了C++中对象复制时出现的问题,特别是当对象包含指针成员时,逐位复制会导致两个对象的指针指向同一内存地址。文章介绍了如何通过重载赋值运算符来实现深拷贝,避免悬挂指针问题,并给出了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前讲过,我们可以把一个对象赋值给一个类型与之相同的变量。编译器将生成必要的代码把“源”对象各属性的值分别赋值给“目标”对象的对应成员。这种赋值行为称之为逐位赋值(bitwise copy)。这种行为在绝大多数数场合都没有问题,但如果某些成员变量时指针的话,那么问题来了:对象成员进行逐位复制的结果是你将拥有两个一模一样的实例,而这两个副本里的同名指针会指向相同的地址。

事实上,对于任何一个类,如果没有用户自定义的赋值运算符函数,系统会自动地为其生成一个默认的赋值运算符函数,以完成数据成员之间的复制。例如下面的程序段:

X & X::operator=(const X &source)
{
    //类对象成员之间赋值语句
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

一旦类X的两个对象ob1和ob2已创建,就可以用ob1=ob2进行赋值了。通常情况下,默认的赋值运算符函数就可以完成赋值任务,但在某些特殊情况下。例如,类中有一种指针类的形式,如果使用默认的赋值运算符函数就会产生指针悬挂的错误。此时,就必须显式地定义一个赋值运算符重载函数使参与赋值的两个对象有各自的存储空间,以解决这个问题。

要是程序员在当初进行对象“复制”时能够精确地表明应该复制些什么和如何复制,那就理想了。

分析下面几行代码:

MyClass obj1;
MyClass obj2;
obj2 = obj1;

解决方案:

1、重载赋值操作符

---MyClass &operator = (const MyClass &rhs);

因为这里使用的参数是一个引用,所以编译器在传递输入参数时就不会再为它创建另外一个副本(否则可能导致无限递归)。又因为这里只需要读取这个输入参数,而不用改变它的值,所以我们用const把那个引用声明为一个常量确保万无一失。返回一个引用,该引用指向一个MyClass类的对象。

#include <iostream>
#include <string>

using namespace std;

class MyClass
{
public:
	MyClass(int *p);
	~MyClass();

	MyClass &operator = (const MyClass &rhs);  //重载赋值符
	void print();

private:
	int *ptr;
};

MyClass::MyClass(int *p)
{
	ptr = p;
}

MyClass::~MyClass()
{
	delete ptr;
}

MyClass &MyClass::operator = (const MyClass &rhs)
{
	if(this != &rhs)
	{
		delete ptr;

		ptr = new int;
		*ptr = *rhs.ptr;
	}
	else
	{
		cout << "赋值号两边为同个对象,不做处理!\n";
	}
	
	return *this;
}

void MyClass::print()
{
	cout << *ptr << endl;
}

int main()
{
	MyClass obj1(new int(1));
	MyClass obj2(new int(2));

	obj1.print();
	obj2.print();

	obj2 = obj1;

	obj1.print();
	obj2.print();

	return 0;
}

但是,只对赋值符进行重载还不能完美地解决问题,正如刚才所说,C++的发明者把解决方案弄得有点复杂。

有个问题:为啥在VC上能编译,但运行时会程序中止。

以下代码通过构造一个副本构造器

#include <iostream>
#include <string>

using namespace std;

class MyClass
{
public:
	MyClass(int *p);
	MyClass(const MyClass &rhs);   //副本构造器
	~MyClass();

	MyClass &operator = (const MyClass &rhs);  //重载赋值符
	void print();

private:
	int *ptr;
};

MyClass::MyClass(int *p)+
{
	cout << "进入主构造器\n";
	ptr = p;
	cout << "离开主构造器\n";
}

MyClass::MyClass(const MyClass &rhs)
{
	cout << "进入副本构造器\n";
	*this = rhs;    //这里的赋值已经是重载之后的赋值符
	cout << "离开副本构造器\n";
}

MyClass::~MyClass()
{
	cout << "进入析构器\n";
	delete ptr;
	cout << "离开析构器\n";
}

MyClass &MyClass::operator = (const MyClass &rhs)
{
	cout << "进入赋值语句重载\n";
	if(this != &rhs)
	{
		delete ptr;

		ptr = new int;
		*ptr = *rhs.ptr;
	}
	else
	{
		cout << "赋值号两边为同个对象,不做处理!\n";
	}
	
	cout << "离开赋值语句重载\n";

	return *this;
}

void MyClass::print()
{
	cout << *ptr << endl;
}

int main()
{
	MyClass obj1(new int(1));
	MyClass obj2(new int(2));
	obj2 = obj1;
	obj1.print();
	obj2.print();

	cout << "-----------------------\n";

	MyClass obj3(new int(3));
	MyClass obj4 = obj3;
	obj3.print();
	obj4.print();

	cout << "-----------------------\n";

	MyClass obj5(new int(5));
	obj5 = obj5;
	obj5.print();

	return 0;
}

重新写的一个例子:

#include <iostream>
#include <string>

using namespace std;

class Internet
{
public:
	Internet(char *name, char *url);   //定义构造函数
	Internet(Internet &temp);     //拷贝构造函数
	~Internet();   

	Internet& operator = (Internet &temp);  //赋值运算符重载

public:
	char *name;
	char *url;
};

Internet::Internet(char *name, char *url)
{
	this -> name = new char[strlen(name)+1];
	this -> url = new char[strlen(url)+1];

	if(name)
	{
		strcpy(this -> name, name);
	}
	if(url)
	{
		strcpy(this -> name, name);
	}
}
Internet::Internet(Internet &temp)
{
	this -> name = new char[strlen(temp.name) + 1];
	this -> url = new char[strlen(temp.url) + 1];
	if(name)
	{
		strcpy(this -> name, temp.name);
	}
	if(url)
	{
		strcpy(this -> name, temp.url);
	}
}

Internet::~Internet()
{
	delete[] name;
	delete[] url;
}

Internet& Internet::operator = (Internet &temp)
{
	delete[] this -> name;
	delete[] this -> url;
	this -> name = new char[strlen(temp.name) + 1];
	this -> url = new char[strlen(temp.url) + 1];
	if(this -> name)
	{
		strcpy(this -> name, temp.name);
	}
	if(this -> url)
	{
		strcpy(this -> url, temp.url);
	}

	return *this;
}

int main()
{
	Internet a("Education", "www.edu.cn");   //创建对象
	Internet b = a;     //b对象还不存在,所以调用拷贝构造函数,进行构造处理
 
	cout << b.name << endl << b.url << endl;

	Internet c("Tsinghua", "www.tsinghua.edu.cn");  //b对象已经存在,所以系统选择赋值运算符重 
                                                    //载函数处理
	b = c;

	cout << b.name << endl << b.url << endl;

	return 0;
}

注意:在进行赋值运算符"="重载时,赋值运算符只能重载为成员运算符,不能重载为友元运算符函数。此外,赋值运算符重载后不能被继承。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值