### C++總結-[類的繼承]


面向對象中關於繼承的總結。

#@author:       gr
#@date:         2015-07-26
#@email:        forgerui@gmail.com

一、類的隱藏

重載(overload)、覆蓋(override)與隱藏(hidden)。
重載:
相同的范圍(在同一個類中)
函數名字相同
參數不同
virtual 關鍵字可有可無

覆蓋(重寫):
不同的范圍(分別位於派生類與基類)
函數名字相同
參數相同
基類函數必須有 virtual 關鍵字

隱藏:
如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無 virtual 關鍵字,基類的函數將被隱藏(注意別與重載混淆)
如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有 virtual關鍵字。此時,基類的函數被隱藏,基類有virtual關鍵字的話就是覆蓋了。

父類是virtual方法          形參表相同  ---> 構成重寫
父類是virtual方法          形參表不同  ---> 隱藏父類方法
父類不是virtual方法        形參表相同  --->隱藏父類方法
父類不是virtual方法        形參表不同  --->隱藏父類方法

  
總結:
重載肯定是在一個域中的,C++不支持跨域重載,即子類同名函數永遠不與父類函數形成重載,而是隱藏掉父類同名函數。
覆蓋的要求比較嚴格,子類與父類只有一種情況構成重寫,三種情況下子類與父類同名函數構成隱藏。

繼承類函數會隱藏子類中所有同名的函數,即使是子類中的虛函數也會隱藏所有同名函數,即使將父類的函數聲明為virtual都不會改善這種情況。

class A {
public:
	void print() {			//加上virtual不會改善這種情況
		cout << "hello base class" << endl;
	}
};

class B : public A {
public:
	void print(int) {		//加上virtual也不會改善這種情況
		cout << "hello inheritance class" << endl;
	}
};

上面的print(int)會隱藏掉所有基類中叫print的函數。

B b;
b.print();		//報錯,子類的該函數已被隱藏

如果想要調用A中的函數,需要使用類作用域來限定:

B b;
b.A::print();		//類作用域調用

除此之外,還可以在子類中使用using聲明基類函數,如下:

class B : public A {
public:
	using A::print;			//注意這里沒有括號
	void print(int) {
		cout << "hello inheritance class" << endl;
	}
};

二、多態實現

普通函數表現在靜態綁定上,即根據當前類類型決定調用函數,而不是實際的對象類型。如下:

class A {
public:
	void print() {
		cout << "hello base class" << endl;
	}
};

class B : public A {
public:
	void print() {
		cout << "hello inheritance class" << endl;
	}
};

void main() {
	A* pA = new B();
	pA->print();			//此句會調用A::print()
}

上面pA只會調用A類的print()函數,如果我們想根據實際對象的類型來調用相應的函數,那么就需要進行動態綁定。將基類的print()函數聲明為virtual,可以解決這個問題。

class A {
public:
	virtual void print() {
		cout << "hello base class" << endl;
	}
};
class B : public A {
public:
	void print() {
		cout << "hello inheritance class" << endl;
	}
};

void main() {
	A* pA = new B();
	pA->print();			//根據對象的實際類型調用B::print()
}

虛函數的成本:

當然,"天下沒有免費的午餐"。實現這種功能必須花費一定的成本。
一般函數是不會增加對象空間的,一般C++對象的大小由非靜態成員變量,虛函數,以及對齊決定。

而使用虛函數,主要有兩個成本

  1. 一個類會在內存中建立一個虛函數表(vtbl),用來記錄虛函數信息,所有該類對象共享該虛函數表。類的虛函數表是一塊連續的內存,每個內存單元中記錄一個JMP指令的地址,也就是每個函數的地址。
  2. 其次,會在每個類對象內增加一個指針(vptr, 32位的話占用4個字節),用以指向虛函數表。

因為基類、子類有兩個不同的虛函數表,且兩個虛函數指針指向各自的虛函數表,所以在運行時可以體現出多態。同理,如果作為函數參數,不能以值傳遞,這樣對象會被slice,必須傳遞指針或引用。虛函數需要在運行時才能確定運行的函數,但在編譯時就要給出可以執行的命令,主要是如下幾步:

  1. 根據pC1所指對象的虛函數指針(vptr)索引到虛函數表(vtbl)。
  2. 在虛函數表中找到函數的偏移,記為i。
  3. 調用第二步中的函數,*(pC1->vptr[i])(pC1);

三、類的權限控制

  1. 繼承方式

    public繼承: public => public, protect =>protect, private => 不可訪問
    protect繼承:public => protect, protect =>protect, private => 不可訪問
    private繼承:public => private, protect => private, private => 不可訪問

  2. 子類成員

    public成員:public => public, protect => protect, private => private
    protect成員:public => protect, protect => protect, private => private
    private成員:都不可訪問

默認繼承方式是private繼承(所以如果需要使用子類成員,需要public繼承),默認成員是private。

四、虛基類

含有純虛函數的類叫做虛基類,類似Java里的接口(interface)。虛基類無法被實例化,只能通過繼承,實現子類。

class AbstractBase {
public:
	virtual void solve() = 0;         //純虛函數
	virtual ~AbstractBase(){}
};
class Derivation : public class AbstractBase {
public:
	void solve(){}
};

int main() {
	AbstractBase* thing = new Derivation();
	thing->solve();
}

五、繼承與組合

繼承是is a關系,組合是has a關系。有時需要根據事實正確塑模出其關系。

六、多繼承與虛繼承

為了滿足一個類包含兩個類的性質,C++提供多繼承。
為了解決多繼承帶來的包含多個基類問題,使用虛繼承,可以只包含一個基類。

class A {};
class B1 : virtual public A {};    //虛繼承
class B2 : virtual public A {};    //虛繼承
class C : public B1, public B2 {}; //使用虛繼承,C中只包含一個A


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM