溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++語言(03)——對象的構(gòu)造

發(fā)布時間:2020-07-29 11:58:30 來源:網(wǎng)絡(luò) 閱讀:428 作者:三九感冒靈 欄目:編程語言

對象的構(gòu)造(上)

成員變量的初始值

(1)從程序設(shè)計的角度來看,對象只是變量,定義對象就是定義變量,所以:
在棧上創(chuàng)建對象時,成員變量初始值為隨機值
在堆上創(chuàng)建對象時,成員變量初始值為隨機值
在靜態(tài)數(shù)據(jù)區(qū)上創(chuàng)建對象時,成員變量初始值為0
(2)全局變量和static修飾的局部變量存儲在靜態(tài)數(shù)據(jù)區(qū),沒有顯式初始化其值為0(bss/ZI段)


/**
從程序設(shè)計的角度來看,對象只是變量,定義對象就是定義變量,所以:
    在棧上創(chuàng)建對象時,成員變量初始值為隨機值
    在堆上創(chuàng)建對象時,成員變量初始值為隨機值
    在靜態(tài)數(shù)據(jù)區(qū)上創(chuàng)建對象時,成員變量初始值為0
**/
#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    Test t1;

    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());

    Test* pt = new Test;

    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());

    delete pt;

    return 0;
}

對象的初始化

(1)生活中的對象都是初始化之后上市的,如手機,電腦等,我們希望程序中的對象也可以初始化為固定值

initialize函數(shù)

(1)在類中提供一個public的initialize函數(shù)
(2)函數(shù)中手工對類的成員進行顯式初始化
(3)對象創(chuàng)建后需要立即調(diào)用initialize函數(shù)進行初始化
(4)initialize函數(shù)只是一個普通函數(shù),如果未及時調(diào)用,運行結(jié)果是不確定的

/**
對象創(chuàng)建后需要立即調(diào)用initialize函數(shù)進行初始化
initialize函數(shù)只是一個普通函數(shù),如果未及時調(diào)用,運行結(jié)果是不確定的
**/
#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    void initialize()
    {
        i = 1;
        j = 2;
    }
};

Test gt;

int main()
{
    gt.initialize();

    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    Test t1;

    //t1.initialize();

    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());

    t1.initialize();

    Test* pt = new Test;

    pt->initialize();

    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());

    delete pt;

    return 0;
}

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

(1)C++中可以定義與類同名的構(gòu)造函數(shù)
(2)構(gòu)造函數(shù):與類同名、沒有任何的返回值類型、在對象定義時會被自動調(diào)用

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    Test()  //構(gòu)造函數(shù):與類同名、沒有任何的返回值類型、在對象定義時會被自動調(diào)用
    {
        printf("Test() Begin\n");

        i = 1;
        j = 2;

        printf("Test() End\n");
    }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    Test t1;

    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());

    Test* pt = new Test;

    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());

    delete pt;

    return 0;
}

對象的構(gòu)造(中)

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

(1)構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù)
(2)一個類中可以存在多個重載的構(gòu)造函數(shù)構(gòu)造函數(shù)的重載遵循C++重載的規(guī)則

/**
(1)構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù)
(2)一個類中可以存在多個重載的構(gòu)造函數(shù)構(gòu)造函數(shù)的重載遵循C++重載的規(guī)則
**/
#include <stdio.h>

class Test
{
public:
    Test() 
    { 
        printf("Test()\n");
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);
    }
};

int main()
{
    Test t;      // 調(diào)用 Test()
    Test t1(1);  // 調(diào)用 Test(int v)
    //注意此處的Test t1(1),因為1是int類型數(shù)據(jù),所以是要告訴編譯器這個對象初始化時調(diào)用的構(gòu)造函數(shù)
    //參數(shù)為int而且只有一個參數(shù)

    Test t2 = 2; // 調(diào)用 Test(int v)

    //C++中支持這樣初始化
    int i(100);

    printf("i = %d\n", i);

    return 0;
}

(3)構(gòu)造函數(shù)在對象定義時會被自動調(diào)用,此外我們可以手工調(diào)用構(gòu)造函數(shù)

#include <stdio.h>

class Test
{
private:
    int m_value;
public:
    Test() 
    { 
        printf("Test()\n");

        m_value = 0;
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);

        m_value = v;
    }
    int getValue()
    {
        return m_value;
    }
};

int main()
{
    Test ta[3];
    //Test ta[3] = {Test(), Test(1), Test(2)};  //我們可以手工調(diào)用構(gòu)造函數(shù)

    for(int i=0; i<3; i++)
    {
        printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());
    }

/*  int i(100);     //C++中是可以這樣初始化的,等價于int i = 100;
    printf("i = %d.\n", i);

    Test t = Test(100);

    printf("t.getValue() = %d\n", t.getValue());
 */   
    return 0;
}

注意:
對象的聲明和定義不同,對象定義:申請對象的空間并調(diào)用構(gòu)造函數(shù)
對象聲明:告訴編譯器有這樣一個對象

對象的構(gòu)造(下)

特殊的構(gòu)造函數(shù)

無參構(gòu)造函數(shù):

(1)就是沒有參數(shù)的構(gòu)造函數(shù)
(2)當(dāng)類中沒有定義構(gòu)造函數(shù)時(拷貝構(gòu)造函數(shù)也是構(gòu)造函數(shù)),編譯器會默認提供一個無參構(gòu)造函數(shù),其函數(shù)體為空

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

(1)參數(shù)為const class_name&的構(gòu)造函數(shù)
(2)當(dāng)類中沒有定義拷貝構(gòu)造函數(shù)時,編譯器會默認提供一個拷貝構(gòu)造函數(shù),簡單的進行成員變量的復(fù)制(淺拷貝)
(3)拷貝構(gòu)造函數(shù)的意義:兼容C語言的初始化方式(使用變量為其他變量賦值),使用已創(chuàng)建的對象為其他對象賦值

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int *p;
public:
    int getI()
    {
        return i;
    }
    int getJ()
    {
        return j;
    }
    int * getP()
    {
        return p;
    }
    int getPP()
    {
        return *p;
    }
    /*Test(const Test& t)   //編譯器提供的默認拷貝構(gòu)造函數(shù)
    {
        i = t.i;
        j = t.j;
    }
    Test()                  //編譯器提供的默認無參構(gòu)造函數(shù)
    {                       //函數(shù)體為空
    }*/
     Test(int v)
    {
        i = 1;
        j = 2;
        p = new int;

        *p = v;
    }
    void free()
    {
        delete p;
    }
};

int main()
{
    Test t1(1);
    Test t2 = t1;       //默認進行淺拷貝

    printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
    printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t1.getP());

    //test
    printf("t1.*p = %d.\n", t1.getPP());
    printf("t2.*p = %d.\n", t2.getPP());
    //結(jié)果表明t1(1),把1作為參數(shù),傳給了構(gòu)造函數(shù)

    t1.free();
    //t2.free();    // double free or corruption (fasttop): 0x09441008

    return 0;
}

淺拷貝與深拷貝

(1)淺拷貝:拷貝后的物理狀態(tài)相同
(2)深拷貝:拷貝后的邏輯狀態(tài)相同
(3)編譯器默認提供的拷貝構(gòu)造函數(shù)只進行淺拷貝
什么時候需要深拷貝?
對象中有成員使用了系統(tǒng)資源(成員指向了動態(tài)內(nèi)存空間、成員打開了外存中的文件、成員使用了系統(tǒng)中的網(wǎng)絡(luò)端口)
注意:
(1)調(diào)用淺拷貝構(gòu)造函數(shù)進行初始化,初始化的兩個變量不但參數(shù)相同,而且共用同一塊內(nèi)存,在兩次釋放內(nèi)存時就會出錯
(2)工程中自定義拷貝構(gòu)造函數(shù)時,必然要實現(xiàn)深拷貝(為新的對象重新分配資源)

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int* p;
public:
    int getI()
    {
        return i;
    }
    int getJ()
    {
        return j;
    }
    int* getP()
    {
        return p;
    }
    Test(const Test& t)     //自定義構(gòu)造函數(shù),深拷貝
    {
        i = t.i;
        j = t.j;
        p = new int;        //必須有這一步,重新分配資源

        *p = *t.p;          //注意這里是*t.p
    }
    Test(int v)
    {
        i = 1;
        j = 2;
        p = new int;

        *p = v;
    }
    void free()
    {
        delete p;
    }
};

int main()
{
    Test t1(3);
    Test t2(t1);    //深拷貝,兩個對象有不同的內(nèi)存空間

    printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
    printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());

    t1.free();
    t2.free();

    return 0;
}

初始化列表的使用

類中的const成員

(1)在類中可以定義const成員,const成員會被分配空間,存儲位置取決于其對象定義在哪里
(2)類中的const是只讀變量
(3)在類中不能直接對const成員進行初始化,只能在初始化列表中指定初始值


//在類中不能直接對const成員進行初始化,只能在初始化列表中指定初始值
#include <stdio.h>

class Test
{
private:
    const int ci;       //只讀變量
public:
    Test() : ci(1)
    {
        // ci = 10;
    }
    int getCI() 
    { 
        return ci; 
    }
};

int main()
{
    Test t;

    printf("t.ci = %d\n", t.getCI());

    return 0;
}

初始化列表

(1)C++中提供了初始化列表對成員進行初始化
(2)語法規(guī)則
ClassName::ClassName() //構(gòu)造函數(shù)
: m1(v1),m2(v2, v3),m3(v1) //初始化列表
{
//構(gòu)造函數(shù)函數(shù)體
}
(3)注意事項:
成員的初始化順序與成員的聲明順序相同,與初始化列表中的順序無關(guān)
初始化列表先于構(gòu)造函數(shù)的函數(shù)體執(zhí)行

/*
C++中提供了初始化列表對成員進行初始化
成員的初始化順序與成員的聲明順序相同,與初始化列表中的順序無關(guān)
初始化列表先于構(gòu)造函數(shù)的函數(shù)體執(zhí)行
*/

#include <stdio.h>

class Value
{
private:
    int mi;
public:
    Value(int i)
    {
        printf("i = %d\n", i);
        mi = i;
    }
    int getI()
    {
        return mi;
    }
};

class Test
{
private:
    Value m2;
    Value m3;
    Value m1;
public:
    Test() : m1(1), m2(2), m3(3)    //成員的初始化順序與成員的聲明順序相同,與初始化列表中的順序無關(guān)
    {
        printf("Test::Test()\n");
    }
};

int main()
{
    Test t;     //初始化列表先于構(gòu)造函數(shù)的函數(shù)體執(zhí)行

    return 0;
}

對象的構(gòu)造順序

(1)局部變量的構(gòu)造順序依賴于程序的執(zhí)行流,所以開發(fā)中要避免使用goto語句)(破壞程序的執(zhí)行流)
(2)堆對象的構(gòu)造順序依賴于new的使用順序
(3)全局對象的構(gòu)造順序是不確定的,不同的編譯器使用不同的規(guī)則確定構(gòu)造順序,所以要盡量避免全局對象


//局部變量的構(gòu)造順序依賴于程序的執(zhí)行流,所以開發(fā)中要避免使用goto語句(破壞程序的執(zhí)行流)
#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(int i): %d\n", mi);
    }
    Test(const Test& obj)
    {
        mi = obj.mi;
        printf("Test(const Test& obj): %d\n", mi);
    }
    int getMi()
    {
        return mi;
    }
};

int main()
{
    int i = 0;
    Test a1 = i; // Test(int i): 0

    while( i < 3 )
    {
        Test a2 = ++i; // Test(int i): 1, 2, 3
    }
goto End;       
        Test a(100);    // crosses initialization of ‘Test a’編譯報錯,但是在vc10中編譯時ok的
End:
    printf("a.mi = %d\n", a.getMi());   //此處的訪問必然導(dǎo)致bug
    return 0;
}

對象的銷毀

(1)生活中對象都是初始化后才上市的,對象被銷毀前會做一些清理工作

程序中如何銷毀一個對象

方案1:
提供一個public的free函數(shù),
(1)當(dāng)對象不再需要時立即調(diào)用free函數(shù)進行清理
(2)free只是一個普通的函數(shù),必須顯示的調(diào)用
(3)對象銷毀之前沒有做清理,很可能造成資源泄漏
方案2:
析構(gòu)函數(shù)
(1)C++中可以定義一個特殊的清理函數(shù),析構(gòu)函數(shù),功能和構(gòu)造函數(shù)相反
(2)析構(gòu)函數(shù)在對象銷毀時被自動調(diào)用
(3)析構(gòu)函數(shù)沒有返回值也沒有參數(shù)(表明析構(gòu)函數(shù)在一個類中是唯一的,不可能重載)
(4)語法:
~ClassName()
(5)一般當(dāng)類中自定義了構(gòu)造函數(shù),并且函數(shù)中使用了系統(tǒng)資源,則需要定義析構(gòu)函數(shù),釋放系統(tǒng)資源,防止內(nèi)存泄漏

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(): %d\n", mi);
    }
    ~Test()
    {
        printf("~Test(): %d\n", mi);
    }
};

int main()
{
    Test t(1);

    Test* pt = new Test(2);

    delete pt;

    return 0;
}

神秘的臨時對象

(1)直接調(diào)用構(gòu)造函數(shù)將產(chǎn)生一個臨時對象,臨時對象的生命周期只有一條語句的時間,臨時對象的作用域只在一條語句中
(2)臨時對象是C++中值得警惕的灰色地帶,是性能的瓶頸,也是bug的來源之一

/**直接調(diào)用構(gòu)造函數(shù)將產(chǎn)生一個臨時對象,臨時對象的生命周期只有一條語句的時間,臨時對象的作用域只在一條語句中**/
#include <stdio.h>

class Test 
{
    int mi;
public:
    Test(int i) 
    {
        mi = i;
    }
    Test() 
    {
        Test(0);    //直接調(diào)用構(gòu)造函數(shù)將產(chǎn)生一個臨時對象,臨時對象的作用域只有一行代碼。
                    // 所以此處就相當(dāng)于空
    }
    void print() 
    {
        printf("mi = %d\n", mi);
    }
};

int main()
{
    Test t;

    t.print();

    return 0;
}

(3)實際工程開發(fā)中需要人為的避開臨時對象
(4)現(xiàn)代C++編譯器會盡力避開臨時對象
思考:如何解決構(gòu)造函數(shù)的代碼復(fù)用問題?
方案是提供一個private的init函數(shù),然后在構(gòu)造函數(shù)中去調(diào)用它.


/**
思考如何解決構(gòu)造函數(shù)的代碼復(fù)用問題?
    方案是提供一個private的init函數(shù),然后在構(gòu)造函數(shù)中去調(diào)用它
**/

#include <stdio.h>

class Test {
    int mi;

    void init(int i)    //提供一個private的init函數(shù),然后在構(gòu)造函數(shù)中去調(diào)用它
    {
        mi = i;
    }
public:
    Test(int i) {
        init(i);
    }
    Test() {
        init(0);
    }
    void print() {
        printf("mi = %d\n", mi);
    }
};

int main()
{
    Test t;

    t.print();

    return 0;
}

二階構(gòu)造模式

回顧構(gòu)造函數(shù)的特點:
與類同名,沒有返回值,在對象創(chuàng)建時被動調(diào)用,用于對象的初始化

關(guān)于構(gòu)造的幾個問題:

1、如何判斷構(gòu)造函數(shù)的執(zhí)行結(jié)果?
一般來說無法判斷。但是我們可以人為的類中定義一個用于表明構(gòu)造函數(shù)執(zhí)行結(jié)果的變量,并在構(gòu)造函數(shù)結(jié)束的地方給該變量賦值,最后通過讀取該變量的值來得知構(gòu)造函數(shù)的執(zhí)行結(jié)果
2、在構(gòu)造函數(shù)中執(zhí)行return語句會發(fā)生什么?
首先在構(gòu)造函數(shù)中指向他return是合法的,執(zhí)行return語句后構(gòu)造函數(shù)立即結(jié)束
3、構(gòu)造函數(shù)執(zhí)行結(jié)束是否意味著對象構(gòu)造成功?
構(gòu)造函數(shù)只提供自動初始化成員變量的機會,不能保證初始化邏輯一定成功。構(gòu)造函數(shù)決定的是對象的初始化狀態(tài),而不是對象的誕生。
也就是說構(gòu)造函數(shù)初始化操作的失敗不影響對象的誕生

半成品對象

初始化操作不能按照預(yù)期完成而得到的對象
是C++中的合法對象,也是bug的來源

#include <stdio.h>

class Test
{
    int mi;
    int mj;
    bool mStatus;
public:
    Test(int i, int j) : mStatus(false)
    {
        mi = i;

        //return;

        mj = j;

        mStatus = true;
    }
    int getI()
    {
        return mi;
    }
    int getJ()
    {
        return mj;
    }
    int status()
    {
        return mStatus;
    }
};

int main()
{  
    Test t1(1, 2);

    if( t1.status() )
    {
        printf("t1.mi = %d\n", t1.getI());
        printf("t1.mj = %d\n", t1.getJ());

    }

    return 0;
}

二階構(gòu)造

工程開發(fā)中的構(gòu)造過程可分為:
第一階段構(gòu)造:(真正的構(gòu)造函數(shù))
資源無關(guān)的初始化操作,不可能出現(xiàn)異常的操作
第二階段構(gòu)造:(返回值表示初始化狀態(tài)的普通函數(shù))
需要時用系統(tǒng)資源的操做,可能出現(xiàn)異常情況(內(nèi)存申請,訪問文件)
如圖所示:(27-2)
C++語言(03)——對象的構(gòu)造

#include <stdio.h>

class TwoPhaseCons 
{
private:
    TwoPhaseCons() // 第一階段構(gòu)造函數(shù)
    {   
    }
    bool construct() // 第二階段構(gòu)造函數(shù)(普通函數(shù),返回值表示系統(tǒng)資源初始化狀態(tài))
    { 
        return true; 
    }
public:
     TwoPhaseCons* NewInstance() // 對象創(chuàng)建函數(shù)
    {
        TwoPhaseCons* ret = new TwoPhaseCons();

        // 若第二階段構(gòu)造失敗,返回 NULL    
        if( !(ret && ret->construct()) ) 
        {
            delete ret;
            ret = NULL;
        }

        return ret;
    }
};

//TwoPhaseCons* TwoPhaseCons::NewInstance() 

int main()
{
    TwoPhaseCons* obj = TwoPhaseCons::NewInstance();

    printf("obj = %p\n", obj);

    delete obj;

    return 0;
}

總結(jié):
(1)二階構(gòu)造認為的將初始化分為兩部分,能夠確保創(chuàng)建的對象都是完整的
(2)二階構(gòu)造的構(gòu)造函數(shù)都是私有的,并提供了一個用于創(chuàng)建對象的靜態(tài)函數(shù)指針???(通過類名直接訪問,然后創(chuàng)建對象),所以最終的對象分配在堆區(qū)
(3)實際工程中需要初始化的數(shù)據(jù)都是比較多的,所以對象創(chuàng)建在堆區(qū)是合理的
使用二階構(gòu)造完善之前的數(shù)組類

對象的構(gòu)造順序

析構(gòu)函數(shù)的調(diào)用順序
析構(gòu)函數(shù)與對應(yīng)的構(gòu)造函數(shù)的調(diào)用順序相反,所以我們之只要知道構(gòu)造函數(shù)的調(diào)用順序就可以知道析構(gòu)的順序
(1)單個函數(shù)創(chuàng)建時構(gòu)造函數(shù)的調(diào)用順序(先父母,后他人,再自己)
1、調(diào)用父類的構(gòu)造過程
2、調(diào)用成員變量的構(gòu)造函數(shù)
3、調(diào)用類自身的構(gòu)造函數(shù)
(2)對于棧對象和全局對象,類似于入棧和出棧的順序,最先構(gòu)造的對象最后被析構(gòu)
(3)堆對象的析構(gòu)發(fā)生在使用delete的時候,與delete的使用順序相關(guān)

/**
析構(gòu)函數(shù)與對應(yīng)的構(gòu)造函數(shù)的調(diào)用順序相反,所以我們之只要知道構(gòu)造函數(shù)的調(diào)用順序就可以知道析構(gòu)的順序
(1)單個函數(shù)創(chuàng)建時構(gòu)造函數(shù)的調(diào)用順序
    1、調(diào)用父類的構(gòu)造過程
    2、調(diào)用成員變量的構(gòu)造函數(shù)
    3、調(diào)用類自身的構(gòu)造函數(shù)
**/
#include <stdio.h>

class Member
{
    const char* ms;
public:
    Member(const char* s)
    {
        printf("Member(const char* s): %s\n", s);

        ms = s;
    }
    ~Member()
    {
        printf("~Member(): %s\n", ms);
    }
};

class Test
{
    Member mA;      //調(diào)用成員變量的構(gòu)造函數(shù)
    Member mB;
public:
    Test() : mB("mB"), mA("mA")     //初始化列表對成員進行初始化,與對象的的構(gòu)造順序無關(guān)
    {
        printf("Test()\n");
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Member gA("gA");

int main()
{                                   
    Test t;     //調(diào)用類自身的構(gòu)造函數(shù)

            //對象的構(gòu)造函數(shù)掉順序:
            //gA, mA, mB, Test()
            //對象的析構(gòu)順序與構(gòu)造順序相反

    return 0;
}
向AI問一下細節(jié)
AI