C++ 繼承、多型、虛擬函式

鋸齒流沙發表於2017-12-13

所謂繼承,是指在一個已存在類的基礎上建立一個新類。已存在的類稱為基類(base class)或父類,新建立的類稱為派生類(derived class)或子類。這樣,子類與父類就形成了層次關係(用箭頭表示繼承方向) :

C++.png

而派生的概念則是從父類角度看待繼承所形成的,它與繼承的差異只是看待問題的角度不相同:一個從下往上看,另一個從上往下看, 本質上指的是同一事物。

繼承性是物件導向程式設計最重要的特徵之一

繼承抽取了類之間的共同點,減少了程式碼冗餘,是實現軟體重用的重要手段,繼承減少了程式碼冗餘;

繼承還是實現軟體功能擴充的重要手段;

繼承反映了類的層次結構,並支援對事物從一般到特殊的描述,這符合人的認知規律和行動準則。

說明:

1、繼承方式有三種,即:public(公有繼承)、private(私有繼承)、protected(保護繼承),如果省略,則預設為private繼承方式;

2、多重繼承方式下,各基類之間要用逗號分開,每一個基類都有自己的繼承方式,它們互不影響;

3、派生類只要寫出其新增成員的宣告或定義即可,基類的內容不必重複。

繼承的訪問修飾:

基類中      繼承方式            子類中

public     & public繼承        => public

public     & protected繼承     => protected   

public     & private繼承       => private

protected  & public繼承        => protected

protected  & protected繼承     => protected   

protected  & private繼承       => private

private    & public繼承        => 子類無權訪問

private    & protected繼承     => 子類無權訪問

private    & private繼承       => 子類無權訪問
複製程式碼
class Box{
private:
	double length;
protected:
	double height;
public:
	double width;
	double getLength();
	double getHeight();
	double getWidth();
	Box(double l,double h,double w){
		this->length = l;
		this->width = w;
		this->height = h;
	}
};

double Box::getLength(){
	return length;
}

double Box::getHeight(){
	return height;
}
double Box::getWidth(){
	return width;
}

class ColorBox : public Box{
private:
	int color;
public:
	int getColor();
	ColorBox(double l, double w, double h, int c) : Box(l, w, h){
		this->color = c;
	};
};
int ColorBox::getColor(){
	return color;
}


void main(){
	ColorBox b(0.2,0.6,0.5,20);

	cout << "ColorBox--->width:"<< b.getWidth() << endl;

	b.Box::width = 100.5;

	cout << "Box----->width:" <<b.Box::getWidth() << endl;

	system("pause");
}
複製程式碼

C++.png

單一繼承下的解構函式

說明

1、派生類與基類的解構函式沒有什麼聯絡,彼此獨立,派生類或基類的解構函式只做各自類物件消亡前的清理工作;

2、在派生過程中,基類的解構函式不能繼承,如果需要解構函式的話,就要在派生類中重新定義;

3、派生類解構函式的定義方法與沒有繼承關係的類中解構函式的定義方法完全相同,只要在函式體中負責把派生類新增的非物件成員的清理工作做好就夠了,系統會自己呼叫基類及子物件的解構函式來對基類及子物件進行清理。

解構函式的執行順序:

a、與建構函式的執行順序正好相反

b、先析構派生類自己;

c、再析構各個子物件:如果有多個子物件,析構順序與這些子物件在類中的說明次序相反;

d、最後才是析構基類。

多繼承

//人
class Person{

};

//公民
class Citizen{

};

//學生,既是人,又是公民
class Student : public Person, public Citizen{

};
複製程式碼

多重繼承的定義格式

class 派生類名 :[繼承方式] 基類名1,[繼承方式] 基類名2,……
{
	// 派生類成員宣告;
};
複製程式碼

多重繼承下的建構函式和解構函式

建構函式:

a、當基類中宣告有預設形式的建構函式,派生類建構函式可以不向基類建構函式傳遞引數;

b、若基類中未宣告建構函式,派生類中也可以不宣告建構函式,全採用預設形式的建構函式;

c、當基類宣告有帶形參的建構函式時,派生類也應宣告帶形參的建構函式,並將引數傳遞給基類建構函式。

派生類建構函式的定義格式:

派生類建構函式(總參數列) :基類1建構函式(參數列), ..., 基類n建構函式(參數列), 子物件1的建構函式(參數列), ..., 子物件k的建構函式(參數列)
{
	//派生類新增成員的初始化語句;
}
複製程式碼

多重繼承下派生類建構函式的執行順序:

1)先構造基類:按照派生類定義時各基類在冒號後的宣告順序執行 ;

2)再構造子物件:按照各子物件在派生類的說明順序執行 ;

3)最後才是構造派生類本身 。

解構函式

1)與它的建構函式執行順序相反 :
2)先析構派生類自己;
3)再析構各子物件;
4)最後才是析構各基類。
複製程式碼

繼承的二義性

繼承的二義性.png

當一個派生類是由多個基類派生而來時,如果這些基類中的成員有一些的名稱相同,那麼使用一個表示式引用了這些同名的成員,就會出現無法確定是引用哪個基類成員的情況,這就是對基類成員訪問的二義性。

要避免此種情況,可以在成員名前用物件名及基類名來限定。

格式:
物件名.基類名::成員名
物件名.基類名::成員函式名(參數列)
複製程式碼
class A{
public:
	char* name;
};

class A1 : virtual public A{
	
};

class A2 : virtual public A{

};

class B : public A1, public A2{

};

void main(){
	B b;	
	b.name = "jason";
	//指定父類顯示呼叫
	//b.A1::name = "xiaoming";
	//b.A2::name = "xiaoming";
	system("pause");
}
複製程式碼

支配規則:一個派生類中的名字將優先於它的基類中相同的名字,這時二者之間不存在二義性,當選擇該名字時,使用支配者(派生類中)的名字,這稱為支配規則。

可見性原則:在外層(基類)宣告的識別符號,如果在內層(派生類)沒有同名的識別符號,則它在內層(派生類)仍可見;如果內層(派生類)宣告瞭同名的識別符號,則外層(基類)的識別符號不可見,即它被內層(派生類)同名識別符號所覆蓋。

虛基類

如果一個派生類從多個基類中派生,而這些基類又有一個共同的基類,則在這個派生類將保留共同基類的多份副本。

如下圖所示:

C++.png

要讓派生類只保留共同基類的一份副本,可以讓這個共同基類說明為虛基類。

虛基類的定義格式

格式: class 派生類 :virtual[繼承方式] 基類名

C++.png

虛基類及其派生類的建構函式和解構函式

建構函式

1)要求虛基類的建構函式只能呼叫一次。直接或間接繼承虛基類的所有派生類,都必須在建構函式的成員初始化表中列出對虛基類的初始化。因此,要注意以下幾點;

2)虛基類的建構函式在所有非虛基類之前呼叫;

3)若同一層次中包含多個虛基類,這些虛基類的建構函式按它們說明的次序呼叫;

4)若虛基類由非虛基派生而來,則先呼叫基類建構函式,再呼叫派生類的建構函式。

解構函式 呼叫順序正好與它的建構函式呼叫順序相反

多型性

顧名思義,多型就是“多種形態”的意思。它是物件導向程式設計的一個重要特徵。

在物件導向方法中一般是這樣表述多型性的:同樣的訊息被不同型別的物件接收時導致的不同行為。所謂訊息是指對類的成員函式的呼叫,不同行為是指不同的實現,也就是呼叫了不同的函式。

多型的型別:

過載多型:普通函式或類的成員函式過載就屬於這種型別

強制多型:強制資料型別的變化,以適用函式或操作的要求

包含多型:類族中定義於不同類的同名成員函式的多型行為,主要通過虛擬函式來實現

引數多型:類别範本屬於這種型別,使用實際的型別才能例項化

多型的實現

C++的多型性有兩類:

1、靜態多型性:也就是靜態聯編下實現的多型性,即是在程式編譯時就能確定呼叫哪一個函式,函式過載和運算子過載實現的就是靜態多型性;

2、動態多型性:也就是動態聯編(虛擬函式)下實現的多型性,它只有在程式執行時才解決函式的呼叫問題,虛擬函式是實現動態多型性的基礎

什麼是聯編 ? 又稱為關聯或繫結,是描述編譯器決定在程式執行時,一個函式呼叫應執行哪段程式碼的一個術語,它把一個識別符號與一個儲存地址聯絡起來。

發生動態的條件:

1.繼承
2.父類的引用或者指標指向子類的物件
3.函式的重寫
複製程式碼

虛擬函式

宣告格式:

virtual 函式返回型別 函式名(形參表)
{
	//函式體
}
複製程式碼

說明:

1、只有類的成員函式才能宣告為虛擬函式,普通函式不存在繼承關係,不能宣告為虛擬函式;

2、virtual關鍵字出現在虛擬函式的宣告處,在虛擬函式的類外定義時不加virtual;

3、靜態成員函式不能宣告為虛擬函式;

4、行內函數不能宣告為虛擬函式;

5、建構函式也不能宣告為虛擬函式,因為它是在物件產生之前執行的函式;

6、解構函式可以是虛擬函式而且通常宣告為虛擬函式。

#pragma once
//普通飛機
class Plane{
public:
	virtual void fly();
	virtual void land();
};
複製程式碼
#include "Plane.h"
#include <iostream>
using namespace std;

void Plane::fly(){
	cout << "起飛" << endl;
}
void Plane::land(){
	cout << "著陸" << endl;
}
複製程式碼
#pragma once

#include "Plane.h"

//直升飛機
class Jet : public Plane{
	virtual void fly();
	virtual void land();
};
複製程式碼
#include "Jet.h"
#include <iostream>
using namespace std;

void Jet::fly(){
	cout << "直升飛機在原地起飛..." << endl;
}
void Jet::land(){
	cout << "直升飛機降落在女神的屋頂..." << endl;
}
複製程式碼
#pragma once

#include "Plane.h"

//噴氣式飛機
class Copter :public Plane{
public:
	virtual void fly();
	virtual void land();
};
複製程式碼
#include "Copter.h"
#include <iostream>
using namespace std;

void Copter::fly(){
	cout << "噴氣式飛機在跑道上起飛..." << endl;
}

void Copter::land(){
	cout << "噴氣式飛機在跑道上降落..." << endl;
}
複製程式碼
void bizPlay(Plane& p){
	p.fly();
	p.land();
}


void main(){
	
	Plane p1;
	bizPlay(p1);

	Jet p2;
	bizPlay(p2);

	Copter p3;
	bizPlay(p3);

	system("pause");
}
複製程式碼

C++.png

為什麼要用指標->虛擬函式()方式而不是 物件.虛擬函式()方式來呼叫虛擬函式 ?

如果採用物件.虛擬函式()方式呼叫,只能得到一個個具體類的結果,不具備“跨類”功能。相反,指標則有“跨類”的能力,除此之外,引用也具備這種能力,以後我們將把指標、引用同等看待。

虛解構函式

為什麼要引入虛解構函式 ?

用new命令建立派生類物件時返回的是派生類指標,根據賦值相容規則,可以把派生類指標賦給基類指標。當用delete 基類指標 來刪除派生類物件時,只呼叫基類的解構函式, 不能釋放派生類物件自身佔有的記憶體空間。

這一問題在引進虛解構函式後能夠得到解決。

虛解構函式的宣告格式 :
virtual ~類名()
{
	函式體
}
複製程式碼

虛解構函式與一般虛擬函式的不同之處 :

當基類的解構函式被宣告為虛擬函式時,它的派生類的解構函式也自動成為虛擬函式,這些解構函式不要求同名;

一個虛解構函式的版本被呼叫執行後,接著就要呼叫執行基類版本,依此類推,直到執行到派生序列的最開始的那個解構函式的版本為止,也即說派生類解構函式、基類解構函式能夠依次被執行。

何時需要虛解構函式?

通過基類指標刪除派生類物件時;

通過基類指標呼叫物件的解構函式。

純虛擬函式和抽象類

純虛擬函式

1)在某些情況下, 在基類中不能為虛擬函式提供具體定義, 這時可以把它說明為純虛擬函式。它的定義留給派生類來完成。

2)純虛擬函式的宣告格式:

class 類名
{
	...
	virtual 返回型別 函式名(形參表) = 0;
	...
}
複製程式碼

注意:空虛擬函式與純虛擬函式的區別。

說明

1、純虛擬函式沒有也不允許有函式體,如果強行給它加上將會出現錯誤;

2、最後的“ = 0”並不表示函式的返回值為0,它只是形式上的作用,告訴編譯系統“這是純虛擬函式”;

3、是一個純虛擬函式的宣告語句,它的末尾應有分號;

4、純虛擬函式的作用:在基類中為派生類保留一個虛擬函式的名字,以便派生類根據需要進行定義。如果在基類沒有保留虛擬函式的名字,則無法實現多型性。

5、如果在一個類中宣告瞭純虛擬函式,而在其派生類中沒有對該函式進行定義,則該虛擬函式在派生類中仍然為純虛擬函式。

class Shape{
public:
	//純虛擬函式
	virtual void sayArea() = 0;
	void print(){
		cout << "hi" << endl;
	}
};

//圓
class Circle : public Shape{
public:
	Circle(int r){
		this->r = r;
	}
	void sayArea(){
		cout << "圓的面積:" << (3.14 * r * r) << endl;
	}
private:
	int r;
};

void main(){
	//Shape s;
	Circle c(10);
	c.sayArea();
	system("pause");
}
複製程式碼

C++.png

抽象類:

1.當一個類具有一個純虛擬函式,這個類就是抽象類

2.抽象類不能例項化物件

3.子類繼承抽象類,必須要實現純虛擬函式,如果沒有,子類也是抽象類

抽象類的作用:為了繼承約束,根本不知道未來的實現

抽象類不能用作引數型別、函式返回型別或強制型別轉換,但可以宣告抽象類的指標或引用。

相關文章