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()
}