構造函數和析構函數
對象的初始化和清理是兩個非常重要的安全問題,一個對象沒有初始化狀態,對其使用後果是未知的。同樣使用完一個變量或對象,沒有及時清理,也會造成一定的安全問題,如內存泄漏。
C++利用構造函數和析構函數解決上述的問題,這兩個函數在類的對象中會被編譯器自動調用,完成對象初始化和清理工作。
- 構造函數:主要用於在創建對象時爲對象的成員屬性賦值,默認的構造函數由編譯器自動調用,且是空實現。
- 析構函數:主要用於進行清理工作,工作於對象銷燬前,,默認的析構函數由編譯器自動調用,且是空實現。
構造函數語法:類名(){}
- 沒有返回值,也不寫void;
- 函數名稱於與類名相同;
- 構造函數可以有參數,因此可以發生重載;
- 程序在調用對象時候會自動調用構造函數,且只調用一次;
析構函數語法:~類名(){}
- 沒有返回值,也不寫void;
- 函數名稱於與類名相同,且在名稱前加~;
- 析構函數沒有參數,因此不可以發生重載;
- 程序在銷燬對象時候會自動調用析構函數,且只調用一次;
構造函數的分類和調用
兩種分類方式:
按參數分爲:有參構造函數和無參構造函數(默認構造函數)
按類型分爲:普通構造函數和複製構造函數
複製構造函數的作用是將一個對象的所有屬性值複製一遍到另一個對象,參數是一個對象類型的實例。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
int age;
string name;
Person() {
cout << "Person無參數的構造函數" << endl;
}
Person(int a, string b) {
age = a;
name = b;
cout << "Person有參數的構造函數" << endl;
}
Person(const Person &a) {
age = a.age;
name = a.name;
cout << "Person的複製構造函數" << endl;
}
~Person() {
cout << "Person的析構函數" << endl;
}
};
int main() {
Person A;//調用默認的構造函數
Person B(18,"wl");//調用有參數的構造函數
Person C = B;//調用複製構造函數
return 1;
}
Person無參數的構造函數
Person有參數的構造函數
Person的複製構造函數
Person的析構函數
Person的析構函數
Person的析構函數
構造函數的調用有三種方式:
1、括號法
2、顯示法
3、隱式轉換法
//括號法
Person A; //調用默認的構造函數
Person B(18, "QQ");//調用有參數的構造函數
Person C(B);//調用複製構造函數
注意事項:調用默認構造函數不用加(),例如:Person D(); 雖然不會報錯,但編譯器會認爲這是一個函數聲明,不會認爲是在創建對象。
//顯示法
Person D; //調用默認的構造函數
Person E = Person(18, "QQ"); //調用有參數的構造函數
Person F = Person(E);//調用複製構造函數
Person(18, “QQ”)單獨拿出來叫匿名對象,特點:當前行執行結束後,系統會立即回收匿名對象。
注意事項:不要利用複製構造函數初始化匿名對象,編譯器會認爲Person(F) === Person F;,即對象的聲明,從而造成重定義。
//隱式轉換法
Person F = E;//調用複製構造函數
複製構造函數調用時機
在C++中,複製構造函數調用通常有三種情況:
- 使用一個已經創建的對象來初始化一個新對象;
- 值傳遞的方式給函數參數傳值;
- 以值方式返回局部對象;
//第一種
Person A(18, "QQ");
Person B = A;
void Pr(Person a) {
cout << a.name << "的年齡爲:" << (int)a.age << endl;
}
//第二種
Person A(18,"QQ");
Pr(A);
Person Pr() {
Person A;
return A;
}
//第三種
Pr();
構造函數調用規則
默認情況下,C++編譯器至少給一個類添加了三個函數。
- 默認構造函數(無參,函數體爲空)
- 默認析構函數(無參,函數體爲空)
- 默認複製構造函數(有參,對默認值進行拷貝)
構造函數調用規則如下:
- 如果用戶定義有參構造函數,C++不在提供默認的構造函數,但是會提供默認複製構造函數。
- 如果用戶定義複製構造函數,C++不會再提供其他構造函數。
Person A(18,"QQ");
Person B = A;
Person有參數的構造函數
Person的複製構造函數
Person的析構函數
Person的析構函數
深拷貝與淺拷貝
淺拷貝:簡單的賦值拷貝操作
深拷貝:在堆區重新申請空間,進行拷貝操作
編譯器默認的賦值構造函數屬於淺拷貝
下面的代碼在進行復制構造函數調用時,出現錯誤。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
int age;
string *name;
Person() {
cout << "Person無參數的構造函數" << endl;
}
Person(int a,string b) {
age = a;
name = new string(b);
cout << "Person有參數的構造函數" << endl;
}
~Person() {
if (name != NULL) {
delete name;
name = NULL;
}
cout << "Person的析構函數" << endl;
}
};
int main() {
Person A(18,"QQ");
cout << *A.name << "的年齡爲:" << A.age << endl;
Person B = A;
cout << *B.name <<"的年齡爲:" << B.age << endl;
return 1;
}
報錯的原因是,指針屬性name所存放的地址是一樣的,但在第一次析構函數中,就已經使用delete語句釋放了改內存空間,故而第二次delete,就會報錯,造成內存重複釋放。
解決辦法:將默認複製構造函數的淺拷貝改爲深拷貝。
Person類自定義複製構造函數
Person(const Person &a) {
age = a.age;
name = new string(*a.name);
cout << "Person的複製構造函數" << endl;
}
主函數測試:
Person A(18,"QQ");
cout << *A.name << "的年齡爲:" << A.age << endl;
Person B = A;
cout << *B.name <<"的年齡爲:" << B.age << endl;
Person有參數的構造函數
QQ的年齡爲:18
Person的複製構造函數
QQ的年齡爲:18
Person的析構函數
Person的析構函數
重寫複製構造函數,使得複製的指針變量name所指的內存地址不同,從而避免最後重複釋放。
初始化列表
C++提供了初始化語法,用來初始化屬性。
語法:構造函數():屬性1(值1),屬性2(值2),屬性3(值3)…{}
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
int age;
int height;
string name;
Person():age(18),height(180),name("QQ") {
cout << "Person無參數的構造函數" << endl;
}
Person(int a,int b,string c) :age(a), height(b), name(c) {
cout << "Person有參數的構造函數" << endl;
}
};
int main() {
Person A;
cout << A.name << "的年齡爲:" << A.age << ",身高爲:" << A.height << endl;
Person B(22,175,"WL");
cout << B.name << "的年齡爲:" << B.age << ",身高爲:" << B.height << endl;
return 1;
}
Person無參數的構造函數
QQ的年齡爲:18,身高爲:180
Person有參數的構造函數
WL的年齡爲:22,身高爲:175
類對象作爲類成員
C++類中的成員可以是另一個類的對象,我們稱該成員爲對象成員。下面代碼中,類Person中有一個成員爲Phone的對象。
問題:先調用Person的構造函數還是Phone的調用函數?先調用Person的析構函數還是Phone的析構函數?
#include <iostream>
#include <string>
using namespace std;
class Phone {
public:
string label; //手機品牌類型
Phone(string a) {
cout << "Phone的構造函數" << endl;
label = a;
}
~Phone() {
cout << "Phone的析構函數" << endl;
}
};
class Person {
public:
string name; //姓名
Phone phone; //手機類型
//Phone phone = b 隱式轉換法
Person(string a, string b) :name(a), phone(b) {
cout << "Person的構造函數"<<endl;
}
~Person() {
cout<<"Person的析構函數" << endl;
}
};
答案:當其他類對象作爲本類的成員,構造先調用其他類對象,再調用本類構造函數。析構函數調用順序與構造函數相反。
Person a("張三", "小米10");
cout << a.name <<"的手機類型爲:"<< a.phone.label<< endl;
Phone的構造函數
Person的構造函數
張三的手機類型爲:小米10
Person的析構函數
Phone的析構函數