北京大學MOOC C++學習筆記(五)虛擬函式和多型
虛擬函式:
在類的定義中,前面有 virtual 關鍵字的成員函式就是虛擬函式。 class base { virtual int get() ; }; int base::get() { } virtual 關鍵字只用在類定義裡的函式宣告中,寫函式體時不用。
多型的表現形式一
- 派生類的指標可以賦給基類指標。
- 通過基類指標呼叫基類和派生類中的同名虛擬函式時:
- 若該指標指向一個基類的物件,那麼被呼叫是基類的虛擬函式;
- 若該指標指向一個派生類的物件,那麼被呼叫的是派生類的虛擬函式。
這種機制就叫做“多型”。
class CBase { public: virtual void SomeVirtualFunction() { } }; class CDerived:public CBase { public : virtual void SomeVirtualFunction() { } }; int main() { CDerived ODerived; CBase * p = & ODerived; p -> SomeVirtualFunction(); //呼叫哪個虛擬函式取決於p指向哪種型別的物件 return 0; }
多型的表現形式二:
- 派生類的物件可以賦給基類引用
- 通過基類引用呼叫基類和派生類中的同名 虛擬函式時:
- 若該引用引用的是一個基類的物件,那麼被呼叫是基類的虛擬函式;
- 若該引用引用的是一個派生類的物件,那麼被呼叫的是派生類的虛擬函式。
這種機制也叫做“多型”。
class CBase { public: virtual void SomeVirtualFunction() { } }; class CDerived:public CBase { public : virtual void SomeVirtualFunction() { } }; int main() { CDerived ODerived; CBase & r = ODerived; r.SomeVirtualFunction(); //呼叫哪個虛擬函式取決於r引用哪種型別的物件 return 0; }
多型的作用:
在面向物件的程式設計中使用多型,能夠增強程式的可擴充性,即程式需要修改或增加功能的時候,需要改動和增加的程式碼較少。
多型使用例項:幾何形體處理程式
何形體處理程式: 輸入若干個幾何形體的引數, 要求按面積排序輸出。輸出時要指明形狀。 Input: 第一行是幾何形體數目n(不超過100).下面有n行,每行以一個字母c開頭. 若 c 是 ‘R’,則代表一個矩形,本行後面跟著兩個整數,分別是矩形的寬和高; 若 c 是 ‘C’,則代表一個圓,本行後面跟著一個整數代表其半徑 若 c 是 ‘T’,則代表一個三角形,本行後面跟著三個整數,代表三條邊的長度
Output: 按面積從小到大依次輸出每個幾何形體的種類及面積。每行一個幾何形體,輸 出格式為: 形體名稱:面積
Sample Input: 3 R 3 5 C 9 T 3 4 5 Sample Output Triangle:6 Rectangle:15 Circle:254.34
#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;
class CShape
{
public:
virtual double Area() = 0; //純虛擬函式
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指向的內容
CShape * * p2;
p1 = ( CShape * * ) s1; //s1,s2指向pShapes陣列中的元素,陣列元素的型別是CShape *
p2 = ( CShape * * ) s2; // 故 p1,p2都是指向指標的指標,型別為 CShape **
a1 = (*p1)->Area(); // * p1 的型別是 Cshape * ,是基類指標,故此句為多型
a2 = (*p2)->Area();
if( a1 < a2 )
return -1;
else if ( a2 < a1 )
return 1;
else
return 0;
}
用基類指標陣列存放指向各種派生類物件的指標,然後遍歷該陣列,就能對各個派生類物件做各種操作,是很常用的做法 。
建構函式和解構函式中呼叫虛擬函式:
在建構函式和解構函式中呼叫虛擬函式,不是多型。編譯時即可確定,呼叫的函式是自己的類或基類中定義的函式,不會等到執行時才決定呼叫自己的還是派生類的函式。
虛解構函式:
- 通過基類的指標刪除派生類物件時,通常情況下只調用基類的解構函式。但是,刪除一個派生類的物件時,應該先呼叫派生類的解構函式,然後呼叫基類的解構函式。
- 解決辦法:把基類的解構函式宣告為virtual。派生類的解構函式可以virtual不進行宣告。通過基類的指標刪除派生類物件時,首先呼叫派生類的解構函式,然後呼叫基類的解構函式
- 一般來說,一個類如果定義了虛擬函式,則應該將解構函式也定義成虛擬函式。或者,一個類打算作為基類使用,也應該將解構函式定義成虛擬函式。
- 注意:不允許以虛擬函式作為建構函式
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;
}
輸出: bye from son 沒有執行grandson::~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;
}
: 輸出: bye from grandson
bye from son
執行grandson::~grandson() ,引起執行son::~son() !!!
虛擬函式的訪問許可權:
class Base {
private:
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived:public Base {
public:
virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
Derived d;
Base * pBase = & d;
pBase -> fun2(); // wrong
- 編譯出錯是因為 fun2() 是Base的私有成員。即使執行到此時實際上呼叫的應該是Derived的公有成員 fun2()也不行,因為語法檢查是不考慮執行結果的。
- 如果 將Base中的 private換成public,即使Derived中的fun2() 是private的,編譯依然能通過,也能正確呼叫Derived::fun2()。
純虛擬函式和抽象類:
純虛擬函式: 沒有函式體的虛擬函式
class A { private: int a; public: virtual void Print( ) = 0 ; // 純虛擬函式 void fun() { cout << "fun"; } };
- 包含純虛擬函式的類叫抽象類
- 抽象類只能作為基類來派生新類使用,不能建立抽象類的物件
- 抽象類的指標和引用可以指向由抽象類派生出來的類的物件
A a ; // 錯,A 是抽象類,不能建立物件 A * pa ; // ok,可以定義抽象類的指標和引用 pa = new A ; //錯誤, A 是抽象類,不能建立物件
- 在抽象類的成員函式內可以呼叫純虛擬函式,但是在建構函式或解構函式內部不能呼叫純虛擬函式。
- 如果一個類從抽象類派生而來,那麼當且僅當它實現了基類中的所有純虛擬函式,它才能成為非抽象類。
class A {
public:
virtual void f() = 0; // 純虛擬函式
void g( ) { this->f( ) ; //ok}
A( ){ //f( ); // 錯誤}
};
class B:public A{
public:
void f(){cout<<"B:f()"<<endl; }
};
int main(){
B b;
b.g();
return 0;
}