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<<"\tName:"<<Name<<endl; 32 cout<<"\tAge:"<<Age<<endl; 33 cout<<"\tGender:"<<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 }