欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++踩坑實戰(zhàn)之構(gòu)造和析構(gòu)函數(shù)

 更新時間:2021年07月26日 11:54:33   作者:Just for Life.  
不論是構(gòu)造函數(shù),還是析構(gòu)函數(shù),都是C++、C#語言相對于其他語言而言特殊的地方,它是為了方便類中對象的初始化,這篇文章主要給大家介紹了關(guān)于C++踩坑實戰(zhàn)之構(gòu)造和析構(gòu)函數(shù)的相關(guān)資料,需要的朋友可以參考下

前言

我是練習(xí)時長一年的 C++ 個人練習(xí)生,喜歡野指針、模板報錯和未定義行為(undefined behavior)。之前在寫設(shè)計模式的『工廠模式』時,一腳踩到了構(gòu)造、繼承和 new 組合起來的坑,現(xiàn)在也有時間來整理一下了。

構(gòu)造函數(shù)

眾所周知:在創(chuàng)建對象時,防止有些成員沒有被初始化導(dǎo)致不必要的錯誤,在創(chuàng)建對象的時候自動調(diào)用構(gòu)造函數(shù)(無聲明類型),完成成員的初始化。即:

Class c // 隱式,默認構(gòu)造函數(shù)
Class c = Class() // 顯示,默認構(gòu)造函數(shù)
Class c = Class("name") // 顯示,非默認構(gòu)造函數(shù) 
Class* c = new Class // 隱式,默認構(gòu)造函數(shù)
  • 構(gòu)造函數(shù)執(zhí)行前,對象不存在
  • 構(gòu)造函數(shù)創(chuàng)建對象后,對象不能調(diào)用構(gòu)造函數(shù)
  • 類中如果不定義構(gòu)造函數(shù),編譯器提供有默認的構(gòu)造函數(shù),無參數(shù),也不執(zhí)行任何額外的語句
  • 如果提供非默認構(gòu)造函數(shù),沒有默認構(gòu)造函數(shù)將會出錯。所以要定義一個不接受任何參數(shù)的構(gòu)造函數(shù),并為成員定義合理的值
  • 一般而言,默認的構(gòu)造函數(shù)是用來對所有類成員做隱式初始化的
  • 自己定義的構(gòu)造函數(shù)一般用使用列表初始化來初始化參數(shù)
  • 通過構(gòu)造函數(shù)對成員賦值,要優(yōu)于通過函數(shù)為成員賦值
using namespace std;

class Stone {
private:
    int weight{0};
    double radius{0.0};
public:
    Stone() {
        cout << "Class Stone was created by default creator" << endl;
    };
    Stone(int w, double r) : weight{w}, radius{r} {
        cout << "Class Stone was created by custom creator" << endl;
    }
    void showInfo() {
        cout << "Weight: " << this->weight << ", Radius: " 
             << this->radius << endl;
    }
};

int main (){
    // 隱式,成員有默認值
    Stone s1;
    s1.showInfo();
    // 顯式,通過列表初始化,為成員賦值
    Stone s2 = Stone(12, 3.3);
    s2.showInfo();
    return 0;
}

通過構(gòu)造函數(shù)實現(xiàn)的類型轉(zhuǎn)換

觀察以下的代碼,我們發(fā)現(xiàn) Stone s2;s2 = 3.3; 這樣將一個 double 類型的數(shù)據(jù)賦值給類類型并沒有出錯,這是隱式類型轉(zhuǎn)換,從參數(shù)類型到類類型。

using namespace std;

class Stone {
private:
    int weight{0};
    double radius{0.0};
public:
    Stone() {
        cout << this << endl;
        cout << "Class Stone was created by default creator" << endl;
    };
    // 都關(guān)閉
    Stone(double r) : radius{r} {
        cout << this << endl;
        cout << "Class Stone was created by parameter radius" << endl;
    }
    Stone(int w) : weight{w} {
        cout << this << endl;
        cout << "Class Stone was created by parameter weight" << endl;
    }
    void showInfo() {
        cout << "Weight: " << this->weight << ", Radius: " 
             << this->radius << endl;
    }
};

int main (){
    Stone s2;
    s2 = 3.3;
    s2.showInfo();
    return 0;
}

這是因為:接受一個參數(shù)的構(gòu)造函數(shù)允許使用賦值語法來為對象賦值。s2=3.3 會創(chuàng)建 Stock(double) 臨時對象,臨時對象初始化后,逐成員賦值的方式復(fù)制到對象中,在幾個構(gòu)造函數(shù)中加入了 cout << this 的語句,由對象的地址不同,可以判斷該賦值語句額外生成了臨時對象。

為了防止隱式轉(zhuǎn)換帶來的危險,可以使用關(guān)鍵字 explicit 關(guān)閉這一特性,這樣就得顯式完成參數(shù)類型到類類型的轉(zhuǎn)換:s = Stock(1.3);不過,得保證沒有二義性。

using namespace std;

class Stone {
private:
    int weight{0};
    double radius{0.0};
public:
    Stone() {
        cout << this << endl;
        cout << "Class Stone was created by default creator" << endl;
    };
    // 都關(guān)閉
    explicit Stone(double r) : radius{r} {
        cout << this << endl;
        cout << "Class Stone was created by parameter radius" << endl;
    }
    explicit Stone(int w) : weight{w} {
        cout << this << endl;
        cout << "Class Stone was created by parameter weight" << endl;
    }
    void showInfo() {
        cout << "Weight: " << this->weight << ", Radius: " 
             << this->radius << endl;
    }
};

int main (){
    Stone s2;
    s2 = Stone(3);
    s2.showInfo();
    return 0;
}

上述代碼中,如果 Stone(int w) 沒有被關(guān)閉,那么 s2=3.3 將調(diào)用這一構(gòu)造函數(shù)。所以構(gòu)造函數(shù)建議都加上 explicit 聲明。

派生類的構(gòu)造函數(shù)

派生類要注意的是:派生類被構(gòu)造之前,通過調(diào)用一個基類的構(gòu)造函數(shù),創(chuàng)建基類完成基類數(shù)據(jù)成員的初始化;也就是說,基類對象在程序進入派生類構(gòu)造函數(shù)之前被創(chuàng)建。那么,可以通過初始化列表傳遞給基類參數(shù),不傳遞的話,調(diào)用基類的默認的構(gòu)造函數(shù),如下述程序中的:Gem(){}:Stone()。

using namespace std;

class Stone {
private:
    int weight{0};
    double radius{0.0};
public:
    Stone() {
        cout << "This object was in address: " << this << endl;
    };
    Stone(int w, double r) : weight{2}, radius{r} {};
    void showInfo() {
        cout << "Weight: " << this->weight << ", Radius: " << this->radius;
    }
    int getWeight(){
        return this->weight;
    }
    auto getRadius() -> double {
        return this->radius;
    }
};

class Gem : public Stone {
private:
    double price;
public:
    Gem(){};
    Gem(double p, int w, double r) : Stone(w, r), price{p} {};
    void show() {
        cout << "Weight: " << this->getWeight() << ", Radius" 
             << this->getRadius();
    }
};

int main (){
    Gem g1; // call default
    Gem g2 = Gem(1300, 1, 2.3); // call custom 
    // g.setWeight(130);
    g2.show();
    return 0;
}

  • 首先創(chuàng)建基類對象
  • 派生類通過初始化列表(只能用在構(gòu)造函數(shù))將基類信息傳遞給基類的構(gòu)造函數(shù)
  • 派生類構(gòu)造函數(shù)可以為派生類初始化新的成員

析構(gòu)函數(shù)

對象過期時,程序會調(diào)用對象的析構(gòu)函數(shù)完成一些清理工作,如釋放變量開辟的空間等。如構(gòu)造函數(shù)使用了 new 來申請空間,析構(gòu)就需要 delete 來釋放空間。如果沒有特別聲明析構(gòu)函數(shù),編譯器會為類提供默認的析構(gòu)函數(shù),在對象作用域到期、被刪除時自動被調(diào)用。

如 stock1 = Stock(),這種就申請了一個臨時變量,變量消失時會調(diào)用析構(gòu)函數(shù)。此外,這種局部變量放在棧區(qū),先入后出,也就是,最后被申請的變量最先被釋放。

using namespace std;

class Stone {
private:
    int weight{0};
    double radius{0.0};
public:
    Stone() {
        cout << "This object was in address: " << this << endl;
    };
    ~Stone() {
        cout << this << " Object was deleted." << endl;
    }
};

int main (){
    {
        Stone s1;
        Stone s2;
    }
    return 0;
}

繼承中的析構(gòu)函數(shù)

繼承類比較容易理解,畢竟都學(xué)過面向?qū)ο?。公有繼承的時候,基類的公有成員也是派生類的共有成員;私有成員也是派生類的一部分,不過需要共有或保護方法來訪問。但是但是但是,派生類和基類的析構(gòu)函數(shù)之間,也是一個坑。在繼承中:

  • 如果一個方法不是虛方法,那么將根據(jù)引用類型或指針類型選擇執(zhí)行的方法
  • 如果一個方法是虛方法,將根據(jù)指針或引用指向?qū)ο蟮念愋瓦x擇執(zhí)行的方法

在繼承中,對象的銷毀順序和創(chuàng)建相反。創(chuàng)建時先創(chuàng)建基類,而后創(chuàng)建子類;銷毀時,先調(diào)用子類的析構(gòu)函數(shù),而后自動調(diào)用基類的析構(gòu)函數(shù)。因此,對于基類而言,建議將析構(gòu)函數(shù)寫成虛方法。如果析構(gòu)不是虛方法,對于以下情況,只有基類的析構(gòu)被調(diào)用;如果析構(gòu)是虛方法,子類、基類的析構(gòu)方法都被調(diào)用??梢試L試刪除下述代碼的 virtual 來觀察結(jié)果:

using namespace std;

class Stone {
private:
    int weight{0};
    double radius{0.0};
public:
    Stone() {
        cout << "This object was in address: " << this << endl;
    };
    Stone(int w, double r) : weight{2}, radius{r} {};
    void showInfo() {
        cout << "Weight: " << this->weight << ", Radius: " 
             << this->radius;
    }
    int getWeight(){
        return this->weight;
    }
    auto getRadius() -> double {
        return this->radius;
    }
    virtual ~Stone() {
        cout << "Stone class was deleted." << endl;
    }
};

class Gem : public Stone {
private:
    double price;
public:
    Gem() {};
    Gem(double p, int w, double r) : Stone(w, r), price{p} {};
    void show() {
        cout << "Weight: " << this->getWeight() << ", Radius" 
             << this->getRadius();
    }
    ~Gem() {
        cout << "Gem class was deleted." << endl;
    }
};

int main (){
    Stone* s1 = new Gem(2.3, 2, 3.2);
    delete s1;
    // Gem* g1 = new Gem(2.3, 2, 1.2);
    // delete g1;
    return 0;
}

應(yīng)用

大概常見的坑在上面都記錄好了,來看一段我寫的危險的程序(我大概抽象了一下),覆蓋了:野指針和為定義行為:

using namespace std;

class A {
private:
    int* a;
public:
    int* create() {
        a = new int();
        return a;
    }
    ~A(){
        delete a;
    }
};

int main () {
    A a;
    int* b = a.create();
    delete b;
    return 0;
}

  1. 每次調(diào)用 create 都會 new 一次,但只 delete 了一次。
  2. 如果沒有調(diào)用 create 直接析構(gòu),未定義行為
  3. 如果 b 持有了 a.create() 的指針,然后 a 提前析構(gòu),那么 b 是野指針
  4. delete b 是沒必要的。這樣會 double free,也是未定義行為
  5. 上述代碼沒有區(qū)分類里面 new 且 返回的東西要在哪刪除合適
  6. 可以讓類來管理這一個 new,修改一下 create 的實現(xiàn)或者干脆在構(gòu)造 new,在析構(gòu) delete

總結(jié)

到此這篇關(guān)于C++踩坑實戰(zhàn)之構(gòu)造和析構(gòu)函數(shù)的文章就介紹到這了,更多相關(guān)C++構(gòu)造和析構(gòu)函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++實現(xiàn)掃雷、排雷小游戲

    C++實現(xiàn)掃雷、排雷小游戲

    這篇文章主要為大家詳細介紹了C++實現(xiàn)掃雷、排雷小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • Matlab繪制花里胡哨的山脊圖

    Matlab繪制花里胡哨的山脊圖

    這篇文章主要介紹了如何利用Matlab實現(xiàn)繪制一些花里胡哨的山脊圖,文中的示例代碼講解詳細,對我們學(xué)習(xí)Matlab有一定的幫助,需要的可以參考一下
    2023-02-02
  • C++生成格式化的標準字符串實例代碼

    C++生成格式化的標準字符串實例代碼

    這篇文章主要給大家介紹了關(guān)于C++生成格式化的標準字符串的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 基于Qt實現(xiàn)一個圖片對比選擇瀏覽器

    基于Qt實現(xiàn)一個圖片對比選擇瀏覽器

    這篇文章主要為大家詳細介紹了如何基于Qt實現(xiàn)一個圖片對比選擇瀏覽器,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • 區(qū)分c++中的聲明與定義

    區(qū)分c++中的聲明與定義

    這篇文章主要介紹了如何區(qū)分c++中的聲明與定義,幫助大家更好的理解和學(xué)習(xí)c++,感興趣的朋友可以了解下
    2020-08-08
  • C語言實現(xiàn)2048小游戲

    C語言實現(xiàn)2048小游戲

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)2048小游戲,注釋清晰,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • 淺談C語言中的注釋風(fēng)格小結(jié)

    淺談C語言中的注釋風(fēng)格小結(jié)

    今天小編就為大家分享一篇淺談C語言中的注釋風(fēng)格小結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-12-12
  • C語言中atoi函數(shù)模擬實現(xiàn)詳析

    C語言中atoi函數(shù)模擬實現(xiàn)詳析

    atoi函數(shù)功能是將數(shù)字字符串轉(zhuǎn)換為整數(shù),比如數(shù)字字符串"12345"被atoi轉(zhuǎn)換為12345,數(shù)字字符串"-12345"被轉(zhuǎn)換為-12345,下面這篇文章主要給大家介紹了關(guān)于C語言中atoi函數(shù)模擬實現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • C++實現(xiàn)有向圖的鄰接表表示

    C++實現(xiàn)有向圖的鄰接表表示

    這篇文章主要為大家詳細介紹了C++實現(xiàn)有向圖的鄰接表表示,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C語言數(shù)據(jù)的存儲超詳細講解中篇練習(xí)

    C語言數(shù)據(jù)的存儲超詳細講解中篇練習(xí)

    使用編程語言進行編程時,需要用到各種變量來存儲各種信息。變量保留的是它所存儲的值的內(nèi)存位置。這意味著,當您創(chuàng)建一個變量時,就會在內(nèi)存中保留一些空間。您可能需要存儲各種數(shù)據(jù)類型的信息,操作系統(tǒng)會根據(jù)變量的數(shù)據(jù)類型,來分配內(nèi)存和決定在保留內(nèi)存中存儲什么
    2022-04-04

最新評論