c++ 析構函數 指針成員 虛析構函數 基類與派生類的賦值兼容(亂七八糟……)

這是一個,在寫析構函數的時候想系統理一下知識點,然後亂入了多態訪問控制(在另一篇筆記裏懶得發了),最後又亂入了基類派生類對象互相攪和的故事……
借鑑了兩篇優秀博文
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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章