C++:类中的赋值函数
先来看一个例子:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"调用默认构造函数"<<endl; 8 }; 9 Student(string name,int age,string gender):Name(name),Age(age),Gender(gender){ 10 //cout<<"调用构造函数1"<<endl; 11 } 12 Student(const Student& stu){//拷贝构造函数 13 Name=stu.Name; 14 Age=stu.Age; 15 Gender=stu.Gender; 16 cout<<"调用拷贝构造函数"<<endl; 17 } 18 ~Student(){ 19 //cout<<"调用析构函数"<<endl; 20 } 21 void show(){ 22 cout<<"Name:"<<Name<<endl; 23 cout<<"Age:"<<Age<<endl; 24 cout<<"Gender:"<<Gender<<endl; 25 } 26 private: 27 string Name; 28 int Age; 29 string Gender; 30 }; 31 32 int main(){ 33 Student stu("Tomwenxing",23,"male"); 34 Student stu2("Ellen",24,"female"); 35 cout<<"---------------------赋值操作之前-----------------"<<endl; 36 stu2.show(); 37 cout<<"---------------------赋值操作之后-----------------"<<endl; 38 stu2=stu; 39 stu2.show(); 40 return 0; 41 }
由上面的例子可以看出,C++支持自定义类型的对象之间的赋值操作,而赋值功能的实现则主要依靠自定义类中的赋值函数。每一个自定义类中都有且只有一个赋值函数,该赋值函数既可以由编译器隐式地定义在自定义类中,也可以有用户通过对赋值运算符=的重载显式地定义在自定义类中:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"调用默认构造函数"<<endl; 8 }; 9 Student(string name,int age,string gender):Name(name),Age(age),Gender(gender){ 10 //cout<<"调用构造函数1"<<endl; 11 } 12 Student(const Student& stu){//拷贝构造函数 13 Name=stu.Name; 14 Age=stu.Age; 15 Gender=stu.Gender; 16 cout<<"调用拷贝构造函数"<<endl; 17 } 18 ~Student(){ 19 //cout<<"调用析构函数"<<endl; 20 } 21 Student& operator=(const Student& stu){ //赋值函数 22 cout<<"调用类中的赋值函数"<<endl; 23 if(this!=&stu){ 24 Name=stu.Name; 25 Age=stu.Age; 26 Gender=stu.Gender; 27 } 28 return *this; 29 } 30 void show(){ 31 cout<<"Name:"<<Name<<endl; 32 cout<<"Age:"<<Age<<endl; 33 cout<<"Gender:"<<Gender<<endl; 34 } 35 private: 36 string Name; 37 int Age; 38 string Gender; 39 }; 40 41 int main(){ 42 Student stu("Tomwenxing",23,"male"); 43 Student stu2("Ellen",24,"female"); 44 cout<<"---------------------赋值操作之前-----------------"<<endl; 45 stu2.show(); 46 cout<<"---------------------赋值操作之后-----------------"<<endl; 47 stu2=stu; 48 stu2.show(); 49 return 0; 50 }
特别注意:
Question 1:类中的赋值函数中的参数为什么加const?
Answer:参数使用cosnt的原因有两个:
• 防止类中的赋值函数对用来赋值的“原对象”进行修改
1 Student& Student::operator=(Student& stu){ 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 stu.Name="none";//错误,对用来赋值的“原对象”进行了修改 5 stu.Age=0;//错误 6 stu.Gender="none";//错误 7 Name=stu.Name; 8 Age=stu.Age; 9 Gender=stu.Gender; 10 } 11 return *this; 12 }
•若赋值函数的形参加上const,则赋值函数接受的实参对象既可以是const对象,也可以是非const对象;否则赋值函数能够接受的对象只能是非const对象,而不能是const对象。
1 Student& Student::operator=(Student& stu){ 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 Name=stu.Name; 5 Age=stu.Age; 6 Gender=stu.Gender; 7 } 8 return *this; 9 } 10 int main(){ 11 const Student stu("Tomwenxing",23,"male"); 12 Student stu2("Ellen",24,"female"); 13 stu2=stu; //错误!不能将const对象赋值给非const对象 14 return 0; 15 }
Question 2:类中的赋值函数中的参数为什么使用引用?
Answer:避免调用类中的拷贝构造函数在内存中开辟空间来创建形参对象,而是让形参成为实参的别名,从而节省时间和空间,提供编程效率
1 Student& Student::operator=(const Student &stu){ //参数中使用引用 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 Name=stu.Name; 5 Age=stu.Age; 6 Gender=stu.Gender; 7 } 8 return *this; 9 } 10 11 int main(){ 12 const Student stu("Tomwenxing",23,"male"); 13 Student stu2("Ellen",24,"female"); 14 stu2=stu; 15 return 0; 16 }
1 Student& Student::operator=(const Student stu){ //参数中没有使用引用 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 Name=stu.Name; 5 Age=stu.Age; 6 Gender=stu.Gender; 7 } 8 return *this; 9 } 10 11 int main(){ 12 const Student stu("Tomwenxing",23,"male"); 13 Student stu2("Ellen",24,"female"); 14 stu2=stu; 15 return 0; 16 }
Question 3:类中的赋值函数的返回值类型为什么是Student&,不可以是Student或void吗?
Answer:在C++中,系统支持变量之间的连续赋值,如:
1 int a=10; 2 int b,c; 3 b=c=a;//变量之间的连续赋值,b、c的值均为10
其本质是先将变量a的值赋值给变量c,然后将赋值后的变量c返回到赋值运算符=的右边,再将其赋值给变量b,即:
1 int a=10; 2 b=(c=a); //即c=a,b=c;
同理,如果类中赋值函数的返回值类型为void,即类中的赋值函数没有返回值,此时编译器将不支持对该类中的对象进行连续赋值
1 void Student::operator=(const Student &stu){ 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 Name=stu.Name; 5 Age=stu.Age; 6 Gender=stu.Gender; 7 } 8 } 9 int main(){ 10 const Student stu("Tomwenxing",23,"male"); 11 Student stu2("Ellen",24,"female"); 12 Student stu3; 13 stu3=stu2=stu; //错误!stu3=stu2=stu相当于stu3.operator=(stu2.operator=(stu)) ,由于赋值函数没有返回值,则该语句无法执行
14 return 0; 15 }
而赋值函数的返回值类型之所以是Student&而非Student是为了避免函数在返回对象时调用类中的拷贝构造函数在内存中创建临时对象,从而提高程序的运行效率
1 Student& Student::operator=(const Student& stu){ //返回值类型中使用引用& 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 Name=stu.Name; 5 Age=stu.Age; 6 Gender=stu.Gender; 7 } 8 return *this; 9 } 10 11 int main(){ 12 Student stu("Tomwenxing",23,"male); 13 Student stu2; 14 stu2=stu; 15 return 0; 16 }
1 Student Student::operator=(const Student& stu){ //返回值类型中没有使用引用& 2 cout<<"调用类中的赋值函数"<<endl; 3 if(this!=&stu){ 4 Name=stu.Name; 5 Age=stu.Age; 6 Gender=stu.Gender; 7 } 8 return *this; 9 } 10 11 int main(){ 12 Student stu("Tomwenxing",23,"male"); 13 Student stu2; 14 stu2=stu; 15 return 0; 16 }
Question 4:语句“if(this!=&stu)”的作用是什么?
Answer:通过比较赋值者和被赋值者的地址是否相同来判断两者是否是同一个对象,从而避免自赋值(即自己给自己赋值)的情况的发生,原因如下:
• 为了提高程序的运行效率。自己给自己赋值是完全无意义的行为,只会浪费程序的时间资源。
• 当类中的数据成员中含有指针时,自赋值操作可能会带来灾难性的后果。例如假设对象a和b中都含有一个指针ptr,它们分别指向一块通过new动态开辟的内存空间A和内存空间B,当将对象a赋值给对象b时,要求先将对象b中指针ptr指向的内存空间B通过delete释放掉(否则将造成内存泄漏),然后在内存中重新开辟内存空间C以拷贝内存空间A中的内容,并让对象b的指针ptr重新指向内存空间C,从而完成赋值。如果此时允许对象的自赋值,那么对象会在自赋值前先释放自己指针所指的内存空间,然后重新在内存中开辟空间,然而由于之前的内存空间已经释放,此时新内存空间希望拷贝的内容将不复存在,因而造成灾难性后果。
因此,对于类中的赋值函数,一定要先检查是否是自赋值,如果是,直接return *this。
Question 5:自定义类的对象之间的赋值操作的本质是什么?
Answer:本质是调用类中的成员函数(operator=()函数)。如:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 //cout<<"调用默认构造函数"<<endl; 8 }; 9 Student(string name,int age,string gender):Name(name),Age(age),Gender(gender){ 10 //cout<<"调用构造函数1"<<endl; 11 } 12 Student(const Student& stu){//拷贝构造函数 13 Name=stu.Name; 14 Age=stu.Age; 15 Gender=stu.Gender; 16 cout<<"调用拷贝构造函数"<<endl; 17 } 18 ~Student(){ 19 //cout<<"调用析构函数"<<endl; 20 } 21 Student& operator=(const Student& stu){ //赋值函数 22 cout<<"调用类中的赋值函数"<<endl; 23 if(this!=&stu){ 24 Name=stu.Name; 25 Age=stu.Age; 26 Gender=stu.Gender; 27 } 28 return *this; 29 } 30 void show(){ 31 cout<<" Name:"<<Name<<endl; 32 cout<<" Age:"<<Age<<endl; 33 cout<<" Gender:"<<Gender<<endl; 34 } 35 private: 36 string Name; 37 int Age; 38 string Gender; 39 }; 40 41 int main(){ 42 Student stu("Tomwenxing",23,"male"); 43 Student stu2,stu3; 44 stu2=stu;//对stu2进行赋值 45 cout<<"对象stu2:"<<endl; 46 stu2.show(); 47 cout<<"------------分界线-------------------"<<endl; 48 stu3.operator=(stu);//对stu3进行赋值 49 cout<<"对象stu3:"<<endl; 50 stu3.show(); 51 return 0; 52 }