這是一個,在寫析構函數的時候想系統理一下知識點,然後亂入了多態訪問控制(在另一篇筆記裏懶得發了),最後又亂入了基類派生類對象互相攪和的故事……
借鑑了兩篇優秀博文
https://blog.csdn.net/github_35160620/article/details/52602332
https://blog.csdn.net/starlee/article/details/619827
構造函數 析構函數 的調用順序
構造函數
基類構造函數、派生類對象成員構造函數、派生類本身的構造函數
析構函數
派生類本身的析構函數、派生類對象成員析構函數、基類析構函數(與構造順序正好相反)
不同對象何時析構
局部對象,在退出程序塊時析構
靜態對象,在定義所在文件結束時析構
全局對象,在程序結束時析構
繼承對象,先析構派生類,再析構父類
對象成員,先析構類對象,再析構對象成員
析構函數
通常情況下,系統自動生成的析構函數已足夠使用。但若有需要自己編寫析構函數,則一定需要同時編寫拷貝構造函數和重載賦值運算符,這便是三法則。下面分情況討論:
指針成員
//常規寫法
class NoName{
public:
NoName():pstring(new std::string), i(0), d(0){
cout << "構造函數被調用了!" << endl;
}
~NoName(){
cout << "析構函數被調用了!" << endl;
}
private:
std::string * pstring;
int i;
double d;
};
- 指針成員構造時使用new,擁有額外的資源,則需要手動釋放指針指向的內存。
NoName::~NoName(){
delete pstring;
cout << "析構函數被調用了!" << endl;
}
- 若使用了系統合成的拷貝構造函數,拷貝過去的也只是指針(淺拷貝),則兩個對象的指針成員指向同一塊內存,析構兩個對象時,這塊內存會被釋放兩次,因此需要手動編寫拷貝構造函數。
//編寫拷貝構造函數
NoName::NoName(const NoName & other){
pstring = new std::string; //開闢新的內存
*pstring = *(other.pstring);
i = other.i;
d = other.d;
}
- 賦值操作符同理,若不重載=,對於指針成員,c++自己的=只對指針進行簡單的值拷貝,指向同一段內存,析構時則出現問題
NoName& NoName::operator=(const NoName &rhs){
pstring = new std::string; //開闢新的內存
*pstring = *(other.pstring);
i = other.i;
d = other.d;
return *this;
}
虛析構函數
牢記準則:基類的析構函數需要寫成虛函數
class Base {
public:
Base() {};
virtual ~Base() {};
}
class Derived:public Base {
public:
Derived() {};
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
}
int main() {
Base *b = new Derived;
delete b;
}
若Base的析構函數沒有被定義爲virtual,對於基類指針指向一個派生類對象的情況,派生類的析構函數不會被調用,則會造成內存泄漏。
- 並不需要把所有的析構函數定義爲virtual,因爲當類含有虛函數時,編譯器會給類添加一個虛函數表,用來存放虛函數指針,會增加類的存儲空間。
基類與派生類對象之間的賦值兼容關係
- 可以將基類指針指向其公有派生類的對象
- 不可以將基類指針指向其私有派生類的對象
- 不可以將派生類指針指向基類對象
基類指針指向派生類對象的使用規則
class Base{ //聲明基類
public:
int i;
...
};
class Derived:public Base{ //聲明基類Base的公有派生類Derived
...
};
在Base的對象可以使用的任何地方,都可以用Derived的對象來代替,但只能使用從基類繼承來的成員
- 派生類對象可以向基類對象賦值,即用派生類對象中從基類繼承來的數據成員,逐個賦值給基類對象的數據成員
Base b;
Derived d;
b=d;
- 派生類對象可以初始化基類對象的引用
Derived d;
Base &br=d; //Base的對象的引用br,並用Derived的對象對其進行初始化
- 派生類對象地址可以賦值給指向基類對象的指針
Derived d;
Base *bp=&d;
- 如果函數的形參是基類對象或基類對象的引用,在調用函數時可以將派生類對象作爲實參
void fun(Base &bb)
{
cout << bb.i << endl; //輸出該引用所代表的對象的數據成員i
}
Derived d;
fun(d);