多型是C++中很關鍵的一部分,在面向物件程式設計中的作用尤為突出,其含義是具有多種形式或形態的情形,簡單來說,多型:向不同物件發送同一個訊息,不同的物件在接收時會產生不同的行為,即用一個函式名可以呼叫不同內容的函式,
多型可分為靜態多型與動態多型,靜態多型的實作在于靜態聯編,關聯出現在編譯階段而非運行期,用物件名或者類名來限定要呼叫的函式,稱為靜態關聯或靜態聯編,常見有三種方法
(1)函式多型(函式與運算子的多載);
(2)宏多型;
(3)模板多型,
而對于動態多型的實作是運行階段把虛函式和類物件系結在一起的,即動態聯編,動態系結,具體的說,通過一個指向基類的指標呼叫虛成員函式的時候,運行時系統將能夠根據指標所指向的實際物件呼叫恰當的成員函式實作,
當編譯器使用動態系結時,就會在運行時再去確定物件的型別以及正確的呼叫函式,而要讓編譯器采用遲系結,就要在基類中宣告函式時使用virtual關鍵字,這樣的函式稱之為虛函式(virtual functions),根據賦值兼容,用基型別別的指標指向派生類,就可以通過這個指標來使用派生類的成員函式,如果這個函式是普通的成員函式,通過基型別別的指標訪問到的只能是基類的同名成員, 而如果將它設定為虛函式,則可以使用基型別別的指標訪問到指標正在指向的派生類的同名函式,這樣,通過基型別別的指標,就可以使屬于不同派生類的不同物件產生不同的行為,從而實作運行程序的多型,可看這個例子:
#include <iostream>
using namespace std;
class A
{
public :
void print( ) { cout << “A::print”<<endl ; }
};
class B:public A
{
public :
void print( ) { cout << “B::print” <<endl; }
};
int main( )
{
A a;
B b;
A *pA = &b;
pA->print( );
return 0;
}
此時輸出A::print ,若將A類中的print( )函式宣告為virtual,則此時就為動態聯編 程式執行結果為: B::print,
注意點1:建構式和靜態成員函式不能是虛函式:靜態成員函式不能為虛函式,是因為virtual函式由編譯器提供了this指標,而靜態成員函式沒有this指標,是不受限制于某個物件;建構式不能為虛函式,是因為構造的時候,物件還是一片未定型的空間,只有構造完成后,物件才是具體類的實體,
class A
{
public:
virtual A( ) {}; //error
};
class B
{
public:
virtual static void func( ) {}; //error “virtual”不能和“static”一起使用
};
int main( )
{
B b; //報錯
A *a=&b;
return 0;
}
注意點2:派生類物件的指標可以直接賦值給基類指標,如上面中的A *a=&b;*a可以看作一個類A的物件,訪問它的public成員,通過強制指標型別轉換,可以把a轉換成B類的指標: a = &b; aa = static_cast< B * > a,此外指向基類的指標,可以指向它的公有派生的物件,但不能指向私有派生的物件,對于參考也是一樣的,
class B
{
public:
virtual void print() { cout<<"Hello B"<<endl; }
};
class D : private B
{
public:
virtual void print() { cout<<"Hello D"<<endl; }
};
int main()
{
D d;
B* pb = &d; //轉換存在,無法訪問
pb->print();
B& rb = d; //轉換存在,無法訪問
rb.print();
return 0;
}
注意點3:建構式中呼叫virtual函式 ,在建構式和解構式中呼叫虛函式時:他們呼叫的函式是自己的類或基類中定義的函式,不會等到運行時才決定呼叫自己的還是派生類的函式,
class Transaction
{
public:
Transaction( ){ logTransaction( ); }
virtual void logTransaction( ) = 0;
};
class BuyTransaction: public Transaction
{
public:
int buyNum;
virtual void logTransaction( ) { cout<< "This is a BuyTransaction"; }
};
class SellTransaction: public Transaction
{
public:
int sellNum;
virtual void logTransaction( )
{
cout<< "This is a SellTransaction";
}
};
int main( )
{
BuyTransaction b;
SellTransaction s;
}
以上代碼應該會有報錯提示,
若將基類的Transaction中虛函式logTransaction改為:
virtual void logTransaction( )
{
cout<< "This is a Transaction"<<endl;
};
程式執行結果為: This is a Transaction
This is a Transaction
注意點4:普通成員函式中呼叫虛函式,在普通成員函式中呼叫虛函式,則是動態聯編,是多型,
#include <iostream>
using namespace std;
class Base
{
public:
void func1( ) { func2( ); }
void virtual func2( ) { cout << "Base::func2( )" << endl; }
};
class Derived:public Base
{
public:
virtual void func2( ) { cout << "Derived:func2( )" << endl; }
};
int main( )
{
Derived d;
Base * pBase = & d;
pBase->func1( );
return 0;
}
因為,Base類的func1( )為非靜態成員函式,編譯器會給加一個this指標: 相當于 void func1( ) { this->func2( ); } 編譯這個函式的代碼的時候,由于func2( )是虛函式,this是基類指標,所以是動態聯編,上面這個程式運行到func1函式中時, this指標指向的是d,所以經過動態聯編,呼叫的是Derived::func2( ),
注意點5:虛函式的訪問權限,如果基類定義的成員虛函式是私有的,我們來看看會怎么樣
class Base{
private:
virtual void func( ) { cout << "Base::func( )" << endl; }
};
class Derived : public Base {
public:
virtual void func( ) { cout << "Derived:func( )" << endl; }
};
int main( )
{
Derived d;
Base *pBase = & d;
pBase->func( ); // 無法訪問, private 成員(在“Base”類中宣告)
return 0;
}
對于類的private成員 ,只能由該類中的函式、其友元函式訪問,不能被任何其他訪問,該類的物件也不能訪問.所以即使是虛函式,也沒辦法訪問,但是!派生類虛函式的可訪問性與繼承的方式和虛函式在基類的宣告方式有關(public,或private)與派生類宣告的方式無關(如public繼承,但宣告為private,但仍可訪問),把上面的public與private互換位置,程式可以正常運行,并輸出Derived:func( ),
注意點6:虛函式與友元,先看代碼
class A;
class B
{
private:
int x;
void print() { cout<<x<<endl; }
public:
B(int i = 0) { x = i; }
friend class A;
};
class A
{
public:
void func(B b){ b.print(); }
};
class C : public A
{
};
class D: public B
{
public:
D(int i):B(i){}
};
int main()
{
D d(99);
A a;
C c;
a.func(d);
c.func(d);
return 0;
}
程式執行結果為:99 99
由第一個99可知,A是B的友元類,A中的所有成員函式都為B的友元函式,可訪問B的私有成員函式,友元類A不是基類B的一部分,更不是派生類D的一部分,從上例看,友元視乎能夠被繼承,基類的友元函式或友元類能夠訪問派生類的私有成員,但public繼承是一種“is a”的關系,即一個派生類物件可看成一個基類物件,所以,上例中不是基類的友元被繼承了,而是派生類被識別為基類了,而第二個99說明一個友元類的派生類,可以通過其基類介面去訪問設定其基類為友元類的類的私有成員,也就是說一個類的友元類的派生類,某種意義上還是其友元類,
注意點7:解構式通常是虛函式,虛解構式保證了在析構時,避免只呼叫基類解構式而不呼叫派生類解構式的情況,保證資源正常釋放,避免了記憶體釋放,只有當一個類被用來作為基類的時候,才會把解構式寫成虛函式,
以上為個人總結,有不妥的地方歡迎指出,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/59732.html
標籤:C++
上一篇:c語言心形告白代碼實作
下一篇:C語言指標學習總結
