程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++學習筆記(12.繼承與多態)

c++學習筆記(12.繼承與多態)

編輯:C++入門知識

本節知識點:

1.函數重寫:

a.通過上篇文章 我們可以知道,子類與父類可以定義同名成員變量子類依然繼承了父類中的同名變量並且在默認情況下子類中的同名成員變量隱藏了父類的同名成員變量如果想訪問父類的同名成員變量,就要使用作用域分別符號。 b.子類和父類中的同名成員函數: 第一,函數參數相同的情況,這樣就在子類和父類之間出現了,除了函數體以外,原型完全相同的函數。這種情況叫做函數的重寫對於重寫,子類中的同名成員函數會隱藏父類中的同名成員函數,如果想調用父類中的同名成員函數,就要使用作用域分別符號。注意:函數重寫只發生在父類與子類之間 第二,函數參數不相同的情況切記此時即不發生函數的重寫,也不發生函數的重載(子類與父類之間是不發生重載的),但是因為函數名字相同,子類中的函數會隱藏父類中的同名成員函數 總結下:子類與父類中,只要存在同名成員,子類中的成員就會隱藏父類中的成員。同名成員函數和同名成員變量都一樣。函數參數相同的同名成員函數還會發生函數的重寫 示例代碼:
#include 

using namespace std;

class parent
{
public:
	void fun(int a) //這兩個函數發生了重載 
	{
		cout << "parent fun() " << a << endl;
	}
	void fun()
	{
		cout << "parent fun() " << endl;
	}
};
class child : public parent
{
public:
	void fun() //與上面父類中的函數發生了重寫 
	{
		cout << "child fun() " << endl;
	}
};
int main()
{
	child c;
	c.parent::fun(4); //這裡發生了函數的重載 
	c.parent::fun();
	c.fun();    //發生了函數的重寫  隱藏了父類中的同名函數 
	//c.fun(4); //這裡會報錯 因為沒有發生函數重載  被子類的函數隱藏了 
	return 0;
}
c.父類中被重寫的函數依然會繼承給子類,僅僅是被子類中的函數隱藏了。但是可以通過作用域分辨符:: 來訪問父類中被隱藏的函數

2.函數重寫與賦值兼容性:

a.對於類的賦值兼容性,父類的指針或引用,指向子類的對象,如:parent *p = &c 或 parent &p = c 此時的p它不能訪問子類中獨特的成員,它只能訪問父類繼承給子類的那些成員。其實在賦值的過程中,應該是隱藏了一個類似強制類型轉換的過程就像把一個int的變量賦值給一個char的變量一樣要進行切割的如果父類和子類之間存在同名成員,能訪問的應該都是父類的同名成員(前提是沒有虛函數設置)當p進行訪問的時候,先去判斷訪問成員,是不是父類繼承給子類的,不是,直接就會報錯,說成員不存在(因為p的類型是parent類型) 。然後再去判斷父類子類直接有沒有同名的,不同名的,子類會賦值給父類。同名的,會直接調用父類的同名成員,忽略子類的成員。 示例代碼:
#include 

using namespace std;

class parent 
{
public:
	int a;
	int b;
	void fun()
	{
		cout << "parent fun " << endl;
	}
};

class child : public parent
{
public:
	int a;
	void fun()
	{
		cout << "child fun " << endl;
	} 
};
int main()
{
	child  c;
	c.a = 10;
	c.b = 100;
	parent *p = &c;
	parent &p1 = c;
	cout << p->a << endl;
	cout << p1.a << endl;
	cout << p1.b << endl;
	cout << p->b << endl;
	
	p->fun();
	p1.fun();
	return 0;
} 
注意:如果子類與父類存在同名成員,child c子類對象去訪問同名成員,訪問到的一定是子類中的同名成員,把父類繼承過來的同名成員隱藏(此時是子類的類型)。如果父類的指針或引用指向子類對象的時候,存在同名成員,通過指針或引用訪問到的一定是父類的同名成員,子類的同名成員不是被隱藏了,而是根本就沒有賦值過來(此時是父類的類型)。這裡就說明一個問題,變量的類型決定變量的行為!!!(但是前提一定是沒有設置虛函數)
b.c語言和c++語言都是靜態編譯型語言所謂靜態編譯型語言就是在編譯前清楚變量函數的類型,根據確定下來的變量函數的類型進行編譯。所以說,在默認的情況下,由於程序沒有運行,不可能知道父類指針指向的具體是父類對象還是子類對象,一律認為父類指針指向的是父類對象,因此編譯的結果為調用父類的成員函數。

3.多態的本質:

a.如何根據不同對象的類型來判斷重寫函數的調用,這是面向對象中多態的概念。如果父類指針指向的是父類對象,則調用父類中定義的函數。如果父類指針指向的是子類對象,則調用子類中定義的重寫函數。 b.多態,根據實際的對象類型決定函數調用語句的具體調用目標,打破了c++靜態編譯的弊端。使得同樣的調用語句有多種不同的表現形式。 \
c.使用虛函數,讓重寫後的函數具有多態的特性。c++中通過virtual關鍵字對多態進行支持,使用virtual聲明的函數被重寫後即可展現多態特性 示例代碼:
#include 

using namespace std;

class parent
{
public:
	virtual void fun()
	{
		cout << "parent fun() " << endl;
	}	
};
class child : public parent
{
public:
  	virtual void fun()
	{ 
		cout << "child fun() " << endl;
	}
};
int main()
{
	child c;
	parent *p = &c;
	p->fun(); //同樣的語句  有不同的表現形式
	 
	parent d;
	p = &d;
	p->fun();//次條語句 具有多態的特性 
	return 0;
}
注意:對於虛函數的聲明,其實僅僅在父類中聲明就可以了,不用再在子類中進行聲明了。也可以即在父類中聲明,也在子類中聲明。

4.重寫與重載:

a.在闡述重寫與重載之前,要先說明一些概念。在一個類裡面,肯定是不允許存在同名函數或變量的。但是父類和子類對象之間,雖然說子類是特殊的父類,但是畢竟他倆是相對獨立的,這樣就允許子類與父類之間存在同名成員了同時子類還會默認隱藏父類的同名成員子類中依然繼承了父類的同名成員可以通過作用域分別符進行訪問對於同名成員函數,就存在兩種情況,參數相同的就發生了函數的重寫。對於參數不相同的,切記,這裡沒有發生重載(因為子類和父類之間是不可以發生重載的),雖然說參數不相同就是兩個完全不相同的函數,但是函數名字相同就發生了隱藏。兩者的區別就在,函數的重寫是允許父類和子類之間存在完全相同的函數結構。總之,c++允許子類與父類之間,存在完全相同的同名成員函數(即函數的重寫)和同名成員變量,也允許存在參數不同的同名成員函數(其實允許是合理的),但不發生函數重載,對於同名成員一律進行對父類的隱藏,並可以通過作用域分別符進行訪問。 b.比較重載與重寫: 函數重載:必須在同一個作用域。子類無法重載父類函數,父類同名函數將被隱藏。重載是在編譯期間靜態的根據參數類型和個數決定調用哪個函數的。 函數重寫:必須發生在父類與子類之間。並且父類與子類之間的函數必須有完全相同的函數原型。使用virtual關鍵字聲明後能夠產生多態。多態是在運行期間動態的根據具體對象的類型決定調用函數的。

5.深入理解虛函數:

a.c++中多態的實現原理:當類中聲明虛函數時,編譯器會在類中生成一個虛函數表。虛函數表是一個存儲類成員虛函數的函數指針的數據結構(實際上是一個鏈表)。虛函數表是由編譯器自動生成與維護的。virtual成員函數會被編譯器放入虛函數表中。存在虛函數時,每個對象都有一個指向虛函數表的指針(即VPTR指針)
b.多態實現的過程:首先得有兩個以上具有繼承關系的類(一個父類與多個子類是個經典的例子)然後類之間存在著函數的重寫。再把重寫的函數定義為虛函數。因為定義了virtual成員函數,編譯器就會在這些類創建對象時分別產生各自的虛函數表。當然,各自的虛函數表需要各自的虛函數表的指針(VPTR指針是類的一個成員,且還是第一個成員)。最重要的是,在調用多態的這些函數時,編譯器首先判斷函數是否為虛函數。如果是,就利用此時的對象的VPTR指針所指向的虛函數表中查找這個函數,並調用,查找和調用是在程序運行時完成的(注意對象是如何找到VPTR指針的,這個指針首先是不可以外界調用的,當類中有函數聲明為virtual屬性時,類中就會多一個成員變量,即VPTR指針,且這個指針放在了類的第一個成員的位置,也就是對象的地址就是VPTR指針的地址,當對象創建時,也就是調用了類的構造函數之後就創建了這個對象的虛函數表,此時就給VPTR指針進行了賦值,使其指向對象的虛函數表)。如果不是,編譯器就可以直接調用本類型類的成員函數。 如圖: \

示例代碼:
#include 

using namespace std;
class parent
{
public:
	int a;
	virtual void fun()
	{
		cout << "parent fun()" <fun();
	return 0;
} 
注意:出於效率的考慮,沒有必要將所有成員函數都聲明為虛函數。通過虛函數表指針VPTR調用重寫函數是在程序運行時進行的因此需要通過尋址操作才能確定真正應該調用的函數。而普通成員函數是在編譯時就確定了調用的函數。在效率上,虛函數的效率要低很多。 c.虛函數與多態的關系:多態是依靠虛函數實現的。但是就算我不想實現多態,依然可以在類中定義虛函數的,類創建對象時,依然產生虛函數表,依然存在VPTR指針。 d.對象在創建的時候由編譯器對VPTR指針進行初始化,只有當對象的構造完全結束後VPTR的指向才最終確定,父類對象的VPTR指向父類虛函數表,子類對象的VPTR指向子類虛函數表。 e.構造函數中調用虛函數無法實現多態:說明下這個知識點,是本節的一個疑問。如果有大神路過,希望大神指點下。也希望再日後的學習中,自己可以補充上來!!!對於唐老師的 構造函數與VPTR指針之間的關系分析(唐老師的白板圖)、還有那個this指針的舉例的觀點(唐老師的代碼),我有所保留!!!待日後提高!!!這裡面有一個不知對錯的反例,代碼如下:
#include 

using namespace std;

class parent
{
public:
	parent()
	{
	cout << "hello parent()" << endl;
	}
	virtual void fun()
	{
		cout << "parent fun() " <fun();
	}
	void fun()
	{
		cout << "child fun() " << endl;
	}
}; 
int main()
{	
	parent p;
	child c(&p);
	
	child c1(&c);

}
雖然不確定,但是依然記住構造函數中無法使用虛函數完成多態。

6.純虛函數:

a.面向對象中的抽象類:抽象類可以用於表示現實世界中的抽象概念。抽象類是一種只能定義類型,而不能產生對象的類。抽象類只能被繼承並重寫相關函數。抽象類的直接特征是純虛函數。 b.純虛函數:只聲明函數原型,而故意不定義函數體的虛函數 c.抽象類與純虛函數: 第一:抽象類不能定義對象(因為抽象類,就是利用繼承,讓自己這個父類的純虛函數進行重寫,使得多個子類產生多態的現象),抽象類的目的不是創建對象,而是使子類產生多態。 第二:抽象類只能定義指針和引用 第三:抽象類中的純虛函數必須被子類重寫 第四:因為有純虛函數的存在,所以類變成了抽象類,因為純虛函數沒有函數體且不能被調用,所以抽象類也不能定義對象(純虛函數存在的目的,就是讓抽象類的子類進行函數重寫,從而實現多態),抽象類定義了對象就有可能調用純虛函數! d.純虛函數的示例:
class shape
{
public:
	virtual double area() = 0;
};
注意:area為純虛函數,= 0 是告訴編譯器,這個函數是純虛函數,故意只聲明不定義函數體
示例代碼(這是一個很好的抽象類的例子,也是一個很不錯的多態的例子):
#include 

using namespace std;

class Shape
{
public:
    virtual double area() = 0;
};

class Rectangle : public Shape
{
    double m_a;
    double m_b;
public:
    Rectangle(double a, double b)
    {
        m_a = a;
        m_b = b;
    }
    
    double area()
    {
        return m_a * m_b;
    }
};

class Circle : public Shape
{
    double m_r;
public:
    Circle(double r)
    {
        m_r = r;
    }
    
    double area()
    {
        return 3.14 * m_r * m_r;
    }
};

void area(Shape* s)
{
    cout<area()<



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved