C++的類機制中有支持多態的技術來解決抽象編程,它用的是一種滯後捆綁技術,
這種技術,通過預先設定其成員函數的虛函數性質,使得任何捆綁該成員函數的未定類型的對象操作在編譯時,都以一個不確定的指針特殊地“引命代發”來編碼,
到了運行時,遇到確定類型的對象,才突然制定其真正的行為。即滯後到運行時,根據具體類型的對象來捆綁成員函數,這樣,辨別對象類型的工作就可以不用用戶做了。
虛函數(Virtual Function)
1.多態條件(Polymorphism Condition)
使用類編程中,要能進行抽象編程,不隨類的改動而改動,類機制必須解決這個問題,在C++中,就是虛函數機制。基類與派生類的同名操作。只要標記上virtual,則該操作便具有多態性。
如下代碼所示:
[cpp]
#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun()
{
cout << "In Base class" << endl;
}
};
class Sub : public Base
{
public:
virtual void fun()
{
cout << "In Sub class" << endl;
}
};
void test(Base& b)
{
b.fun();
}
int main()
{
Base bc;
Sub sc;
test(bc);
test(sc);
return 0;
}
代碼輸出如下:
[plain]
In Base class
In Sub class
上述代碼中fun是Base類的虛函數,一旦標記了基類的函數為虛函數,則後面繼承類中一切同名成員函數都編程了虛函數,在test函數中,b是Base類的引用性形參,Base類對象和Sub類對象都可以作為實參傳給形參b。
如果函數參數是實際復制動作的傳遞,則子類對象完全變成基類對象了,這樣,便不會有多態了。如將上述代碼中的test函數參數改為如下形式時,
如下代碼所示:
[cpp]
void test(Base b)
{
b.fun();
}
int main()
{
Base bc;
Sub sc;
test(bc);
test(sc);
return 0;
}
其輸出則變為:
[plain]
In Base class
In Base class
因為對於這種參數的傳遞過程已經將對象的性質做了肯定的轉變,而對於確定的對象,是沒有選擇操作可言的。因此,對於多態,僅僅對於對象的指針和引用的間接訪問,才會發生多態現象。
2. 虛函數機理
在上述代碼中,b.fun()函數調用顯示出多樣性,其編譯不能立即確定fun的確切位置,即不能確定到底是調用基類Base的fun函數,還是子類Sub的fun函數。
當編譯器看到fun的虛函數標志時,以放入虛函數表中,等到遇到b.fun()這個虛函數的調用時,便將該捆綁操作滯後到運行中,以實際的對象類型來實際捆綁其對應的成員函數操作。當然編譯器不可能跟蹤到運行的程序中去,而是在捆綁操作b.fun()處避開函數調用,只做一個指向實際對象的成員函數的間接訪問。如此,實際對象若是基類,則調用的就是基類成員函數,若是子類,則調用的就是子類的成員函數。當然,每一個實際的對象都必須額外占有一個指針空間,以指向類中的虛函數表。如下圖所示。
從圖中可以看處,用了虛函數的類,其對象的空間比不用虛函數的類多了一個指針的空間,由於涉及了其他的操作,包括間接訪問虛函數,對象的指針偏移量計算等,所以在應用了虛函數後,會影響程序的運行效率。
3.虛函數的傳播
虛函數在繼承層次結構中總是會自動地從基類傳播下去。因此,上述代碼中Sub類中的成員函數fun的virtual說明可以省略,
例如下面的例子:
在一個平面形狀類的體系中,基類shape有兩個子類:圓Circle類和長方形Retangle類,專門負責求面積的Area函數在基類中設置為virtual就能使子類的響應同名函數虛擬化。
[plain]
#include<iostream>
#include <cmath>
using namespace std;
class Shape
{
protected:
double xCoord, yCoord;
public:
Shape(double x, double y)
{
xCoord = x;
yCoord = y;
}
virtual double area()const
{
return 0.0;
}
};
class Circle : public Shape
{
protected:
double radius;
public:
Circle(double x, double y, double r) : Shape(x, y)
{
radius = r;
}
double area()const
{
return 3.14 * radius * radius;
}
};
class Rectangle : public Shape
{
protected:
double x2Coord, y2Coord;
public:
Rectangle(double x1, double y1, double x2, double y2) : Shape(x1, y1)
{
x2Coord = x2;
y2Coord = y2;
}
double area()const;
};
double Rectangle::area()const
{
return abs((xCoord - x2Coord) * (yCoord - y2Coord));
}
void fun(const Shape& sp)
{
cout << sp.area() << endl;
}
int main()
{
fun(Circle(2, 5, 4));
fun(Rectangle(2, 4, 1, 2));
return 0;
}