本章內(nèi)容為coursera課程C++程序設(shè)計中第六周的課件的整理
一、虛函數(shù)和多態(tài)
在類的定義中,前面有 virtual 關(guān)鍵字的成員函數(shù)就是虛函數(shù)。
class base {virtual int get() ;};int base::get(){ }virtual 關(guān)鍵字只用在類定義里的函數(shù)聲明中,寫函數(shù)體時不用。多態(tài)的表現(xiàn)形式一派生類的指針可以賦給基類指針。通過基類指針調(diào)用基類和派生類中的同名虛函數(shù)時:(1)若該指針指向一個基類的對象,那么被調(diào)用是基類的虛函數(shù);(2)若該指針指向一個派生類的對象,那么被調(diào)用的是派生類的虛函數(shù)。這種機(jī)制就叫做“多態(tài)”。
class CBase {public:virtual void SomeVirtualFunction() { }};class CDerived:public CBase {public :virtual void SomeVirtualFunction() { }};int main() {CDerived ODerived;CBase * p = & ODerived;p -> SomeVirtualFunction(); //調(diào)用哪個虛函數(shù)取決于p指向哪種類型的對象return 0;}多態(tài)的表現(xiàn)形式二派生類的對象可以賦給基類引用。通過基類引用調(diào)用基類和派生類中的同名虛函數(shù)時:(1)若該引用引用的是一個基類的對象,那么被調(diào)用是基類的虛函數(shù);(2)若該引用引用的是一個派生類的對象,那么被調(diào)用的是派生類的虛函數(shù)。這種機(jī)制也叫做“多態(tài)”。class CBase {public:virtual void SomeVirtualFunction() { }};class CDerived:public CBase {public :virtual void SomeVirtualFunction() { }};int main() {CDerived ODerived;CBase & r = ODerived;r.SomeVirtualFunction(); //調(diào)用哪個虛函數(shù)取決于r引用哪種類型的對象return 0;}多態(tài)的簡單示例class A {public :virtual void PRint( ){ cout << "A::Print"<<endl ; }};class B: public A {public :virtual void Print( ) { cout << "B::Print" <<endl; }};class D: public A {public:virtual void Print( ) { cout << "D::Print" << endl ; }};class E: public B {virtual void Print( ) { cout << "E::Print" << endl ; }};int main() {A a; B b; E e; D d;A * pa = &a; B * pb = &b;D * pd = &d ; E * pe = &e;pa->Print(); // a.Print()被調(diào)用,輸出: A::Printpa = pb;pa -> Print(); //b.Print()被調(diào)用,輸出: B::Printpa = pd;pa -> Print(); //d. Print ()被調(diào)用,輸出: D::Printpa = pe;pa -> Print(); //e.Print () 被調(diào)用,輸出: E::Printreturn 0;}
多態(tài)的作用在面向?qū)ο蟮某绦蛟O(shè)計中使用多態(tài),能夠增強程序的可擴(kuò)充性,即程序需要修改或增加功能的時候,需要改動和增加的代碼較少。二、更多多態(tài)程序?qū)嵗?/strong>
幾何形體處理程序幾何形體處理程序: 輸入若干個幾何形體的參數(shù),要求按面積排序輸出。輸出時要指明形狀。Input:第一行是幾何形體數(shù)目n(不超過100).下面有n行,每行以一個字母c開頭.若 c 是 ‘R’ ,則代表一個矩形,本行后面跟著兩個整數(shù),分別是矩形的寬和高;若 c 是 ‘C’ ,則代表一個圓,本行后面跟著一個整數(shù)代表其半徑若 c 是 ‘T’ ,則代表一個三角形,本行后面跟著三個整數(shù),代表三條邊的長度Output:按面積從小到大依次輸出每個幾何形體的種類及面積。每行一個幾何形體,輸出格式為:形體名稱:面積
Sample Input:
3R 3 5C 9T 3 4 5Sample OutputTriangle:6Rectangle:15Circle:254.34
#include <iostream>#include <stdlib.h>#include <math.h>using namespace std;class CShape{public:virtual double Area() = 0; //純虛函數(shù)virtual void PrintInfo() = 0;};class CRectangle:public CShape{public:int w,h;virtual double Area();virtual void PrintInfo();};class CCircle:public CShape {public:int r;virtual double Area();virtual void PrintInfo();};class CTriangle:public CShape {public:int a,b,c;virtual double Area();virtual void PrintInfo();}; double CRectangle::Area() {return w * h;}void CRectangle::PrintInfo() {cout << "Rectangle:" << Area() << endl;}double CCircle::Area() {return 3.14 * r * r ;}void CCircle::PrintInfo() {cout << "Circle:" << Area() << endl;}double CTriangle::Area() {double p = ( a + b + c) / 2.0;return sqrt(p * ( p - a)*(p- b)*(p - c));}void CTriangle::PrintInfo() {cout << "Triangle:" << Area() << endl;}CShape * pShapes[100];int MyCompare(const void * s1, const void * s2);int main(){int i; int n;CRectangle * pr; CCircle * pc; CTriangle * pt;cin >> n;for( i = 0;i < n;i ++ ) {char c;cin >> c;switch(c) {case 'R':pr = new CRectangle();cin >> pr->w >> pr->h;pShapes[i] = pr;break;case 'C':pc = new CCircle();cin >> pc->r;pShapes[i] = pc;break;case 'T':pt = new CTriangle();cin >> pt->a >> pt->b >> pt->c;pShapes[i] = pt;break;}}qsort(pShapes,n,sizeof( CShape*),MyCompare);for( i = 0;i <n;i ++)pShapes[i]->PrintInfo();return 0;}int MyCompare(const void * s1, const void * s2){double a1,a2;CShape * * p1 ; // s1,s2 是 void * ,不可寫 “ * s1”來取得s1指向的內(nèi)容CShape * * p2;p1 = ( CShape * * ) s1; //s1,s2指向pShapes數(shù)組中的元素,數(shù)組元素的類型是CShape *p2 = ( CShape * * ) s2; // 故 p1,p2都是指向指針的指針,類型為 CShape **a1 = (*p1)->Area(); // * p1 的類型是 Cshape * ,是基類指針,故此句為多態(tài)a2 = (*p2)->Area();if( a1 < a2 )return -1;else if ( a2 < a1 )return 1;elsereturn 0;}case 'C':pc = new CCircle();cin >> pc->r;pShapes[i] = pc;break;case 'T':pt = new CTriangle();cin >> pt->a >> pt->b >> pt->c;pShapes[i] = pt;break;}}qsort(pShapes,n,sizeof( CShape*),MyCompare);for( i = 0;i <n;i ++)pShapes[i]->PrintInfo();return 0;}如果添加新的幾何形體,比如五邊形,則只需要從CShape派生出CPentagon,以及在main中的
switch語句中增加一個case,其余部分不變。用基類指針數(shù)組存放指向各種派生類對象的指針,然后遍歷該數(shù)組,就能對各個派生類對象做各種操作,是很常用的做法。
多態(tài)的又一例子
class Base {public:void fun1() { this->fun2(); } //this是基類指針, fun2是虛函數(shù),所以是多態(tài)virtual void fun2() { cout << "Base::fun2()" << endl; }};class Derived:public Base {public:virtual void fun2() { cout << "Derived:fun2()" << endl; }};int main() {Derived d;Base * pBase = & d;pBase->fun1();return 0;}輸出: Derived:fun2()在非構(gòu)造函數(shù),非析構(gòu)函數(shù)的成員函數(shù)中調(diào)用虛函數(shù),是多態(tài)!!!構(gòu)造函數(shù)和析構(gòu)函數(shù)中調(diào)用虛函數(shù)在構(gòu)造函數(shù)和析構(gòu)函數(shù)中調(diào)用虛函數(shù),不是多態(tài)。編譯時即可確定,調(diào)用的函數(shù)是自己的類或基類中定義的函數(shù),不會等到運行時才決定調(diào)用自己的還是派生類的函數(shù)。//因為先執(zhí)行自己的類或基類的構(gòu)造函數(shù),而派生類的構(gòu)造函數(shù)后執(zhí)行,派生類自己的那一部分成員變量實際上是還沒有被初始話的。如果在基類的構(gòu)造函數(shù)中調(diào)用了虛函數(shù),而又允許是多態(tài)的話,那么在基類構(gòu)造函數(shù)執(zhí)行期間,會調(diào)用派生類的成員函數(shù),可能會出錯。class myclass {public:virtual void hello(){cout<<"hello from myclass"<<endl; };virtual void bye(){cout<<"bye from myclass"<<endl;}};class son:public myclass{ public:void hello(){ cout<<"hello from son"<<endl;};son(){ hello(); };~son(){ bye(); };};//派生類中和基類中虛函數(shù)同名同參數(shù)表的函數(shù),不加virtual也自動成為虛函數(shù)class grandson:public son{ public:void hello(){cout<<"hello from grandson"<<endl;};void bye() { cout << "bye from grandson"<<endl;}grandson(){cout<<"constructing grandson"<<endl;};~grandson(){cout<<"destructing grandson"<<endl;};};int main(){grandson gson;son *pson;pson=&gson;pson->hello(); //多態(tài)return 0;}結(jié)果:hello from sonconstructing grandsonhello from grandsondestructing grandsonbye from myclass三、多態(tài)的實現(xiàn)原理
思考:“多態(tài)”的關(guān)鍵在于通過基類指針或引用調(diào)用一個虛函數(shù)時,編譯時不確定到底調(diào)用的是基類還是派生類的函數(shù),運行時才確定 ---- 這叫“動態(tài)聯(lián)編”。“動態(tài)聯(lián)編”底是怎么實現(xiàn)的呢?
提示:請看下面例子程序:
class Base {public:int i;virtual void Print() { cout << "Base:Print" ; }};class Derived : public Base{public:int n;virtual void Print() { cout <<"Drived:Print" << endl; }};int main() {Derived d;cout << sizeof( Base) << ","<< sizeof( Derived ) ;return 0;}程序運行輸出結(jié)果: 8, 12為什么都多了4個字節(jié)?多態(tài)實現(xiàn)的關(guān)鍵 --- 虛函數(shù)表每一個有虛函數(shù)的類(或有虛函數(shù)的類的派生類)都有一個虛函數(shù)表,該類的任何對象中都放著虛函數(shù)表的指針。虛函數(shù)表中列出了該類的虛函數(shù)地址。多出來的4個字節(jié)就是用來放虛函數(shù)表的地址的。

四、虛析構(gòu)函數(shù)
問題:
class CSon{public: ~CSon() { };};class CGrandson : CSon{public: ~CGrandson() { };}int main(){CSon *p = new CGrandson;delete p;return 0;}通過 基類的指針 刪除 派生類對象 時只調(diào)用基類的析構(gòu)函數(shù)Vs.刪除一個 派生類的對象 時先調(diào)用 派生類的析構(gòu)函數(shù)再調(diào)用 基類的析構(gòu)函數(shù)解決辦法:把基類的析構(gòu)函數(shù)聲明為virtual? 派生類的析構(gòu)函數(shù) virtual可以不進(jìn)行聲明? 通過 基類的指針 刪除 派生類對象 時首先調(diào)用 派生類的析構(gòu)函數(shù)然后調(diào)用 基類的析構(gòu)函數(shù)類如果定義了虛函數(shù), 則最好將析構(gòu)函數(shù)也定義成虛函數(shù)Note: 不允許以虛函數(shù)作為構(gòu)造函數(shù)
class son{public:~son() { cout<<"bye from son"<<endl; };};class grandson : public son{public:~grandson(){ cout<<"bye from grandson"<<endl; };};int main(){son *pson;pson=new grandson;delete pson;return 0;}輸出結(jié)果: bye from son沒有執(zhí)行g(shù)randson::~grandson()!!!class son{public:virtual ~son() { cout<<"bye from son"<<endl; };};class grandson : public son{public:~grandson(){ cout<<"bye from grandson"<<endl; };};int main() {son *pson;pson= new grandson;delete pson;return 0;}輸出結(jié)果: bye from grandsonbye from son執(zhí)行g(shù)randson::~grandson(),引起執(zhí)行son::~son()!!!五、純虛函數(shù)和抽象類純虛函數(shù): 沒有函數(shù)體的虛函數(shù)class A {private:int a;public:virtual void Print( ) = 0 ; //純虛函數(shù)void fun() { cout << “fun”; }};抽象類抽象類: 包含純虛函數(shù)的類? 只能作為 基類 來派生新類使用? 不能創(chuàng)建抽象類的對象? 抽象類的指針和引用 即由抽象類派生出來的類的對象A a; // 錯, A 是抽象類, 不能創(chuàng)建對象A * pa; // ok, 可以定義抽象類的指針和引用pa = new A; //錯誤, A 是抽象類, 不能創(chuàng)建對象抽象類中,? 在 成員函數(shù) 內(nèi)可以調(diào)用純虛函數(shù)? 在 構(gòu)造函數(shù)/析構(gòu)函數(shù) 內(nèi)部不能調(diào)用純虛函數(shù)如果一個類從抽象類派生而來,它實現(xiàn)了基類中的所有純虛函數(shù), 才能成為非抽象類(即使只加了一個{},也算實現(xiàn)了)
class A {public:virtual void f() = 0; //純虛函數(shù)void g( ) { this->f( ); } //ok 成員函數(shù)允許調(diào)用純虛函數(shù)A( ){ } //f( ); // 錯誤 構(gòu)造函數(shù)不允許調(diào)用純虛函數(shù)};class B : public A{public:void f(){ cout<<"B: f()"<<endl; }};int main(){B b;b.g();return 0;}輸出結(jié)果:B: f()
新聞熱點
疑難解答