C++中的多態及虛函數大總結

多態是C++中很關鍵的一部分,在面向對象程序設計中的作用尤為突出,其含義是具有多種形式或形態的情形,簡單來說,多態:向不同對象發送同一個消息,不同的對象在接收時會產生不同的行為。即用一個函數名可以調用不同內容的函數。

多態可分為靜態多態與動態多態,靜態多態的實現在於靜態聯編,關聯出現在編譯階段而非運行期,用對象名或者類名來限定要調用的函數,稱為靜態關聯或靜態聯編。常見有三種方法

(1)函數多態(函數與運算符的重載);

(2)宏多態;

(3)模板多態。

而對於動態多態的實現是運行階段把虛函數和類對象綁定在一起的,即動態聯編,動態綁定。具體的說,通過一個指向基類的指針調用虛成員函數的時候,運行時系統將能夠根據指針所指向的實際對象調用恰當的成員函數實現。

當編譯器使用動態綁定時,就會在運行時再去確定對象的類型以及正確的調用函數。而要讓編譯器採用遲綁定,就要在基類中聲明函數時使用virtual關鍵字,這樣的函數稱之為虛函數(virtual functions)。根據賦值兼容,用基類類型的指針指向派生類,就可以通過這個指針來使用派生類的成員函數。如果這個函數是普通的成員函數,通過基類類型的指針訪問到的只能是基類的同名成員。 而如果將它設置為虛函數,則可以使用基類類型的指針訪問到指針正在指向的派生類的同名函數。這樣,通過基類類型的指針,就可以使屬於不同派生類的不同對象產生不同的行為,從而實現運行過程的多態。可看這個例子:

<code>

1

2

using

 

namespace

 

std

;

3

class

 

A

4 {

5

public

 :   

6

     

void

 

print

( )

{  

cout

<< “A::print”<<

endl

; }

7

};

8

class

 

B

:

public

 A

9

{

10

public

 :     

11

   

void

 

print

( )

{  

cout

<< “B::print” <<

endl

; }

12

};

13

int

 

main

( )

14 {

15

         A a;

16

         B b;

17

         A *pA = &b;  

18

         pA->print( );  

19

     

return

 

0

;

20

}/<code>

  此時輸出A::print ,若將A類中的print( )函數聲明為virtual,則此時就為動態聯編 程序執行結果為: B::print。

注意點1:構造函數和靜態成員函數不能是虛函數:靜態成員函數不能為虛函數,是因為virtual函數由編譯器提供了this指針,而靜態成員函數沒有this指針,是不受限制於某個對象;構造函數不能為虛函數,是因為構造的時候,對象還是一片未定型的空間,只有構造完成後,對象才是具體類的實例。


<code>

1

class

 

A

2 {

3

public

:

4

     

virtual

 

A

( )

{};  

5

};

6

class

 

B

7 {

8

public

:

9

     

virtual

 

static

 

void

 

func

( )

{};  

10

};

11

int

 

main

( )

12 {      
       

13

         B b; 

14

     

return

 

0

;

15

}/<code>

  注意點2:派生類對象的指針可以直接賦值給基類指針,如上面中的A *a=&b;*a可以看作一個類A的對象,訪問它的public成員。通過強制指針類型轉換,可以把a轉換成B類的指針: a = &b; aa = static_cast< B * > a。此外指向基類的指針,可以指向它的公有派生的對象,但不能指向私有派生的對象,對於引用也是一樣的。


<code>

1

class

 

B

2 {

3

public

:

4

     

virtual

 

void

 

print

()

{

cout

<<

"Hello B"

<<

endl

; }

5

};

6

class

 

D

private

 B

7

{

8

public

:

9

     

virtual

 

void

 

print

()

{

cout

<<

"Hello D"

<<

endl

; }

10

};

11

int

 

main

()

12 {

13

     D d;

14

    B* pb = &d;  

15

     pb->print();

16

     B& rb = d;  

17

     rb.print();

18

   

return

 

0

;

19

}/<code>

注意點3:構造函數中調用virtual函數 ,在構造函數和析構函數中調用虛函數時:他們調用的函數是自己的類或基類中定義的函數,不會等到運行時才決定調用自己的還是派生類的函數


<code>

1

class

 

Transaction

2 {

3

public

:

4

     Transaction( ){  logTransaction( ); }

5

   

virtual

 

void

 

logTransaction

( )

=

0

;

6

};

7

class

 

BuyTransaction

public

 Transaction

8

{

9

public

:

10

     

int

 buyNum;

11

   

virtual

 

void

 

logTransaction

( )

cout

12 };

13

class

 

SellTransaction

public

 Transaction

14

{

15

public

:

16

   

int

 sellNum;

17

   

virtual

 

void

 

logTransaction

( )

18     {

19

       

cout

20     }

21

};

22

int

 

main

( )

23 {

24

     BuyTransaction b;

25

     SellTransaction s;

27

}/<code>

  以上代碼應該會有報錯提示,

若將基類的Transaction中虛函數logTransaction改為:


<code>

1

virtual

 

void

 

logTransaction

( )

  2 {  

3

cout

endl;  

4

};/<code>

程序執行結果為: This is a Transaction

This is a Transaction

注意點4:普通成員函數中調用虛函數,在普通成員函數中調用虛函數,則是動態聯編,是多態。


<code>

1

2

using

 

namespace

 

std

;

3

class

 

Base

4 {

5

public

:

6

     

void

 

func1

( )

  { func2( ); }

7

     

void

 

virtual

 

func2

( )

{

cout

endl; }

8

};

9

class

 

Derived

:

public

 Base

10

{

11

public

:

12

   

virtual

 

void

 

func2

( )

{

cout

endl; }

13

};

14

int

 

main

( )

15 {

16

     Derived d;

17

    Base * pBase = & d;

18

     pBase->func1( );

19

     

return

 

0

;

20

}/<code>

因為,Base類的func1( )為非靜態成員函數,編譯器會給加一個this指針: 相當於 void func1( ) { this->func2( ); } 編譯這個函數的代碼的時候,由於func2( )是虛函數,this是基類指針,所以是動態聯編。上面這個程序運行到func1函數中時, this指針指向的是d,所以經過動態聯編,調用的是Derived::func2( )。

注意點5:虛函數的訪問權限,如果基類定義的成員虛函數是私有的,我們來看看會怎麼樣


<code>

1

class

 

Base

{

2

private

:

3

     

virtual

 

void

 

func

( )

  {

cout

endl; }

4

};

5

class

 

Derived

public

 Base {

6

public

:

7

     

virtual

 

void

 

func

( )

{

cout

endl; }

8

};

9

int

 

main

( )

10 {

11

    Derived d;

12

    Base *pBase = & d;

13

     pBase->func( );  

14

     

return

 

0

;

15

}/<code>

對於類的private成員 ,只能由該類中的函數、其友元函數訪問,不能被任何其他訪問,該類的對象也不能訪問.所以即使是虛函數,也沒辦法訪問。但是!派生類虛函數的可訪問性與繼承的方式和虛函數在基類的聲明方式有關(public,或private)與派生類聲明的方式無關(如public繼承,但聲明為private,但仍可訪問),把上面的public與private互換位置,程序可以正常運行,並輸出Derived:func( )。

注意點6:虛函數與友元,先看代碼


<code>

1

class

 

A;

2

class

 

B

3

{

4 private:

5

         

int

 

x;

6

       

void

 

print()

{

cout<

}

7 public:

8

       

B(int

 

i

=

0

)

{

x

=

i;

}

9

       

friend

 

class

 

A;

10

};

11

class

 

A

12

{

13 public:

14

       

void

 

func(B

b){

b.print();

}

15

};

16

class

 

C

:

 

public

 

A

17

{

18

};

19

class

 

D:

 

public

 

B

20

{

21 public:

22

         

D(int

 

i):B(i){}

23

};

24

int

 

main()

25

{

26

         

D

d(99);

27

         

A

a;

28

         

C

c;

29

         

a.func(d);

30

         

c.func(d);

31

         

return

 

0

;

32

}

/<code>

程序執行結果為:99 99

由第一個99可知,A是B的友元類,A中的所有成員函數都為B的友元函數,可訪問B的私有成員函數。友元類A不是基類B的一部分,更不是派生類D的一部分。從上例看,友元視乎能夠被繼承,基類的友元函數或友元類能夠訪問派生類的私有成員。但public繼承是一種“is a”的關係,即一個派生類對象可看成一個基類對象。所以,上例中不是基類的友元被繼承了,而是派生類被識別為基類了。而第二個99說明一個友元類的派生類,可以通過其基類接口去訪問設置其基類為友元類的類的私有成員,也就是說一個類的友元類的派生類,某種意義上還是其友元類。

注意點7:析構函數通常是虛函數。虛析構函數保證了在析構時,避免只調用基類析構函數而不調用派生類析構函數的情況,保證資源正常釋放,避免了內存釋放。只有當一個類被用來作為基類的時候,才會把析構函數寫成虛函數。

感謝觀看,以上為個人總結,有不妥的地方歡迎指出。給個讚唄~


分享到:


相關文章: