1. 程式人生 > >c++學習總結(六)——虛擬函式與多型

c++學習總結(六)——虛擬函式與多型

一、學習總結

    在面向物件程式設計中,多型性是指一個名字,多種語義;或者介面相同,多種實現。過載函式是多型性的一種簡單形式。C++為類體系提供一種靈活的多型機制——虛擬函式。虛擬函式允許函式呼叫與函式體的聯絡在執行時才進行,成為動態聯編。類、繼承和多型,提供了對軟體重用性和擴充性需要的卓越表達能力。

     多型性(Polymorphism)是指一個名字,多種語義;或介面相同,多種實現。
     過載函式是多型性的一種簡單形式。
     虛擬函式允許函式呼叫與函式體的聯絡在執行時才進行,稱為動態聯編。

二、內容及例項

9.3    虛擬函式和動態聯編

(1)冠以關鍵字 virtual 的成員函式稱為虛擬函式

(2)實現執行時多型的關鍵首先是要說明虛擬函式,另外,必須用基類指標呼叫派生類的不同實現版本

9.3.1    虛擬函式和基類指標

基類指標雖然獲取派生類物件地址,卻只能訪問派生類從基類繼承的成員

#include<iostream>
using namespace std ;
class  Base
{ public :       
        Base(char xx)  { x = xx; }
        void who()  { cout << "Base class: " << x << "\n" ; }
   protected:    
        char x;//基類定義虛擬函式
} ;
class  First_d : public  Base

    public :       
        First_d(char xx, char yy):Base(xx)  { y = yy; }
        void who()  { cout << "First derived class: "<< x << ", " << y << "\n" ; }
   protected:    
        char y;
} ;
class  Second_d : public  First_d
{ public :
        Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; } 
        void who()  { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
   protected:    
        char z;
} ;
int main()

    Base  B_obj( 'A' ) ;   
    First_d F_obj( 'T', 'O' ) ;  
    Second_d S_obj( 'E', 'N', 'D' ) ;
    Base  * p ;
    p = & B_obj ;    
    p -> who() ;
    p = &F_obj ;     
    p -> who() ;
    p = &S_obj ;     
    p -> who() ;
    F_obj.who() ;
    ( ( Second_d * ) p ) -> who() ;

}

注意:
一個虛擬函式,在派生類層介面相同的過載函式都保持虛特性
  虛擬函式必須是類的成員函式
  不能將友元說明為虛擬函式,但虛擬函式可以是另一個類的友元

  解構函式可以是虛擬函式,但建構函式不能是虛擬函式

9.3.2    虛擬函式的過載特徵

 (1)在派生類中過載基類的虛擬函式要求函式名、返回型別、引數個數、
 (2)引數型別和順序完全相同
 (3)如果僅僅返回型別不同,C++認為是錯誤過載

 (4)如果函式原型不同,僅函式名相同,丟失虛特性 

例:
class  base
{ public : 
      virtual  void  vf1 ( ) ;
      virtual  void  vf2 ( ) ;
      virtual  void  vf3 ( ) ;
      void  f ( ) ;
 } ;
class  derived : public  base
{ public : 
      void  vf1 ( ) ;

// 虛擬函式
      void  vf2 ( int ) ; // 過載,引數不同,虛特性丟失
      char  vf3 ( ) ; // error,僅返回型別不同
      void f ( ) ; // 非虛擬函式過載
 } ;
void  g ( ) 
{ derived   d ;
   base  * bp = & d ;        // 基類指標指向派生類物件
   bp -> vf1 ( ) ;         // 呼叫 deriver :: vf1 ( )
   bp -> vf2 ( ) ;         // 呼叫 base :: vf2 ( )
   bp -> f ( ) ;         // 呼叫 base :: f ( )

} ;

9.3.3    虛解構函式

(1) 建構函式不能是虛擬函式。建立一個派生類物件時,必須從類層次的根開始,沿著繼承路徑逐個呼叫基類的建構函式

(2)解構函式可以是虛的。虛解構函式用於指引 delete 運算子正確析構動態物件 

例 :普通解構函式在刪除動態派生類物件的呼叫情況

#include<iostream>
using namespace std ;
class A
{ public:
        ~A(){ cout << "A::~A() is called.\n" ; }
} ;
class B : public A
{ public:
        ~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main() 
{
    A *Ap = new B ;
    B *Bp2 = new B ;
    cout << "delete first object:\n" ;
    delete Ap;
    cout << "delete second object:\n" ;
    delete Bp2 ;


虛解構函式在刪除動態派生類物件的呼叫情況:

#include<iostream>
using namespace std ;
class A
{ public:
        ~A(){ cout << "A::~A() is called.\n" ; }
 } ;
class B : public A
{ public:
        ~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main()
{ A *Ap = new B ;
    B *Bp2 = new B ;
    cout << "delete first object:\n" ;
    delete Ap;
    cout << "delete second object:\n" ;
    delete Bp2 ;


說明:
1.派生類應該從它的基類公有派生。
2.必須首先在基類中定義虛擬函式。
3.派生類對基類中宣告虛擬函式重新定義時,關鍵字virtual可以不寫。
4.一般通過基類指標訪問虛擬函式時才能體現多型性。
5.一個虛擬函式無論被繼承多少次,保持其虛擬函式特性。
6.虛擬函式必須是其所在類的成員函式,而不能是友元函式,也不能是靜態函式。
7.建構函式、內聯成員函式、靜態成員函式不能是虛擬函式。
(虛擬函式不能以內聯的方式進行處理)

8.解構函式可以是虛擬函式,通常宣告為虛擬函式。

成員函式呼叫虛擬函式(採用動態聯編)

#include <iostream>
#include<string>
using namespace std;
class Animal
{
string name;
public:
Animal(string a_name):name(a_name){}
virtual void show(){}
void show_name()
{
cout<< "The name is "<<name<<".“<<endl;
}
};
class Cat :public Animal
{
string kind;
public:
Cat(string a_name,string a_kind):Animal(a_name),kind(a_kind){}
void show();
};
void Cat::show()
{
show_name();
cout<<" It's a "<<kind<<endl;
}
class Dog:public Animal
{
string kind;
public:
Dog(string a_name,string a_kind):Animal(a_name),kind(a_kind){}
void show();
};
void Dog::show()
{
show_name();
cout<<" It's a "<<kind<<endl;
}
class Tiger:public Cat
{
    public:
        Tiger(string a_name,string a_kind):Cat(a_name,a_kind){}
};
int main()
{
Animal *p;
Cat cat("Tom","cat");
Dog dog("Jerry","Dog");
Tiger tiger("DuDu","Tiger");
p=&cat;
p->show();
p=&dog;
p->show();
p=&tiger;
p->show();
return 0;

}


9.4純虛擬函式和抽象類

純虛擬函式是一種特殊的虛擬函式,在許多情況下,在基類中不能對虛擬函式給出有意義的實現,而把它宣告為純虛擬函式,它的實現留給該基類的派生類去做。

(1)純虛擬函式是一個在基類中說明的虛擬函式,在基類中沒有定義, 要求任何派生類都定義自己的版本
(2)純虛擬函式為各派生類提供一個公共介面
(3)純虛擬函式說明形式:
  virtual  型別  函式名(引數表)= 0 ;

(4) 一個具有純虛擬函式的基類稱為抽象類。 

例:

class  point { /*……*/ } ;
class  shape ; // 抽象類
{ point  center ;
      ……
  public :
  point  where ( ) { return  center ; }
  void  move ( point p ) {center = p ; draw ( ) ; }
  virtual  void  rotate ( int ) = 0 ; // 純虛擬函式
  virtual  void  draw ( ) = 0 ; // 純虛擬函式
} ;

      …...

shape  x ; // error,抽象類不能建立物件
shape  *p ; // ok,可以宣告抽象類的指標
shape  f ( ) ; // error, 抽象類不能作為函式返回型別
void  g ( shape ) ; // error, 抽象類不能作為傳值引數型別

shape  & h ( shape &) ; // ok,可以宣告抽象類的引用

class  ab_circle : public  shape
{       int  radius ;
    public :   
        void  rotate ( int ) { } ;

} ;  

//ab_circle 類仍為抽象類
ab_circle :: draw ( ) 、ab_circle :: rotate ( ) 

也是純虛擬函式

        要使 ab_circle 成為非抽象類,
  必須作以下說明:
   class  ab_circle : public  shape
  {    int  radius ;
     public :  
        void  rotate ( int ) ;
        void  draw ( ) ;
  } ;  
  並提供 ab_circle :: draw ( )
  和ab_circle :: rotate ( int  ) 
  的定義

例:

#include<iostream>
using namespace std ;
class Number
{ public :      
        Number (int i) { val = i ; }
        virtual void Show() = 0 ;
  protected:  
        int val ;
};
class Hex_type : public Number
{ public:    
        Hex_type(int i) : Number(i) { }
        void Show()  { cout << "Hexadecimal:" << hex << val << endl ; }
};
class Dec_type : public Number
{ public:    
        Dec_type(int i) : Number(i) { }
        void Show()  { cout << "Decimal: " << dec << val << endl ; }
};
class Oct_type : public Number
{ public:    
        Oct_type(int i) : Number(i) { }
        void Show()  { cout << "Octal: " << oct << val << endl ; }
}; 
void fun( Number & n ) // 抽象類的引用引數
{  
    n.Show() ;

int main()

    Dec_type n1(50);
    fun(n1); // Dec_type::Show()
    Hex_type n2(50);
    fun(n2); // Hex_type::Show()
    Oct_type n3(50);
    fun(n3); // Oct_type::Show()