虚函数与多态

一.多态的实现
(1) 多态性的实现和联编这一概念有关。所谓联编(Binding,绑定)就是把函数名与函数体的程序代码连接(联系)在一起的过程。
(2) 联编分成两大类:静态联编和动态联编。
(3) 静态联编优点:调用速度快,效率高,但缺乏灵活性;动态联编优点:运行效率低,但增强了程序灵活性。
(4) C++为了兼容C语言仍然是编译型的,采用静态联编。为了实现多态性,利用虚函数机制,可部分地采用动态联编。
(5) 多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。
编译时的多态是通过静态联编来实现的。静态联编就是在编译阶段完成的联编。编译时多态性主要是通过函数重载和运算符重载实现的。
运行时的多态是用动态联编实现的。动态联编是运行阶段完成的联编。运行时多态性主要是通过虚函数来实现的。

二.静态联编
1.概念:联编是指一个程序模块、代码之间互相关联的过程。
(1) 静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。
(2) 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。 switch 语句和 if 语句是动态联编的例子。
2.普通成员函数重载可表达为两种形式:
(1)在一个类说明中重载:
void Show ( int , char ) ;
void Show ( char * , float ) ;
(2). 基类的成员函数在派生类重载。有 3 种编译区分方法:
·根据参数的特征加以区分
void Show ( int , char ); 与 void Show ( char * , float ); 不是同一函数,编译能够区分
·使用“ :: ”加以区分
A :: Show ( );有别于 B :: Show ( );
·根据类对象加以区分:根据this指针类型区分
Aobj . Show ( ) 调用 A :: Show ( )
Bobj . Show ( ) 调用 B :: Show ( )

三.类指针的关系
1.基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
(1) 直接用基类指针引用基类对象;
(2) 直接用派生类指针引用派生类对象;
(3)用基类指针引用一个派生类对象;
(4) 用派生类指针引用一个基类对象。
例:
A * p ; // 指向类型 A 的对象的指针
A A_obj ; // 类型 A 的对象
B B_obj ; // 类型 B 的对象
p = & A_obj ; // p 指向类型 A 的对象
p = & B_obj ; // p 指向类型 B 的对象,它是 A 的派生类
利用 p,可以通过 B_obj 访问所有从 A 类继承的元素 , 但不能用 p访问 B 类自定义的元素 (除非用了显式类型转换)

2.基类指针引用派生类对象

#include<iostream>
#include<cstring>
using namespace std ;
class  A_class
{      char name[20] ;
    public :    void  put_name( char * s ) { strcpy_s( name, s ) ; }
                    void  show_name() { cout << name << "\n" ; }
};
class  B_class  : public  A_class
{      char phone_num[ 20 ] ;
    public :    void  put_phone( char * num )  { strcpy_s ( phone_num , num ) ; }
                    void  show_phone()  { cout << phone_num << "\n" ; }
};
int main()
{ A_class  * A_p ;      A_class  A_obj ;
   B_class   B_obj ;  
   A_p = & A_obj ;     
   A_p -> put_name( "Wang xiao hua" ) ;   A_p -> show_name() ;  
   A_p = & B_obj ;
   A_p -> put_name( "Chen ming" ) ;     A_p -> show_name() ; 
   B_obj.put_phone ( "5555_12345678" );
   ( ( B_class * ) A_p ) -> show_phone() ;
}

3.派生类指针引用基类对象
派生类指针只有经过强制类型转换之后,才能引用基类对象

#include<iostream>
using namespace std ;
class Date{
 public:
       Date( int y, int m, int d )   { SetDate( y, m, d ); }
       void SetDate( int y, int m, int d ) { year = y ; month = m ; day = d ; }
       void Print() { cout << year << '/' << month << '/' << day << "; " ; }
  protected :    int year , month , day ;
} ;
class DateTime : public Date
{ public :
       DateTime( int y, int m, int d, int h, int mi, int s ) : Date( y, m, d ) { SetTime( h, mi, s ); }
       void SetTime( int h, int mi, int s )  { hours = h;  minutes = mi;  seconds = s; }
       void Print()
         { ( ( Date * ) this ) -> Print();	
             cout << hours << ':' << minutes << ':' << seconds << '\n' ; 
         }
  private:    int hours , minutes , seconds ;	  
};
int main()   {
     DateTime dt( 2009, 1, 1, 12, 30, 0 ) ;
      dt.Print() ;
   }

4.根据赋值兼容规则,可以将派生类的地址赋值给基类的指针。能否用这个指针访问派生类的成员函数?
说明:在编译阶段,基类指针对函数的操作只能绑定到基类的成员函数。

#include <iostream.h>
class Undergraduate
{
public:
	void Display()
	{
		cout<<"Call BaseClass"<<endl;		
	  cout<<"Unergraduate LiMing"<<endl;
	}
};
class Master:public Undergraduate
{
public:
void Display()
	{
		cout<<"Call MasterClass"<<endl;
		cout<<"Master WangWei"<<endl;		
	}
};
class Doctor:public Master
{
public:
	void Display()
	{
		cout<<"Call DoctorClass"<<endl;
		cout<<"Doctor ZhangHua"<<endl;		
	}
};
void main(){
	Undergraduate s1,*pointer; 
	Master s2;
	Doctor s3;
	pointer=&s1;
	pointer->Display();
	pointer=&s2;
	pointer->Display();         
	pointer=&s3;
	pointer->Display();         
}	
 根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用类(基类或派生类)的成员函数。
 如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。
而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。从而实现运行过程的多态。

5.实现动态联编方式的前提
●先要声明虚函数
●类之间满足赋值兼容规则
●通过指针与引用来调用虚函数。

三.虚函数和基类指针

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

2.注意:
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
虚函数必须是类的成员函数。不能将友元说明为虚函数,但虚函数可以是另一个类的友元。 析构函数可以是虚函数,但构造函数不能是虚函数。

3.虚函数的重载特性
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同。如果仅仅返回类型不同,C++认为是错误重载。 如果函数原型不同,仅函数名相同,丢失虚特性。

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

4.虚析构函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数。
析构函数可以是虚的。虚析构函数用于指引 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 ;
} 

5.说明:
1.派生类应该从它的基类公有派生。
2.必须首先在基类中定义虚函数。
3.派生类对基类中声明虚函数重新定义时,关键字virtual可以不写。
4.一般通过基类指针访问虚函数时才能体现多态性。
5.一个虚函数无论被继承多少次,保持其虚函数特性。
6.虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态函数。
7.构造函数、内联成员函数、静态成员函数不能是虚函数。
(虚函数不能以内联的方式进行处理)
8.析构函数可以是虚函数,通常声明为虚函数。

6.成员函数调用虚函数(采用动态联编)

#include <iostream.h>
class A
{  public:
  virtual double funA(double x)
  { cout<<"funA of class A called."<<endl;
      return x*x;  }
  double funB(double x)
   {   return funA(x)/2;   }
};
class B:public A
{   public:
    virtual double funA(double x)
  {  cout<<"funA of class B called."<<endl;
      return 2*x*x;  }
}; 
class C:public B
{   public:
  virtual double funA(double x)
 {  cout<<"funA of class C called."<<endl;
      return 3*x*x;
   }
};
void main()
{
     C objc;
     cout<<objc.funB(3)<<endl;
     B objb;
     cout<<objb.funB(3)<<endl;
}

四.纯虚函数和抽象类
1.作用:纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
2.概念: 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本
纯虚函数为各派生类提供一个公共界面
纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
一个具有纯虚函数的基类称为抽象类。

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,可以声明抽象类的引用

例:简单图形类

#include<iostream>
using namespace std ;
#include"figure.h"
class figure
{ protected : double x,y;
  public:    void set_dim(double i, double j=0) { x = i ;  y = j ; }
                 virtual void show_area() = 0 ;
};
class triangle : public figure
{ public :
      void show_area()
       { cout<<"Triangle with high "<<x<<" and base "<<y <<" has an area of "<<x*0.5*y<<"\n"; }
};
class square : public figure
{ public:
      void show_area()
         { cout<<"Square with dimension "<<x<<"*"<<y <<" has an area of "<<x*y<<"\n"; }
};
class circle : public figure
{ public:
    void show_area()
    { cout<<"Circle with radius "<<x;
       cout<<" has an area of "<<3.14*x*x<<"\n";
    }
};
int main()
 { triangle t ;	//派生类对象
    square s ;    circle c;
    t.set_dim(10.0,5.0) ;
    t.show_area();
    s.set_dim(10.0,5.0) ;
    s.show_area() ;
    c.set_dim(9.0) ;
    c.show_area() ;
 }

五.虚函数与多态的应用
虚函数和多态性使成员函数根据调用对象的类型产生不同的动作。多态性特别适合于实现分层结构的软件系统,便于对问题抽象时定义共性,实现时定义区别 。

计算雇员工资

//Employee.h
class Employee
{ public:
       Employee(const int,const string );
       virtual ~Employee();			
       const string getName() const;
       const int getNumber() const;
       virtual double earnings() const=0;	
       virtual void print() const;
  protected:
       int number;		// 编号
       string name;		// 姓名
};
class Manager : public Employee
{ public:
       Manager(const int , const string, double =0.0);
       ~Manager() { }
       void setMonthlySalary(double);
       virtual double earnings() const;
       virtual void print() const;
  private:
      double monthlySalary ;	
};
class HourlyWorker : public Employee
{ public:
       HourlyWorker(const long, const string, double=0.0, int =0 );
       ~HourlyWorker(){}
       void setWage(double);		
       void setHours(int);		
       virtual double earnings() const; 
       virtual void print() const;	
  private:
      double wage;
      double hours;
};
class PieceWorker : public Employee
{ public:
       PieceWorker(const long , const string, double =0.0, int =0 );
       ~PieceWorker() { }
       void setWage ( double ) ;		
       void setQuantity ( int ) ;		
       virtual double earnings() const;
       virtual void print() const;
  private:
       double wagePerPiece;	
       int quantity;			
};
void test1()
{ 
   Manager m1 ( 10135, "Cheng ShaoHua", 1200 ) ;
   Manager m2 ( 10201, "Yan HaiFeng");
   m2.setMonthlySalary ( 5300 ) ;
   HourlyWorker hw1 ( 30712, "Zhao XiaoMing", 5, 8*20 ) ;
   HourlyWorker hw2 ( 30649, "Gao DongSheng" ) ;
   hw2.setWage ( 4.5 ) ;
   hw2.setHours ( 10*30 ) ;
   PieceWorker pw1 ( 20382, "Xiu LiWei", 0.5, 2850 ) ;
   PieceWorker pw2 ( 20496, "Huang DongLin" ) ;
   pw2.setWage ( 0.75 ) ;
   pw2.setQuantity ( 1850 ) ;
   // 使用抽象类指针,调用派生类版本的函数
   Employee *basePtr; 	
   basePtr=&m1;     basePtr->print();
   basePtr=&m2;     basePtr->print();
   basePtr=&hw1;   basePtr->print();
   basePtr=&hw2;   basePtr->print();
   basePtr=&pw1;   basePtr->print();
   basePtr=&pw2;   basePtr->print();
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值