? ? ? ? 多態是面向對象開發過程中一個非常重要的概念。
11.1 多態概述
11.1.1 什么是多態
? ? ? ? 多態(polymorphism),從字面理解是“多種形態,多種形式”,是一種將不同的特殊行為泛化為當個特殊記號的機制。
多態從實現的角度可劃分為兩類:
(1)編譯時的多態:編譯過程中確定了同名操作的具體操作對象
(2)運行時的多態:程序運行時才動態確定操作所針對的具體對象
11.1.2? 多態的引入
先嘗試理解以下程序:
#include<iostream>
using namespace std;class Animal
{public:void sleep(){cout<<"Animal sleep"<<endl;}void breathe(){cout<<"Animal breath"<<endl;}
};
class Fish:public Animal
{public:void breathe(){cout<<"Fish double"<<endl;}
};
int main()
{Fish fh;Animal *an=&fh;an->breathe();return 0;
}
運行結果:
分析過程如下:
如何解決這個問題呢?可以通過多態來解決。
11.1.3 聯編
聯編是確定操作的具體對象的過程,只計算機程序自身彼此關聯的過程,即把一個標識符名和一個存儲地址聯系在一起的過程,
根據進行階段的不同,可以分為靜態聯編與動態聯編,這兩種聯編過程分別對應多態的兩種實現方式,如下圖:
11.2 函數重載
? ? ? ? 函數重載與運算符重載可以實現編譯時的多態性。以下只介紹函數重載,運算符重載見第12章。
? ? ? ? 函數重載也稱為多態函數
(1)作用:使程序能夠用同一個名字來訪問一組相關的函數,提高程序靈活性。
(2)含義:函數名相同,但函數所帶的參數個數或數據類型不同(編譯系統會根據參數來決定調用哪個同名函數)????????
? ? ? ? 函數重載表現為兩種情況:
(1)參數個數或類型有所差別的重載
(2)函數的參數完全相同但屬于不同類
第一種情況與構造函數重載類似,不作追訴,只介紹第二種情況。當函數的參數完全相同但屬于不同類時,為了讓編譯能正確區分調用哪個類的同名函數,可用以下兩種方法別:
(1)用對象名區別,在函數名前加上對象名來限制(對象名.函數名)
(2)用類名和作用域運算符區別,在函數名前加“類名::”來限制(類名::函數名)
示例代碼:
#include<iostream>
using namespace std;class Point
{int x,y;public:Point(int a,int b){x=a;y=b;}double area(){return x*y;}};
class Circle:public Point
{int r;public:Circle(int a,int b,int c):Point(a,b){r=c;}double area(){return 3.1416*r*r;}
};
int main()
{Point pob(15,15);Circle cob(20,20,10);cout<<"pob.area()="<<pob.area()<<endl<<endl;cout<<"cob.area()"<<cob.area()<<endl<<endl;cout<<"cob.Point::area()"<<cob.Point::area()<<endl<<endl;return 0;
}
運行結果:
通過函數重載方式,可以是同名函數實現不同功能,即實現了靜態多態性。
11.3 虛函數
? ? ? ? ?虛函數是實現運行時多態的一個重要方式,是重載的另一種形式。也就是動態編聯。
11.3.1 定義虛函數
? ? ? ? 虛函數的定義是在基類中進行的,即把基類中需要定義為虛函數的成員函數聲明為virtual。虛函數定義的一般形式如下:
virtual <函數類型><函數名>(參數表)
{函數體;
}
virtual void breathe()
{cout<<"Animal breathe"<<endl;
}
? ? ? ? 當基類中的某個成員函數被聲明為虛函數后,就可以在派生類中重新定義。在派生類中重新定義時,其函數原型包括返回類型、函數名、參數個數和類型,參數的順序都必須與基類中的原型完全一致。
示例代碼:(與第一個代碼做對比)
#include<iostream>
using namespace std;class Animal
{public:void sleep(){cout<<"Animal sleep"<<endl;}virtual void breathe(){cout<<"Animal breath"<<endl;}
};
class Fish:public Animal
{public:void breathe(){cout<<"Fish double"<<endl;}
};
int main()
{Fish fh;Animal *an=&fh;an->breathe();return 0;
}
運行結果:
代碼將基類中的成員函數breathe()定義為虛函數,即加上virtual關鍵字,然后在主函數main()中定義Animal對象指針指向Fish的對象fh,調用breathe()函數后,得到的結果就是預期輸出Fish bubble的結果。具體分析過程如下:
在使用派生類對象指針時應該注意:
(1)不能指向私有派生類的對象;
(2)指向公有派生類對象時,只能訪問派生類中從基類繼承下來的成員,不能直接訪問公有派生類中定義的成員。
(3)指向派生類對象的指針不能指向基類的對象;
虛函數可以很好的實現多態,在使用虛函數是應注意的問題如下:
(1)虛函數的聲明只能出現在類函數原型的聲明中,不能出現在函數體實現時;
(2)基類中只有保護成員或公有成員才能被聲明為虛函數;
(3)在派生類中重新定義虛函數時,關鍵字virtual可以寫也可以不寫,但在容易引起混亂時,應該寫上關鍵字;
(4)動態編聯只能通過成員函數來調用或通過指針、引用來訪問虛函數。
在派生類中程序定義基類中的虛函數,是函數重載的另一種形式,但它與函數重載又有區別:
(1)一般函數重載,要求其函數的參數數量或參數類型必須有所不同,函數的返回值也可以不同;
(2)重載一個虛函數時,要求函數名、返回類型、參數個數、參數的類型和參數的順序必須與基類中的虛函數的原型完全相同。
11.3.2 多級繼承和虛函數
? ? ? ? 多級繼承可以看作是多個單繼承的組合,多級繼承的虛函數與單繼承的虛函數的調用相同。即不同類創建的對象調用的函數是不一樣的。
示例代碼:
#include<iostream>
using namespace std;class Base
{public:virtual void func(){cout<<"Base output"<<endl;}};
class Derived1:public Base
{public:void func(){cout<<"Derived1 output"<<endl;}
};
class Derived2:public Derived1
{public:void func(){cout<<"Derived2 output"<<endl;}
};
void test(Base &b)
{b.func();
}int main()
{Base bObj;Derived1 d1Obj;Derived2 d2Obj;test(bObj);cout<<endl;test(d1Obj);cout<<endl;test(d2Obj);cout<<endl;return 0;
}
運行結果:
????????上述代碼中定義了一個多級繼承,在基類中定義了虛函數func(),在主函數main()中調用該函數時,不同類創建的對象調用的函數是不一樣的,即實現了多態的“一個接口,多種實現”。
11.4 純虛函數與抽象類
? ? ? ? 抽象類是一種包含純虛函數的特殊類。建立抽象類時為了多態地使用抽象類的成員函數。
11.4.1 純虛函數
? ? ? ? 在基類中不能為虛函數給出一個有意義的實現時,可以將其聲明為純虛函數。純虛函數的實現可以留給派生類來完成。純虛函數的作用是為派生類提供一個一致的接口。一般來說,一個抽象類帶有至少一個純虛函數。純虛函數的一般定義如下:
? ? ? ? 純虛函數與普通函數定義的不同在于書寫形式加了“=0”,說明在基類中不用定義改函數的函數體。
示例代碼:純虛函數的函數體由派生類定義;
#include<iostream>
using namespace std;class Point
{protected:int x0,y0;public:Point(int i=0,int j=0){x0=i;y0=j; } virtual void set()=0;//聲明純虛函數
};
class Line:public Point
{protected:int x1,y1;public:Line(int i=0,int j=0,int m=0,int n=0):Point(i,j){x1=m;y1=j;}void set()//定義接口函數{cout<<"Line::set() called \n";}
};void setobj(Point *p)
{p->set();
}int main()
{Line *lineobj = new Line;setobj(lineobj);return 0;
}
運行結果:
11.4.2 抽象類
? ? ? ? 抽象類是包含純虛函數的一種特殊類,是為了抽象和設計而建立的,處于繼承層次結果的教上層。抽象類是不能建立對象的,為了強調一個類是抽象類,可講該類的構造函數聲明為保護的訪問控制權限。抽象類的主要作用如下:
(1)將有關的類組織在一個繼承層層次結構中,由抽象類來為它們提供一個公共的根,相關的子類是從這個根派生出來的。
(2)抽象類只描述這組子類溝通的操作接口,而完整的實現留給子類。
使用抽象類是應注意:
(1)抽象類只能用做其他類的基類,不能創建抽象類的對象
(2)抽象類不能用做參數類型、函數的返回類型或顯示轉換的類型
(3)可以聲明抽象類的對象指針或對象引用,從而可以訪問派生類對象成員,實現多態編聯
(4)若派生類中沒有給出抽象類的所有純虛函數的函數體,派生類仍然是一個抽象類,若派生類中給出抽象類的所有純虛函數的函數體,則這個派生類不再是抽象類,可以創建自己的對象。
示例代碼:
#include<iostream>
using namespace std;class Vehicle
{protected:float speed;int total;public:Vehicle(float speed,int total){Vehicle::speed=speed;Vehicle::total=total;}virtual void ShowMember()=0;
};
class Car:public Vehicle
{protected:int aird;public:Car(int aird,float speed,int total):Vehicle(speed,total){Car::aird=aird;}void ShowMember(){cout<<"the speed is:"<<speed<<endl;cout<<"the total is:"<<total<<endl;cout<<"the aird is:"<<aird<<endl;}
};int main()
{//Vehicle a(100,4);Car b(250,150,4);b.ShowMember();return 0;
}
運行結果:
聲明抽象類Vehicle的對象a(100,4)時報錯,不能為抽象類聲明對象。