官术网_书友最值得收藏!

2.1 類(lèi)和對(duì)象

類(lèi)是面向?qū)ο蟪绦蛟O(shè)計(jì)的核心,它實(shí)際上是一種新的數(shù)據(jù)類(lèi)型,也是實(shí)現(xiàn)抽象類(lèi)型的工具,因?yàn)轭?lèi)是通過(guò)抽象數(shù)據(jù)類(lèi)型的方法來(lái)實(shí)現(xiàn)的一種數(shù)據(jù)類(lèi)型。類(lèi)是對(duì)某一類(lèi)對(duì)象的抽象;而對(duì)象是某一種類(lèi)的實(shí)例,因此,類(lèi)和對(duì)象是密切相關(guān)的。

2.1.1 類(lèi)的定義

類(lèi)的定義一般分為聲明部分和實(shí)現(xiàn)部分。聲明部分用來(lái)聲明該類(lèi)中的成員,包含數(shù)據(jù)成員(或稱(chēng)“成員變量”)的聲明和成員函數(shù)的聲明。成員函數(shù)是用來(lái)對(duì)數(shù)據(jù)成員進(jìn)行操作的,又稱(chēng)方法。實(shí)現(xiàn)部分用來(lái)對(duì)成員函數(shù)進(jìn)行定義。概括地說(shuō),聲明部分是告訴使用者“干什么”,而實(shí)現(xiàn)部分是告訴使用者“怎么干”。

C++中定義類(lèi)的一般格式如下:

class <類(lèi)名>
{
    private:
        [<私有型數(shù)據(jù)和函數(shù)>]
    public:
        [<公有型數(shù)據(jù)和函數(shù)>]
    protected:
        [<保護(hù)型數(shù)據(jù)和函數(shù)>]
};
<各個(gè)成員函數(shù)的實(shí)現(xiàn)>

其中,class是定義類(lèi)的關(guān)鍵字,class的后面是用戶(hù)定義的類(lèi)名(它通常用大寫(xiě)的C字母開(kāi)始的標(biāo)識(shí)符來(lái)描述,C表示Class,以與對(duì)象、函數(shù)及其他數(shù)據(jù)類(lèi)型名相區(qū)別)。類(lèi)中的數(shù)據(jù)和函數(shù)是類(lèi)的成員,分別稱(chēng)為數(shù)據(jù)成員和成員函數(shù)。由一對(duì)花括號(hào)構(gòu)成的是類(lèi)體。注意,類(lèi)體中最后一個(gè)花括號(hào)后面的分號(hào)“;”不能省略。

類(lèi)中的關(guān)鍵字public、private和protected聲明了類(lèi)中的成員與程序其他部分(或類(lèi)外)之間的關(guān)系,稱(chēng)為訪問(wèn)權(quán)限。對(duì)于public成員來(lái)說(shuō),它們是公有的,能被外面的程序訪問(wèn);對(duì)于private成員來(lái)說(shuō),它們是私有的,不能被外面的程序所訪問(wèn)。數(shù)據(jù)成員只能由類(lèi)中的函數(shù)所使用,成員函數(shù)只允許在類(lèi)中調(diào)用。而對(duì)于protected成員來(lái)說(shuō),它們是受保護(hù)的,具有半公開(kāi)性質(zhì),可在類(lèi)中或其子類(lèi)中訪問(wèn)(以后還會(huì)討論)。

<各個(gè)成員函數(shù)的實(shí)現(xiàn)>是類(lèi)定義中的實(shí)現(xiàn)部分,這部分包含所有在類(lèi)體中聲明的函數(shù)的定義(即對(duì)成員函數(shù)的實(shí)現(xiàn))。如果一個(gè)成員函數(shù)在類(lèi)體中定義,則其實(shí)現(xiàn)部分將不需要。如果所有的成員函數(shù)都在類(lèi)體中定義,則實(shí)現(xiàn)部分可以省略。需要說(shuō)明的是,當(dāng)類(lèi)的成員函數(shù)的函數(shù)體在類(lèi)的外部定義時(shí),必須由作用域運(yùn)算符“::”來(lái)通知編譯系統(tǒng)該函數(shù)所屬的類(lèi)。例如:

class  CMeter
{
public:
    double m_nPercent;                // 聲明一個(gè)公有數(shù)據(jù)成員
    void StepIt();                    // 聲明一個(gè)公有成員函數(shù)
    void SetPos(int nPos);            // 聲明一個(gè)公有成員函數(shù)
    int  GetPos()
    {
            return m_nPos;
    }                                // 聲明一個(gè)公有成員函數(shù)并定義
private:
    int  m_nPos;                     // 聲明一個(gè)私有數(shù)據(jù)成員
};                                   // 注意分號(hào)不能省略
void CMeter::StepIt()
{
    m_nPos++;
}
void CMeter::SetPos(int nPos)
{
    m_nPos = nPos;
}

類(lèi)CMeter中,成員函數(shù)GetPos是在類(lèi)體中定義的,而StepIt和SetPos是在類(lèi)的外部定義的,注意兩者的區(qū)別。另外,定義類(lèi)時(shí)還應(yīng)注意:

(1)類(lèi)中的數(shù)據(jù)成員的類(lèi)型可以是任意的,包含整型、浮點(diǎn)型、字符型、數(shù)組、指針和引用等,也可以是另一個(gè)類(lèi)的對(duì)象,但不允許對(duì)所定義的數(shù)據(jù)成員進(jìn)行初始化,也不能指定除static之外的任何存儲(chǔ)類(lèi)型。例如,類(lèi)CMeter中,下面的定義是錯(cuò)誤的:

class  CMeter
{   //…
private:
    int  m_nPos=10;           // 錯(cuò)誤:不能直接對(duì)數(shù)據(jù)成員進(jìn)行初始化
    auto int n;               // 錯(cuò)誤:不合法的存儲(chǔ)類(lèi)型
    //…
};

(2)在“public:”、“protected:”或“private:”后面定義的所有成員都是公有、保護(hù)或私有的,直到下一個(gè)“public:”、“protected:”或“private:”出現(xiàn)為止。若成員前面沒(méi)有任何訪問(wèn)權(quán)限的指定,則所定義的成員是private(私有),這是類(lèi)的默認(rèn)設(shè)置。事實(shí)上,結(jié)構(gòu)也可看成類(lèi)的一種簡(jiǎn)單形式,只是其成員的默認(rèn)訪問(wèn)權(quán)限是公有的。一般來(lái)說(shuō),當(dāng)只需要描述數(shù)據(jù)結(jié)構(gòu)而不想在結(jié)構(gòu)中進(jìn)行數(shù)據(jù)操作時(shí),則用結(jié)構(gòu)較好。而若既要描述數(shù)據(jù)又要描述對(duì)數(shù)據(jù)的處理方法時(shí),則用類(lèi)為好。

(3)關(guān)鍵字public、protected和private可以在類(lèi)中出現(xiàn)多次,且前后的順序沒(méi)有關(guān)系;但最好先聲明公有成員,后聲明私有成員,因?yàn)閜ublic成員是用戶(hù)最關(guān)心的。每個(gè)訪問(wèn)權(quán)限關(guān)鍵詞為類(lèi)成員所確定的訪問(wèn)權(quán)限是從該關(guān)鍵詞開(kāi)始到下一個(gè)關(guān)鍵詞為止的。

(4)在進(jìn)行類(lèi)設(shè)計(jì)時(shí),通常將數(shù)據(jù)成員聲明為私有的,而將大多數(shù)成員函數(shù)聲明成公有的。這樣,類(lèi)以外的代碼就不能直接訪問(wèn)類(lèi)的訪問(wèn)權(quán)限私有數(shù)據(jù),從而實(shí)現(xiàn)了數(shù)據(jù)的封裝。而公有成員函數(shù)可為內(nèi)部的私有數(shù)據(jù)成員提供外部接口,但接口實(shí)現(xiàn)的細(xì)節(jié)在類(lèi)外又是不可見(jiàn)的,這就是C++類(lèi)的優(yōu)點(diǎn)之一。

(5)盡量將類(lèi)單獨(dú)存放在一個(gè)文件中,或?qū)㈩?lèi)的聲明放在.h文件中,而將成員函數(shù)的實(shí)現(xiàn)放在與.h文件同名的.cpp文件中。以后將會(huì)看到,Visual C++ 6.0為用戶(hù)創(chuàng)建的應(yīng)用程序框架中都是將各個(gè)類(lèi)以.h和同名的.cpp文件組織的。

2.1.2 對(duì)象的定義

作為一種復(fù)雜的數(shù)據(jù)構(gòu)造類(lèi)型,類(lèi)聲明后,就可以定義該類(lèi)的對(duì)象。與結(jié)構(gòu)類(lèi)型一樣,它也有三種定義方式:聲明之后定義、聲明之時(shí)定義和一次性定義。但由于“類(lèi)”比任何數(shù)據(jù)類(lèi)型都要復(fù)雜得多,為了提高程序的可讀性,真正將“類(lèi)”當(dāng)成一個(gè)密閉、“封裝”的盒子(接口),在程序中應(yīng)盡量在對(duì)象的聲明之后定義方式,并按下列格式進(jìn)行:

<類(lèi)名> <對(duì)象名表>

其中,類(lèi)名是用戶(hù)已定義過(guò)的類(lèi)的標(biāo)識(shí)符,對(duì)象名可以有一個(gè)或多個(gè),多個(gè)時(shí)要用逗號(hào)分隔。被定義的對(duì)象既可以是一個(gè)普通對(duì)象,也可以是一個(gè)數(shù)組對(duì)象或指針對(duì)象。例如:

CMeter  myMeter,*Meter,Meters[2];

這時(shí),myMeter是類(lèi)CMeter的一個(gè)普通對(duì)象,Meter和Meters分別是該類(lèi)的一個(gè)指針對(duì)象和對(duì)象數(shù)組。

一個(gè)對(duì)象的成員就是該對(duì)象的類(lèi)所定義的數(shù)據(jù)成員(成員變量)和成員函數(shù)。訪問(wèn)對(duì)象的成員變量和成員函數(shù)和訪問(wèn)變量和函數(shù)的方法是一樣的,只不過(guò)要在成員前面加上對(duì)象名和成員運(yùn)算符“.”,其表示方式如下:

<對(duì)象名>.<成員變量>
<對(duì)象名>.<成員函數(shù)>(<參數(shù)表>)

例如:

myMeter.m_nPercent,  myMeter.SetPos(2),  Meters[0].StepIt();

需要說(shuō)明的是,一個(gè)類(lèi)對(duì)象只能訪問(wèn)該類(lèi)的公有型成員,而對(duì)于私有型成員則不能訪問(wèn)。上述成員m_nPercent、SetPos、StepIt都是public訪問(wèn)類(lèi)型。

若對(duì)象是一個(gè)指針,則對(duì)象的成員訪問(wèn)形式如下:

<對(duì)象指針名>-><成員變量>
<對(duì)象指針名>-><成員函數(shù)>(<參數(shù)表>)

“->”是一個(gè)表示成員的運(yùn)算符,它與“.”運(yùn)算符的區(qū)別是:“->”用來(lái)表示指向?qū)ο蟮闹羔樀某蓡T,而“.”用來(lái)表示一般對(duì)象的成員。

需要說(shuō)明的是,下面的兩種表示是等價(jià)的:

<對(duì)象指針名>-><成員變量>
(*<對(duì)象指針名>).<成員變量>

這對(duì)于成員函數(shù)也適用。另外,對(duì)于引用類(lèi)型對(duì)象,其成員訪問(wèn)形式與一般對(duì)象的成員訪問(wèn)形式相同。例如:

CMeter  &other=one;
cout<< other.GetPos()<<endl;

2.1.3 類(lèi)作用域和成員訪問(wèn)權(quán)限

類(lèi)的作用域是指在類(lèi)的定義中由一對(duì)花括號(hào)所括起來(lái)的部分。從類(lèi)的定義可知,類(lèi)作用域中可以定義變量,也可以定義函數(shù)。從這一點(diǎn)上看,類(lèi)作用域與文件作用域很相似。但是,類(lèi)作用域又不同于文件作用域,在類(lèi)作用域中定義的變量不能使用auto、register和extern等修飾符,只能用static修飾符,而定義的函數(shù)也不能用extern修飾符。另外,在類(lèi)作用域中的靜態(tài)成員和成員函數(shù)還具有類(lèi)外的連接屬性(以后會(huì)討論)。文件作用域中可以包含類(lèi)作用域,顯然,類(lèi)作用域小于文件作用域。一般地,類(lèi)作用域中可包含成員函數(shù)的作用域。

1.類(lèi)名的作用域

如果在類(lèi)聲明時(shí)指定了類(lèi)名,則類(lèi)名的作用范圍是從類(lèi)名指定的位置開(kāi)始一直到文件結(jié)尾,即類(lèi)名是具有文件作用域的標(biāo)識(shí)符。若類(lèi)的聲明是放在頭文件中的,則類(lèi)名在程序文件中的作用范圍是從包含預(yù)處理指令位置處開(kāi)始一直到文件結(jié)尾。

需要說(shuō)明的是,如果在類(lèi)聲明之前就需要使用該類(lèi)名定義對(duì)象,則必須用下列格式在使用前進(jìn)行提前聲明(注意,類(lèi)的這種形式的聲明可以在相同作用域中出現(xiàn)多次):

class <類(lèi)名>;

例如:

class COne;                       // 將類(lèi)COne提前聲明
class COne;                       // 可以聲明多次
class  CTwo
{   //…
private:
    COne a;                       // 數(shù)據(jù)成員a是已定義的COne類(lèi)對(duì)象
};
class  COne
{   //…
};

2.類(lèi)中成員的可見(jiàn)性

(1)在類(lèi)中使用成員時(shí),成員聲明的前后不會(huì)影響該成員在類(lèi)中的使用,這是類(lèi)作用域的特殊性。例如:

class  A
{
    void f1()
    {
        f2();                  // 調(diào)用類(lèi)中的成員函數(shù)f2
        cout<<a<<endl;         // 使用類(lèi)中的成員變量a
    }
    void f2(){}
    int a;
};

(2)由于類(lèi)的成員函數(shù)可以在類(lèi)體外定義,因而此時(shí)由“類(lèi)名::”指定開(kāi)始一直到函數(shù)體最后一個(gè)花括號(hào)為止的范圍也是該類(lèi)作用域的范圍。例如:

class  A
{
    void f1();
    //…
};
void A::f1()
{   //…
}

則從A::開(kāi)始一直到f1函數(shù)體最后一個(gè)花括號(hào)為止的范圍都是屬于類(lèi)A的作用域。

(3)在同一個(gè)類(lèi)的作用域中,不管成員具有怎樣的訪問(wèn)權(quán)限,都可在類(lèi)作用域中使用,而在類(lèi)作用域外卻不可使用。例如:

class  A
{
public:
    int a;
    //…
};
a=10;                       // 錯(cuò)誤,不能在A作用域外直接使用類(lèi)中的成員

3.類(lèi)外對(duì)象成員的可見(jiàn)性

對(duì)于訪問(wèn)權(quán)限public、private和protected來(lái)說(shuō),只有在子類(lèi)中或用對(duì)象來(lái)訪問(wèn)成員時(shí),它們才會(huì)起作用。在用類(lèi)外對(duì)象來(lái)訪問(wèn)成員時(shí),只能訪問(wèn)public成員,而對(duì)private和protected均不能訪問(wèn)。

2.1.4 構(gòu)造函數(shù)和析構(gòu)函數(shù)

事實(shí)上,一個(gè)類(lèi)總有兩種特殊的成員函數(shù):構(gòu)造函數(shù)和析構(gòu)函數(shù)。構(gòu)造函數(shù)的功能是在創(chuàng)建對(duì)象時(shí),使用給定的值將對(duì)象初始化。析構(gòu)函數(shù)的功能是用來(lái)釋放一個(gè)對(duì)象,在對(duì)象刪除前,用它來(lái)做一些內(nèi)存釋放等清理工作,它與構(gòu)造函數(shù)的功能正好相反。

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

前面已提及,在類(lèi)的定義中是不能對(duì)數(shù)據(jù)成員進(jìn)行初始化的。為了能給數(shù)據(jù)成員設(shè)置某些初值,就要使用類(lèi)的特殊成員函數(shù)——構(gòu)造函數(shù)。構(gòu)造函數(shù)的最大特點(diǎn)是在對(duì)象建立時(shí)它會(huì)被自動(dòng)執(zhí)行,因此用于變量、對(duì)象的初始化代碼一般都放在構(gòu)造函數(shù)中。

C++規(guī)定,一個(gè)類(lèi)的構(gòu)造函數(shù)必須與相應(yīng)的類(lèi)同名,它可以帶參數(shù),也可以不帶參數(shù),與一般的成員函數(shù)定義相同,可以重載,也可以有默認(rèn)的形參值。例如:

class  CMeter
{
public:
    CMeter(int nPos)                    // 帶參數(shù)的構(gòu)造函數(shù)
    {
        m_nPos = nPos;
    }
    //…
}

這樣若有:

CMeter oMeter(10), oTick(20);

則會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)CMeter(int nPos),從而使得對(duì)象oMeter中的私有成員m_nPos的值為10,使得對(duì)象oTick中的私有成員m_nPos的值為20。

2.對(duì)構(gòu)造函數(shù)的幾點(diǎn)說(shuō)明

雖然構(gòu)造函數(shù)的定義方式與一般成員函數(shù)沒(méi)有什么區(qū)別,但要注意:

(1)構(gòu)造函數(shù)的約定使系統(tǒng)在生成類(lèi)的對(duì)象時(shí)自動(dòng)調(diào)用。同時(shí),指定對(duì)象括號(hào)里的參數(shù)就是構(gòu)造函數(shù)的實(shí)參,例如,oMeter(10)就是oMeter. CMeter(10)。故當(dāng)構(gòu)造函數(shù)重載及設(shè)定構(gòu)造函數(shù)默認(rèn)形參值時(shí),要避免出現(xiàn)二義。

CPerson(char*str,float h=170,float w=130)   //A
{
    strcpy(name, str);
    height = h;
    weight = w;
}
CPerson(char*str)                           //B
{
    strcpy(name, str);
}

則當(dāng)執(zhí)行“CPerson other("DING");”時(shí),即“other.CPerson("DING");”,因編譯無(wú)法確定是上述哪一個(gè)構(gòu)造函數(shù)的調(diào)用,從而出現(xiàn)編譯錯(cuò)誤。

(2)定義的構(gòu)造函數(shù)不能指定其返回值的類(lèi)型,也不能指定為void類(lèi)型。事實(shí)上,由于構(gòu)造函數(shù)主要用于對(duì)象數(shù)據(jù)成員的初始化,因而無(wú)須返回函數(shù)值,也就無(wú)須有返回類(lèi)型。

(3)若要用類(lèi)定義對(duì)象,則構(gòu)造函數(shù)必須是公有型成員函數(shù),否則類(lèi)無(wú)法實(shí)例化(即無(wú)法定義對(duì)象)。若類(lèi)僅用于派生其他類(lèi),則構(gòu)造函數(shù)可定義為保護(hù)型成員函數(shù)。

3.默認(rèn)構(gòu)造函數(shù)

實(shí)際上,在類(lèi)定義時(shí),如果沒(méi)有定義任何構(gòu)造函數(shù),則編譯自動(dòng)為類(lèi)隱式生成一個(gè)不帶任何參數(shù)的默認(rèn)構(gòu)造函數(shù),由于函數(shù)體是空塊,因此默認(rèn)構(gòu)造函數(shù)不進(jìn)行任何操作,僅僅為了滿(mǎn)足對(duì)象創(chuàng)建時(shí)的語(yǔ)法需要。其形式如下:

<類(lèi)名>()
{}

例如,對(duì)于CMeter類(lèi)來(lái)說(shuō),默認(rèn)構(gòu)造函數(shù)的形式如下:

CMeter()                         // 默認(rèn)構(gòu)造函數(shù)的形式
{}

默認(rèn)構(gòu)造函數(shù)的目的是使下列對(duì)象定義形式合法:

CMeter  one;                      //one.CMeter(); 會(huì)自動(dòng)調(diào)用默認(rèn)構(gòu)造函數(shù)

此時(shí),由于對(duì)象one沒(méi)指定任何初值,因而編譯會(huì)自動(dòng)調(diào)用類(lèi)中隱式生成的默認(rèn)構(gòu)造函數(shù)對(duì)其初始化。需要說(shuō)明的是:

(1)默認(rèn)構(gòu)造函數(shù)對(duì)數(shù)據(jù)成員初值的初始化還取決于對(duì)象的存儲(chǔ)類(lèi)型。例如:

CMeter  one;                        // 自動(dòng)存儲(chǔ)類(lèi)型,數(shù)據(jù)成員的初值為無(wú)效值
static CMeter  one;                 // 靜態(tài)存儲(chǔ)類(lèi)型,數(shù)據(jù)成員的初值為空值或0

(2)若類(lèi)定義中指定了構(gòu)造函數(shù),則隱式的默認(rèn)構(gòu)造函數(shù)不再存在,因此,對(duì)于前面定義的CMeter類(lèi)來(lái)說(shuō),若有:

CMeter  four;                      // 錯(cuò)誤

則因?yàn)檎也坏侥J(rèn)構(gòu)造函數(shù)而出現(xiàn)編譯錯(cuò)誤。此時(shí),在類(lèi)中還要給出默認(rèn)構(gòu)造函數(shù)的具體定義,即定義一個(gè)不帶任何參數(shù)的構(gòu)造函數(shù),稱(chēng)為顯式的默認(rèn)構(gòu)造函數(shù),這樣才能對(duì)four進(jìn)行定義并初始化。

(3)在定義對(duì)象時(shí),不能寫(xiě)成“CMeter four();”,因?yàn)檫@是一個(gè)函數(shù)的聲明。

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

與構(gòu)造函數(shù)相對(duì)應(yīng)的是析構(gòu)函數(shù)。析構(gòu)函數(shù)是另一個(gè)特殊的C++成員函數(shù),它只是在類(lèi)名稱(chēng)前面加上一個(gè)“~”符號(hào)(邏輯非),以示與構(gòu)造函數(shù)功能相反。每一個(gè)類(lèi)只有一個(gè)析構(gòu)函數(shù),沒(méi)有任何參數(shù),也不返回任何值。例如:

class  CMeter
{
public:
    //…
    ~CMeter()                    // 析構(gòu)函數(shù)
    {
    }
    //…
}

析構(gòu)函數(shù)只有在下列兩種情況下才會(huì)被自動(dòng)調(diào)用:

(1)當(dāng)對(duì)象定義在一個(gè)函數(shù)體中,該函數(shù)調(diào)用結(jié)束后,析構(gòu)函數(shù)被自動(dòng)調(diào)用。

(2)用new為對(duì)象分配動(dòng)態(tài)內(nèi)存,當(dāng)使用delete釋放對(duì)象時(shí),析構(gòu)函數(shù)被自動(dòng)調(diào)用。

與默認(rèn)構(gòu)造函數(shù)類(lèi)似,若類(lèi)的聲明中沒(méi)有定義析構(gòu)函數(shù),則編譯也會(huì)自動(dòng)生成一個(gè)隱式的不做任何操作的默認(rèn)析構(gòu)函數(shù)。

5.應(yīng)用示例

類(lèi)的構(gòu)造函數(shù)和析構(gòu)函數(shù)的一個(gè)典型應(yīng)用是在構(gòu)造函數(shù)中用new為指針成員開(kāi)辟獨(dú)立的動(dòng)態(tài)內(nèi)存空間,而在析構(gòu)函數(shù)中用delete釋放它們。

【例Ex_Name】 使用構(gòu)造函數(shù)和析構(gòu)函數(shù)

#include <iostream.h>
#include <string.h>
class CName
{
public:
    CName()                             //A:顯式默認(rèn)構(gòu)造函數(shù)
    {
        strName=NULL;                   // 空值
    }
    CName(char*str)                     //B
    {
        strName = str;
    }
    ~CName() {}                         // 顯式默認(rèn)析構(gòu)函數(shù)
    char*getName()                      // 獲取字符串
    {
        return strName;
    }
private:
    char *strName;                      // 字符指針,名稱(chēng)
};
int  main()
{
    char*p=new char[5];                  // 為p開(kāi)辟內(nèi)存空間
    strcpy(p,"DING");                   // p指向的內(nèi)存空間的值為"DING"
    CName one(p);                        // 對(duì)象初始化
    delete[]p;                          // 釋放p的內(nèi)存空間
    cout<<one.getName()<<endl;
    return 0;
}

由于“CName one(p);”調(diào)用的是B重載構(gòu)造函數(shù),從而使得私有指針成員strName的指向等于p的指向。而p指向new開(kāi)辟的內(nèi)存空間,其內(nèi)容為“DING”,一旦p指向的內(nèi)存空間刪除后,p的指向就變得不確定了,此時(shí)strName指向也不確定,所以此時(shí)運(yùn)行結(jié)果為:

m葺葺葺葺?

顯然,輸出的是一個(gè)無(wú)效的字符串。因此,為了保證類(lèi)的封裝性,類(lèi)中的指針成員所指向的內(nèi)存空間必須在類(lèi)中自行獨(dú)立開(kāi)辟和釋放。因此,類(lèi)CName應(yīng)改成下列代碼:

class CName
{
public:
    CName()                          //A:顯式默認(rèn)構(gòu)造函數(shù)
    {
        strName=NULL;                // 空值
    }
    CName(char*str)                  //B
    {
        strName = (char *)new char[strlen(str)+1];
        // 因字符串后面還有一個(gè)結(jié)束符,因此內(nèi)存空間的大小要多開(kāi)辟1個(gè)內(nèi)存單元
        strcpy(strName,str);         // 復(fù)制內(nèi)容
    }
    ~CName()
    {
        if(strName)    delete[]strName;
        strName=NULL;                // 一個(gè)好習(xí)慣
    }
    char *getName()
    {
        return strName;
    }
private:
    char *strName;                 // 字符指針,名稱(chēng)
};

這樣,主函數(shù)中的代碼才會(huì)有正確的運(yùn)行結(jié)果:

DING

總之,為了使類(lèi)具有通用性,通常將類(lèi)中的字符串?dāng)?shù)據(jù)用char指針來(lái)描述,如char *strName,但此時(shí)應(yīng)將strName在構(gòu)造函數(shù)中另開(kāi)辟獨(dú)立的內(nèi)存空間來(lái)存儲(chǔ)字符串,然后在析構(gòu)函數(shù)中釋放,以保證成員數(shù)據(jù)在類(lèi)中的封裝性。若在構(gòu)造函數(shù)中將指針成員直接指向字符串或指向外部的存儲(chǔ)字符串的內(nèi)存空間,則會(huì)出現(xiàn)潛在的危險(xiǎn)。

1.賦值

在C++中,一個(gè)類(lèi)的對(duì)象的初值設(shè)定可以有多種形式。例如,對(duì)于前面的類(lèi)CName來(lái)說(shuō),則可有下列對(duì)象的定義方式:

CName o1;                         // 通過(guò)A顯式默認(rèn)構(gòu)造函數(shù)設(shè)定初值
CName o2("DING");                 // 通過(guò)B重載構(gòu)造函數(shù)設(shè)定初值

2.1.5 對(duì)象賦值和拷貝

等都是合法有效的。但是若有:

o1=o2;                           // 通過(guò)賦值語(yǔ)句設(shè)定初值

則雖合法,但因?yàn)橥?lèi)型的變量可以直接用“=”賦值,運(yùn)行后卻會(huì)出現(xiàn)程序終止的情況,這是為什么呢?這是因?yàn)閷?duì)于“CName o1;”這種定義方式,編譯會(huì)自動(dòng)調(diào)用相應(yīng)的默認(rèn)構(gòu)造函數(shù),此時(shí)顯式的默認(rèn)構(gòu)造函數(shù)使私有指針成員strName為空值;而“o1 = o2;”中,C++賦值運(yùn)算符的操作是將右操作對(duì)象的內(nèi)容拷貝(復(fù)制,余同)到左操作對(duì)象的內(nèi)存空間,由于左操作對(duì)象o1中的strName沒(méi)有指向任何內(nèi)存空間,因此試圖將數(shù)據(jù)復(fù)制到一個(gè)不存在的內(nèi)存空間中,程序必然異常終止。所以“o1 = o2;”看上去合法,但實(shí)際上是不可行的。

C++還常用下列形式的初始化來(lái)將另一個(gè)對(duì)象作為對(duì)象的初值:

<類(lèi)名> <對(duì)象名1>(<對(duì)象名2>)

例如:

CName o2("DING");                 //A:通過(guò)構(gòu)造函數(shù)設(shè)定初值
CName o3(o2);                     //B:通過(guò)指定對(duì)象設(shè)定初值

B語(yǔ)句是將o2作為o3的初值,與o2一樣,o3這種初始化形式要調(diào)用相應(yīng)的構(gòu)造函數(shù),但此時(shí)找不到相匹配的構(gòu)造函數(shù),因?yàn)镃Name類(lèi)沒(méi)有任何構(gòu)造函數(shù)的形參是CName類(lèi)對(duì)象。事實(shí)上,CName還隱含一個(gè)特殊的默認(rèn)構(gòu)造函數(shù),其原型為CName(const CName &),這種特殊的默認(rèn)構(gòu)造函數(shù)稱(chēng)為默認(rèn)拷貝構(gòu)造函數(shù)。在C++中,每一個(gè)類(lèi)總有一個(gè)默認(rèn)拷貝構(gòu)造函數(shù),其目的是保證B語(yǔ)句中對(duì)象初始化形式的合法性,其功能就等價(jià)于“CName o3 = o2;”。但語(yǔ)句“CName o3(o2);”與語(yǔ)句“o1 = o2;”一樣,也會(huì)出現(xiàn)程序終止的情況,其原因和“o1 = o2;”的原因一樣。但是,若有類(lèi)CData:

class CData
{
public:
    CData( int data = 0)
    {
        m_nData = data;
    }
    ~CData()  {}
    int getData()
    {
        return m_nData;
    }
private:
    int  m_nData;
};

則下列初始化形式卻都是合法有效的:

CData a(3);                            // 通過(guò)重載構(gòu)造函數(shù)設(shè)定初值
CData b(a);                            // 通過(guò)默認(rèn)拷貝構(gòu)造函數(shù)設(shè)定初值
                                      // 等價(jià)于 CData b=a;
cout<<a.getData()<<endl;              // 輸出 3
cout<<b.getData()<<endl;              // 輸出 3

可見(jiàn),與變量一樣,在C++中類(lèi)對(duì)象的初始化也可以有兩種方式:賦值方式和默認(rèn)拷貝方式。這兩種方式是等價(jià)的,例如,CData b(a);和CData b = a;是等價(jià)的。

為什么CData對(duì)象的賦值和默認(rèn)拷貝初始化是可行的,而CName對(duì)象的賦值和默認(rèn)拷貝初始化卻是不行的呢?問(wèn)題就出在其數(shù)據(jù)成員的內(nèi)存空間上。

CName的數(shù)據(jù)成員strName是一個(gè)“char *”指針,由于其自身的內(nèi)存空間是用來(lái)存放指針的地址,因而其數(shù)據(jù)的存儲(chǔ)還需另辟一個(gè)不依附外部的獨(dú)立的內(nèi)存空間。而CData的數(shù)據(jù)成員m_nData自身的內(nèi)存空間就是用來(lái)存儲(chǔ)數(shù)據(jù)的,因此CData對(duì)象初始化所進(jìn)行的數(shù)值拷貝是有效的。

解決CName對(duì)象初始化的內(nèi)容拷貝問(wèn)題,在C++中有兩種手段,一是給“=”運(yùn)算符賦予新的操作,稱(chēng)為運(yùn)算符重載(以后會(huì)討論);二是重新定義或重載默認(rèn)拷貝構(gòu)造函數(shù)。

2.淺拷貝和深拷貝

前面已說(shuō)過(guò),每一個(gè)C++類(lèi)都有一個(gè)隱式的默認(rèn)拷貝構(gòu)造函數(shù),其目的是保證對(duì)象初始化方式的合法性,其功能是將一個(gè)已定義的對(duì)象所在的內(nèi)存空間的內(nèi)容依次拷貝到被初始化對(duì)象的內(nèi)存空間中。這種僅僅將內(nèi)存空間的內(nèi)容拷貝的方式稱(chēng)為淺拷貝。也就是說(shuō),默認(rèn)拷貝構(gòu)造函數(shù)是淺拷貝方式。

事實(shí)上,對(duì)于數(shù)據(jù)成員有指針類(lèi)型的類(lèi)來(lái)說(shuō),均會(huì)出現(xiàn)如CName類(lèi)的問(wèn)題,由于默認(rèn)拷貝構(gòu)造函數(shù)無(wú)法解決,因此必須自己定義一個(gè)拷貝構(gòu)造函數(shù),在進(jìn)行數(shù)值拷貝之前,為指針類(lèi)型的數(shù)據(jù)成員另辟一個(gè)獨(dú)立的內(nèi)存空間。由于這種拷貝還需另辟內(nèi)存空間,因而稱(chēng)其為深拷貝

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

拷貝構(gòu)造函數(shù)是一種比較特殊的構(gòu)造函數(shù),除遵循構(gòu)造函數(shù)的聲明和實(shí)現(xiàn)規(guī)則外,還應(yīng)按下列格式進(jìn)行定義。

<類(lèi)名>(參數(shù)表)
{}

可見(jiàn),拷貝構(gòu)造函數(shù)的格式就是帶參數(shù)的構(gòu)造函數(shù)。由于拷貝操作的實(shí)質(zhì)是類(lèi)對(duì)象空間的引用,因此C++規(guī)定,拷貝構(gòu)造函數(shù)的參數(shù)個(gè)數(shù)可以是1個(gè)或多個(gè),但左起的第1個(gè)參數(shù)必須是類(lèi)的引用對(duì)象,它可以是“類(lèi)名 &對(duì)象”或是“const類(lèi)名 &對(duì)象”形式,其中“類(lèi)名”是拷貝構(gòu)造函數(shù)所在類(lèi)的類(lèi)名。也就是說(shuō),對(duì)于CName的拷貝構(gòu)造函數(shù),可有下列合法的函數(shù)原型:

CName(CName&x);                     //x為合法的對(duì)象標(biāo)識(shí)符
CName( const CName &x );
CName(CName&x,…);                  // “…”表示還有其他參數(shù)
CName( const CName &x,…);

需要說(shuō)明的是,一旦在類(lèi)中定義了拷貝構(gòu)造函數(shù),則隱式的默認(rèn)拷貝構(gòu)造函數(shù)和隱式的默認(rèn)構(gòu)造函數(shù)就不再有效了。

【例Ex_CopyCon】 使用拷貝構(gòu)造函數(shù)

#include <iostream.h>
#include <string.h>
class CName
{
public:
    CName()
    {
        strName = NULL;
    }
    CName( char *str )
    {
        strName = (char *)new char[strlen(str)+1];
        strcpy(strName,str);              // 復(fù)制內(nèi)容
    }
    CName(CName&one)                      // A:顯式的默認(rèn)拷貝構(gòu)造函數(shù)
    {
        // 為strName開(kāi)辟獨(dú)立的內(nèi)存空間
        strName = (char *)new char[strlen(one.strName)+1];
        strcpy(strName,one.strName);      // 復(fù)制內(nèi)容
    }
    CName(CName&one,char*add)             // B:帶其他參數(shù)的拷貝構(gòu)造函數(shù)
    {   // 為strName開(kāi)辟獨(dú)立的內(nèi)存空間
        strName = (char *)new char[strlen(one.strName) + strlen(add) +1];
        strcpy(strName,one.strName);      // 復(fù)制內(nèi)容
        strcat(strName,add);              // 連接到strName中
    }
    ~CName()
    {
        if(strName)    delete[]strName;
        strName=NULL;                     // 一個(gè)好習(xí)慣
    }
    char *getName()
    {
        return strName;
    }
private:
 char *strName;                            // 字符指針,名稱(chēng)
};
int main()
{
    CName o1("DING");                      // 通過(guò)構(gòu)造函數(shù)初始化
    CName o2(o1);                          // 通過(guò)顯式的默認(rèn)拷貝構(gòu)造函數(shù)來(lái)初始化
    cout<<o2.getName()<<endl;
    CName o3(o1,"YOU HE");                  // 通過(guò)帶其他參數(shù)的拷貝構(gòu)造函數(shù)來(lái)初始化
    cout<<o3.getName()<<endl;
    return 0;
}

代碼中,類(lèi)CName定義了兩個(gè)拷貝構(gòu)造函數(shù)A和B,其中A稱(chēng)為顯式的默認(rèn)拷貝構(gòu)造函數(shù), B稱(chēng)為重載拷貝構(gòu)造函數(shù),它還帶有字符指針參數(shù),用來(lái)將新對(duì)象的數(shù)據(jù)成員字符指針strName指向一個(gè)開(kāi)辟的動(dòng)態(tài)內(nèi)存空間,然后將另一個(gè)對(duì)象one的內(nèi)容復(fù)制到strName中,最后調(diào)用cstring頭文件定義的庫(kù)函數(shù)strcat,將字符指針參數(shù)add指向的字符串連接到strName中。

程序運(yùn)行結(jié)果如下:

DING

DING YOU HE

2.1.6 對(duì)象成員的初始化

在實(shí)際應(yīng)用中,一個(gè)類(lèi)的數(shù)據(jù)成員除了普通數(shù)據(jù)類(lèi)型變量外,還往往是其他已定義的類(lèi)的對(duì)象,這樣的成員就稱(chēng)為對(duì)象成員,擁有對(duì)象成員的類(lèi)常稱(chēng)為組合類(lèi)。此時(shí),為提高對(duì)象初始化效率,增強(qiáng)程序的可讀性,C++允許在構(gòu)造函數(shù)的函數(shù)頭后面跟一個(gè)由冒號(hào)“:”來(lái)引導(dǎo)的對(duì)象成員初始化列表,列表中包含類(lèi)中對(duì)象成員或數(shù)據(jù)成員的拷貝初始化代碼,各對(duì)象初始化之間用逗號(hào)分隔,如下列格式:

以前已討論過(guò),數(shù)據(jù)成員的初始化是通過(guò)構(gòu)造函數(shù)來(lái)進(jìn)行的。這就意味著,類(lèi)的對(duì)象成員也可在類(lèi)的構(gòu)造函數(shù)體中進(jìn)行初始化。這樣一來(lái),類(lèi)的對(duì)象成員的初始化就可以有兩種方式:一是在構(gòu)造函數(shù)體中進(jìn)行,稱(chēng)為函數(shù)構(gòu)造方式;二是使用由冒號(hào)“:”來(lái)引導(dǎo)的對(duì)象成員初始化列表的形式,稱(chēng)為對(duì)象成員列表方式。

先來(lái)看看第一種方式(函數(shù)構(gòu)造方式),例如:

class CPoint
{
public:
    CPoint( int x, int y)
    {
        xPos=x;  yPos=y;
    }
private:
    int xPos, yPos;
};
class CRect
{
public:
    CRect( int x1, int y1, int x2, int y2)
    {
        {L-End}
m_ptLT  =CPoint(x1,y1);
        {L-End}
m_ptRB  =CPoint(x2,y2);
    }
private:
    {L-End}
CPoint m_ptLT, m_ptRB;
};
int  main()
{
    CRect rc(10, 100, 80, 250);
    return 0;
}

雖然,類(lèi)CRect中的對(duì)象成員m_ptLT和m_ptRB的初值的設(shè)定是在構(gòu)造函數(shù)中完成的,但此時(shí)編譯卻出現(xiàn)“找不到匹配的CPoint默認(rèn)構(gòu)造函數(shù)”的編譯錯(cuò)誤。這是因?yàn)楫?dāng)主函數(shù)main中定義并初始化CRect對(duì)象rc時(shí),它首先為類(lèi)CRect的數(shù)據(jù)成員m_ptLT和m_ptRB作定義并分配內(nèi)存空間,由于m_ptLT和m_ptRB是CPoint對(duì)象,因而編譯會(huì)查找其構(gòu)造函數(shù)進(jìn)行初始化,此時(shí)m_ptLT和m_ptRB不帶任何初值,故需要調(diào)用CPoint類(lèi)的默認(rèn)構(gòu)造函數(shù),而CPoint類(lèi)已定義了帶參數(shù)的構(gòu)造函數(shù),因此CPoint類(lèi)的默認(rèn)構(gòu)造函數(shù)不再存在,所以會(huì)出現(xiàn)編譯錯(cuò)誤。

但若使用第二種方式(對(duì)象成員列表方式),即使用由冒號(hào)“:”來(lái)引導(dǎo)的對(duì)象成員初始化列表的形式,如下面的代碼:

class CPoint
{//…
};
class CRect
{
public:
    CRect( int x1, int y1, int x2, int y2)
        {L-End}
: m_ptLT(x1, y1), m_ptRB(x2, y2)
    {}
private:
    CPoint m_ptLT, m_ptRB;
};
int main()
{
    CRect rc(10, 100, 80, 250);
    return 0;
}

則編譯會(huì)順利通過(guò)。這是因?yàn)榈诙N由冒號(hào)“:”來(lái)引導(dǎo)的對(duì)象成員初始化列表的形式實(shí)際上是將對(duì)象成員的定義和初始化同時(shí)進(jìn)行。當(dāng)在main函數(shù)中定義了CRect對(duì)象rc時(shí),編譯首先根據(jù)類(lèi)中聲明的數(shù)據(jù)成員次序,為成員分配內(nèi)存空間,然后從對(duì)象初始化列表中尋找其初始化代碼,若查找不到,則調(diào)用相應(yīng)的構(gòu)造函數(shù)進(jìn)行初始化,若可查找到,則根據(jù)對(duì)象成員的初始化形式調(diào)用相應(yīng)的構(gòu)造函數(shù)進(jìn)行初始化。顯然,在對(duì)象成員初始化列表中由于存在m_ptLT(x1, y1)和m_ptRB(x2, y2)對(duì)象初始化代碼,因此成員m_ptLT和m_ptRB構(gòu)造時(shí)調(diào)用的是CPoint( int , int)形式的構(gòu)造函數(shù),而類(lèi)CPoint剛好有此形式的構(gòu)造函數(shù)定義,故編譯能通過(guò)。可見(jiàn):

(1)函數(shù)構(gòu)造方式實(shí)際上是將對(duì)象成員進(jìn)行了兩次初始化:第一次是在對(duì)象成員聲明的同時(shí)自動(dòng)調(diào)用默認(rèn)構(gòu)造函數(shù)進(jìn)行的,而第二次是在構(gòu)造函數(shù)體中執(zhí)行的初始化代碼。

(2)對(duì)象成員列表方式雖是將對(duì)象成員的定義和初始化代碼分在兩個(gè)地方書(shū)寫(xiě),但卻是同時(shí)運(yùn)行。對(duì)比函數(shù)構(gòu)造方式可以看出,由冒號(hào)“:”來(lái)引導(dǎo)的對(duì)象初始化列表的形式能簡(jiǎn)化對(duì)象初始化操作,提高對(duì)象初始化效率。

(3)在對(duì)象成員列表方式下,成員初始化的順序是按成員的聲明次序進(jìn)行的,而與成員在由冒號(hào)“:”來(lái)引導(dǎo)的對(duì)象初始化列表中的次序無(wú)關(guān)。

主站蜘蛛池模板: 抚顺县| 西青区| 深州市| 凭祥市| 阳新县| 原阳县| 永昌县| 丁青县| 荥阳市| 普定县| 高唐县| 潮州市| 当涂县| 博兴县| 响水县| 九江市| 永春县| 易门县| 永和县| 广饶县| 依安县| 镇原县| 泌阳县| 若羌县| 德兴市| 达州市| 类乌齐县| 景洪市| 卢湾区| 沅江市| 历史| 绥化市| 汨罗市| 龙陵县| 武胜县| 安宁市| 甘德县| 板桥市| 天祝| 九江市| 静乐县|