类的赋值操作
前文我们说到,在什么情况下我们需要自定义复制构造函数。现在我们再来讲讲类的赋值操作是否也需要重载。我们先来看个例子。
1: #include <iostream>2: using namespace std;3:4: class Test
5: {6: public:
7:8: Test(char* p)
9: {10: cout<<"This is a override constructor."<<endl;
11: if(p)
12: {13: a=new char[strlen(p)+1];14: strcpy(a,p);15: }else
16: {17: a=new char[1];18: *a='\0';19: }20: }21:22: Test(Test const &t)
23: {24: cout<<"This is a copy constructor."<<endl;
25: if (t.a)
26: {27: a=new char[strlen(t.a)+1];28: strcpy(a,t.a);29: }else
30: {31: a=new char[1];32: *a='\0';33: }34: }35:36: ~Test()37: {38: delete []a;
39: }40:41: public:
42: char* a;
43: };44:45: int main()
46: {47: Test *t1=new Test("Hello, C++!");48: cout<<"t1:"<<t1->a<<endl;
49: Test t2("Hello,t1!");
50: cout<<"t2:"<<t2.a<<endl;
51: t2=*t1;52: delete t1;
53: cout<<"t2:"<<t2.a<<endl;
54: return 0;
55: }56:57:
得到的结果如下,可以看出,出现野指针了……
我们知道,默认情况下,编译器编译时会自动生成一个复制操作的实现,完成所有数据成员的值拷贝。那么我们就能看出上面例子的问题了。第一,野指针的问题;第二,复制后会有内存不释放的现象。我们简单地画个示意图,表示其中存在的问题。
图1 执行“t2=*t1;”前
图2 执行“t2=*t1;”后
从图1和图2的对比中我们可以看出,执行“t2=*t1;”后,t2中的a指针指向了内存1,内存2没有被释放。当执行完“delete t1; ”后,内存1被释放掉,t2的a指针称为野指针。既然我们知道问题是如何产生的,那么修改上面的代码以解决这些问题。新的代码如下:
1: #include <iostream>2: using namespace std;3:4: class Test
5: {6: public:
7:8: Test(char* p)
9: {10: cout<<"This is a override constructor."<<endl;
11: if(p)
12: {13: a=new char[strlen(p)+1];14: strcpy(a,p);15: }else
16: {17: a=new char[1];18: *a='\0';19: }20: }21:22: Test(Test const &t)
23: {24: cout<<"This is a copy constructor."<<endl;
25: if (t.a)
26: {27: a=new char[strlen(t.a)+1];28: strcpy(a,t.a);29: }else
30: {31: a=new char[1];32: *a='\0';33: }34: }35:36: //重载=操作符
37: Test& operator=(Test const& t)38: {39: cout<<"This is a operator=."<<endl;
40: if (this==&t)41: {42: return *this;43: }44: if (t.a)
45: {46: if (a)
47: {48: delete []a;
49: }50: a=new char[strlen(t.a)+1];51: strcpy(a,t.a);52: }else
53: {54: a=new char[1];55: *a='\0';56: }57: return *this;58: }59:60: ~Test()61: {62: delete []a;
63: }64:65: public:
66: char* a;
67: };68:69: int main()
70: {71: Test *t1=new Test("Hello, C++!");72: cout<<"t1:"<<t1->a<<endl;
73: Test t2("Hello,t1!");
74: cout<<"t2:"<<t2.a<<endl;
75: t2=*t1;76: delete t1;
77: cout<<"t2:"<<t2.a<<endl;
78: return 0;
79: }80:81:
我们再来看看运行结果。
这次的运行结果是正确的,重载的=操作符使得t2重新申请了空间,存放数据。当t1被删除后,t2的数据并没有被删除。好了,写到这,大家应该清楚重载=操作符的重要性了吧。
还需要补充的两点。
1 “Test t2=t1;”是属于类的赋值还是属于类的复制构造?
属于类的复制构造。各位可以用上面的代码试一试嘛。额外添加“Test t3=t2;cout<<"t3:"<<t3.a<<endl;”,得到的结果是
2 “Test t2=t1;”和“Test t2;t2=t1;”谁的效率更高?
//调用类的复制构造函数实例化对象t2
Test t2=t1;
//调用类的默认构造函数实例化对象t2,再调用复制操作
Test t2;
t2=t1;
最后,我们得到的结论是在含有指针数据成员的时候,类的赋值和复制构造函数必须同时设计,尽量用复制构造函数来代替赋值。
感谢:《C++编程关键路径——程序员求职指南》