C++ 面向?qū)ο缶幊?/h1>

C++ 面向?qū)ο缶幊?/b>

博客園地址:http://www.cnblogs.com/xiongxuanwen/p/4290090.html

面向?qū)ο缶幊袒谌齻€(gè)基本概念:數(shù)據(jù)抽象繼承動(dòng)態(tài)綁定

1 基類和派生類

1.1 定義基類

在基類中,除了構(gòu)造函數(shù)之外,任意非 static 成員函數(shù)都可以是虛函數(shù)。

基類通常應(yīng)將派生類需要重定義的任意函數(shù)定義為虛函數(shù)。

1.2 訪問控制

(1)private ?成員

? ?通過類對象無法訪問類的private成員。

? ?在派生類中不能訪問基類的private成員。

? ?private?成員只能在當(dāng)前類的作用域內(nèi)訪問,類的友元也可以訪問類的private?成員。例如,在成員函數(shù)中可以訪問private?成員,在成員函數(shù)中還可以通過自己類的對象來訪問類的private?成員。類的作用域包括:類定義的{}之內(nèi),類定義之外的成員函數(shù)的函數(shù)體,形參列表等。

class Base

{

public:

void Test1(Base& b)

{

b.iBase = 0;//有沒有問題?

}

private:

int iBase;

};

class Derived : public Base

{

public:

void Test2(Base& b)

{

b.iBase = 0;//有沒有問題?

}

void Test3(Derived& d)

{

d.iDerived = 0;//有沒有問題?

}

private:

int iDerived;

};

(2)protected ?成員

? ?通過類對象無法訪問protected 成員。

? ?protected 成員可被public派生類(包括派生類的派生類,向下傳遞)訪問,也就是說在派生類中可以使用基類的protected 成員。

?? 派生類只能通過派生類對象訪問其基類的 protected 成員,派生類無法訪問其基類類型對象的 protected 成員。

1.3 派生類

類派生列表指定了一個(gè)或多個(gè)基類,具有如下形式:

class classname: access-label base-class

這里 access-label 是 public、protected 或 private,base-class 是已定義的類的名字,類派生列表可以指定多個(gè)基類。

一旦函數(shù)在基類中聲明為虛函數(shù),它就一直為虛函數(shù),派生類無法改變該函數(shù)為虛函數(shù)這一事實(shí)。派生類重定義虛函數(shù)時(shí),可以使用 virtual 保留字,也可以不使用。

(1)派生類一般會(huì)重定義所繼承的虛函數(shù)。如果派生類沒有重定義某個(gè)虛函數(shù),則使用基類中定義的版本。

(2)一般情況下,派生類中虛函數(shù)的聲明必須與基類中的定義方式完全匹配例外返回對基類型A的引用(或指針)的虛函數(shù)。派生類中的虛函數(shù)可以返回類A的派生類的引用(或指針)。

提示:絕對不要重新定義繼承而來的non-virtual函數(shù)

因?yàn)閚on-virtual函數(shù)是靜態(tài)綁定的。一個(gè)子類對象綁定到一個(gè)父類指針,另一個(gè)子類對象綁定到一個(gè)子類指針,通過父類指針調(diào)用該函數(shù),調(diào)用的是父類的該函數(shù),而不是子類的函數(shù)。例如:

class Base

{

public:

voidFuncTest()

{

std::cout << "Base" << std::endl;

}

};

class Derived: public Base

{

public:

voidFuncTest()

{

std::cout << "Derived" << std::endl;

}

};

Derived d;

Base* pB = &d;

Derived* pD = &d;

d.FuncTest();//輸出“Derived”

pB->FuncTest();//輸出“Base”

pD->FuncTest();//輸出“Derived”

1.4?non-virtual 和? virtual? 函數(shù)的調(diào)用

(1) 將基類類型的引用或指針綁定到派生類對象,如果調(diào)用非虛函數(shù),則無論實(shí)際對象是什么類型,都執(zhí)行基類類型所定義的函數(shù)。

class Base

{

public:

void FuncTest()

{

std::cout << "Base" << std::endl;

}

};

class Derived: public Base

{

public:

};

Derived d;

Base* pB = &d;

pB->VirtFunc();//輸出“Base”

(2)將基類類型的引用或指針綁定到派生類對象,如果調(diào)用虛函數(shù),則直到運(yùn)行時(shí)才能確定調(diào)用哪個(gè)函數(shù),運(yùn)行的虛函數(shù)是引用所綁定的或指針?biāo)赶虻膶ο笏鶎兕愋投x的版本。

class Base

{

public:

virtual void VirtFunc()

{

std::cout << "Base" << std::endl;

}

};

class Derived: public Base

{

public:

void VirtFunc()

{

std::cout << "Derived" << std::endl;

}

};

Derived d;

Base* pB = &d;

Derived* pD = &d;

pB->VirtFunc();//輸出“Derived”

pD->VirtFunc();//輸出“Derived”

1.5 虛函數(shù)與默認(rèn)實(shí)參

虛函數(shù)也可以有默認(rèn)實(shí)參。如果一個(gè)調(diào)用省略了具有默認(rèn)值的實(shí)參,則所用的值由調(diào)用該函數(shù)的類型定義,與對象的動(dòng)態(tài)類型無關(guān)。通過基類的引用或指針調(diào)用虛函數(shù)時(shí),默認(rèn)實(shí)參為在基類虛函數(shù)聲明中指定的值,如果通過派生類的指針或引用調(diào)用虛函數(shù),則默認(rèn)實(shí)參是在派生類的版本中聲明的值。

提示: 絕不重新定義繼承而來virtual函數(shù)的缺省參數(shù)值

不要重新定義繼承而來的non-virtual函數(shù),但是可以重新定義一個(gè)繼承而來的virtual函數(shù)。

virtual函數(shù)是動(dòng)態(tài)綁定,而該函數(shù)的缺省參數(shù)值卻是靜態(tài)綁定。C++這么做的就是為了提高運(yùn)行期效率。

如果子類重新定義了繼承而來的virtual函數(shù)的缺省參數(shù)值,那么使用父類指針指向子類對象,然后使用父類指針來調(diào)用該函數(shù),所使用的默認(rèn)參數(shù)仍然是從父類繼承而來,而非子類重新定義定義的。例如:

class Base

{

public:

virtualvoidVirtFunc(string sMsg = "Base")

{

cout << sMsg << endl;

}

};

class Derived:public Base

{

public:

void?VirtFunc(string sMsg = "Derived")

{

cout << sMsg << endl;

}

};

Derived?d;

d.VirtFunc();//輸出"Derived"

Base* pD = &d;

pD->VirtFunc();//輸出"Base",而不是"Derived"

為了避免出現(xiàn)上面這種情況,必須將子類中繼承而來的virtual函數(shù)設(shè)計(jì)的跟父類一樣,也就是有同樣的缺省參數(shù)值。如果父類修改了,子類也必須跟著同樣修改。

替換的設(shè)計(jì)方案是:設(shè)計(jì)一個(gè) public non-virtual函數(shù)(帶有默認(rèn)參數(shù)值)來調(diào)用private virtual函數(shù)(不帶默認(rèn)參數(shù)值)。public non-virtual函數(shù)在子類中不能重新定義,但是private virtual函數(shù)可以在子類中重新定義。

class Base

{

public:

void Func(string sMsg = "Base")

{

VirtFunc(sMsg);

}

private:

virtual void VirtFunc(string sMsg )

{

cout << sMsg << endl;

}

};

class Derived:public Base

{

private:

virtual void VirtFunc(string sMsg)

{

cout << sMsg << endl;

}

};

D d;

d.Func();//輸出"Base"

B* pD = &d;

pD->mf();//輸出"Base"

1.6 友元關(guān)系與繼承

友元關(guān)系不能繼承:

(1)基類的友元對派生類的成員沒有特殊訪問權(quán)限。

(2)友元類的派生類不能訪問授予友元關(guān)系的類。

1.7 繼承與靜態(tài)成員

如果基類定義 static 成員,則整個(gè)繼承層次中只有一個(gè)這樣的成員。無論從基類派生出多少個(gè)派生類,每個(gè) static 成員只有一個(gè)實(shí)例。

static 成員遵循常規(guī)訪問控制:如果成員在基類中為 private,則派生類不能訪問它。

如果可以訪問成員,則既可以通過基類訪問 static 成員,也可以通過派生類訪問 static 成員。一般而言,既可以使用作用域操作符也可以使用點(diǎn)或箭頭成員訪問操作符。

2 轉(zhuǎn)換和繼承

2.1 派生類到基類的轉(zhuǎn)換

(1)指針或引用

派生類型引用到基類類型引用

派生類型指針到基類類型指針。

反之是不行的。

(2)對象

一般可以使用派生類型的對象對基類類型的對象進(jìn)行初始化或賦值,但沒有從派生類型對象到基類類型對象的直接轉(zhuǎn)換,編譯器不會(huì)自動(dòng)將派生類型對象轉(zhuǎn)換為基類類型對象。

3 構(gòu)造函數(shù)和復(fù)制控制

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

構(gòu)造函數(shù)和復(fù)制控制成員不能繼承,每個(gè)類定義自己的構(gòu)造函數(shù)和復(fù)制控制成員。像任何類一樣,如果類不定義自己的默認(rèn)構(gòu)造函數(shù)和復(fù)制控制成員,就將使用合成版本。

派生類的合成默認(rèn)構(gòu)造函數(shù),除了初始化派生類的數(shù)據(jù)成員之外,它還初始化派生類對象的基類部分。基類部分由基類的默認(rèn)構(gòu)造函數(shù)初始化。

派生類構(gòu)造函數(shù)的初始化列表只能初始化派生類的成員,不能直接初始化繼承成員。派生類構(gòu)造函數(shù)只能通過將基類包含在構(gòu)造函數(shù)初始化列表中來間接初始化繼承成員。

class People

{

public:

People(std::string s1, int i1) : name("s1"),age(i1)

{

}

private:

std::string name;

int age;

};

class Student: public People

{

public:

Student(std::string s1, int i1,std::string s2) : uniName("s2"),People(s1,i1)

{

}

private:

std::string uniName;//學(xué)校名稱

};

構(gòu)造函數(shù)初始化列表為類的基類和成員提供初始值,它并不指定初始化的執(zhí)行次序。首先初始化基類,然后根據(jù)聲明次序初始化派生類的成員。

一個(gè)類只能初始化自己的直接基類(通過將基類包含在構(gòu)造函數(shù)初始化列表中來間接初始化基類)。

3.2 復(fù)制控制和繼承

只包含類類型或內(nèi)置類型數(shù)據(jù)成員、不含指針的類一般可以使用合成操作。

具有指針成員的類一般需要定義自己的復(fù)制控制來管理這些成員。

(1) 派生類的復(fù)制構(gòu)造函數(shù)

如果派生類定義了自己的復(fù)制構(gòu)造函數(shù),該復(fù)制構(gòu)造函數(shù)一般應(yīng)顯式使用基類復(fù)制構(gòu)造函數(shù)初始化對象的基類部分:

class Base { /* ... */ };

class Derived: publicBase

{

public:

Derived(const Derived& d):Base(d) { /*... */ }

};

(2)派生類賦值操作符

賦值操作符通常與復(fù)制構(gòu)造函數(shù)類似:如果派生類定義了自己的賦值操作符,則該操作符必須對基類部分進(jìn)行顯式賦值。

//Base::operator=(const Base&)

Derived &Derived::operator=(const Derived &rhs)

{

if (this != &rhs)//防止給自己賦值

{

Base::operator=(rhs); //調(diào)用 Base 類的賦值操作符給基類部分賦值

……//為派生類Derived?的成員賦值

}

return *this;

}

(3)派生類析構(gòu)函數(shù)

派生類析構(gòu)函數(shù)不負(fù)責(zé)撤銷基類對象的成員。每個(gè)析構(gòu)函數(shù)只負(fù)責(zé)清除自己的成員。

class Derived: public Base

{

public:

~Derived()??? {/*... */}

};

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

如果層次中基類的析構(gòu)函數(shù)為虛函數(shù),則派生類析構(gòu)函數(shù)也將是虛函數(shù),無論派生類顯式定義析構(gòu)函數(shù)還是使用合成析構(gòu)函數(shù),派生類析構(gòu)函數(shù)都是虛函數(shù)。

建議:即使析構(gòu)函數(shù)沒有工作要做,繼承層次的根類也應(yīng)該定義一個(gè)虛析構(gòu)函數(shù)。

在復(fù)制控制成員中,只有析構(gòu)函數(shù)應(yīng)定義為虛函數(shù),構(gòu)造函數(shù)不能定義為虛函數(shù)。

建議:為多態(tài)基類聲明為virtual析構(gòu)函數(shù)

父類指針指向子類對象,delete父類指針時(shí),如果父類含有non-virtual析構(gòu)函數(shù),則只有繼承自父類的部分被銷毀,而子類非繼承的部分沒有被銷毀,也就是說子類對象被部分銷毀。解決的辦法是,將父類的析構(gòu)函數(shù)定義為virtual析構(gòu)函數(shù)。

class Base

{

public:

Base()

{

// std::cout << "Base的構(gòu)造函數(shù)" << std::endl;

}

~Base()

{

std::cout << "Base的析構(gòu)函數(shù)" << std::endl;

}

};

class Derived : public Base

{

public:

Derived()

{

// std::cout << "Derived的構(gòu)造函數(shù)" << std::endl;

}

~Derived()

{

std::cout << "Derived的析構(gòu)函數(shù)" << std::endl;

}

};

Base* pB = new Derived;

delete pB;//顯示“Base的析構(gòu)函數(shù)”

如果把Base的析構(gòu)函數(shù)前面添加virtual,則上面結(jié)果輸出:

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

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

注意:

任何class只要帶有virtual函數(shù),幾乎確定應(yīng)該也有一個(gè)virtual析構(gòu)函數(shù)。一般的,基類應(yīng)該含有virtual析構(gòu)函數(shù)。基類定義了virtual關(guān)鍵字,子類就不用添加該關(guān)鍵字了。

如果class不含有virtual函數(shù),通常表示它并不打算被用作一個(gè)基類,所以就不應(yīng)該將析構(gòu)函數(shù)定義為virtual的。

4 繼承情況下的類作用域

4.1 名字沖突與繼承

與基類成員同名的派生類成員將屏蔽對基類成員的直接訪問。

可以使用作用域操作符訪問被屏蔽的基類成員:

struct Derived : Base

{

int get_base_mem() { returnBase::mem; }

};

設(shè)計(jì)派生類時(shí),只要可能,最好避免與基類成員的名字沖突。

4.2 作用域與成員函數(shù)

在基類和派生類中使用同一名字的成員函數(shù),其行為與數(shù)據(jù)成員一樣:在派生類作用域中派生類成員將屏蔽基類成員。即使函數(shù)原型不同,基類成員也會(huì)被屏蔽。

4.3 重載函數(shù)

如果派生類重定義了重載成員,則通過派生類型只能訪問派生類中重定義的那些成員。

如果派生類想要通過自身類型來使用重載的版本,那么派生類必須重定義所有的重載版本,但這樣會(huì)繁瑣,可以通過給重載成員提供using 聲明來達(dá)到簡化的目的。

using Base::Func;//注意,將所有基類Base中的Func函數(shù)在本類中可見。

4.3 名字查找與繼承

(1)首先確定進(jìn)行函數(shù)調(diào)用的對象、引用或指針的靜態(tài)類型。

(2)在該類中查找函數(shù),如果找不到,就在直接基類中查找,如此循著類的繼承鏈往上找,直到找到該函數(shù)或者查找完最后一個(gè)類。如果不能在類或其相關(guān)基類中找到該名字,則調(diào)用是錯(cuò)誤的。

(3)一旦找到了該名字,就進(jìn)行常規(guī)類型檢查,查看如果給定找到的定義,該函數(shù)調(diào)用是否合法。

(4)假定函數(shù)調(diào)用合法,編譯器就生成代碼。如果函數(shù)是虛函數(shù)且通過引用或指針調(diào)用,則編譯器生成代碼以確定根據(jù)對象的動(dòng)態(tài)類型運(yùn)行哪個(gè)函數(shù)版本,否則,編譯器生成代碼直接調(diào)用函數(shù)。

5 純虛函數(shù)

在函數(shù)形參表后面寫上 = 0 以指定純虛函數(shù)。該函數(shù)為后代類型提供了可以覆蓋的接口,但是這個(gè)類中的版本決不會(huì)調(diào)用。

含有(或繼承)一個(gè)或多個(gè)純虛函數(shù)的類是抽象基類。除了作為抽象基類的派生類的對象的組成部分,不能創(chuàng)建抽象類型的對象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容