虚函数与多态

  • 多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
  • 重载函数是多态性的一种简单形式。
  • 虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。

多态的实现
多态性的实现和联编这一概念有关。所谓联编(Binding,绑定)就是把函数名与函数体的程序代码连接(联系)在一起的过程。

联编分成两大类:静态联编和动态联编。

静态联编优点:调用速度快,效率高,但缺乏灵活性;动态联编优点:运行效率低,但增强了程序灵活性。

C++为了兼容C语言仍然是编译型的,采用静态联编。为了实现多态性,利用虚函数机制,可部分地采用动态联编。

多态从实现的角度来讲可以划分为两类:编译时的多态运行时的多态

  • 编译时的多态是通过静态联编来实现的。静态联编就是在编译阶段完成的联编。编译时多态性主要是通过函数重载和运算符重载实现的。
  • 运行时的多态是用动态联编实现的。动态联编是运行阶段完成的联编。运行时多态性主要是通过虚函数来实现的。

静态联编

  • 联编是指一个程序模块、代码之间互相关联的过程。
  • 静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。
  • 重载函数使用静态联编。
  • 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。
  • switch 语句和 if 语句是动态联编的例子。

普通成员函数重载可表达为两种形式:

  1. 在一个类说明中重载
    例如:
    void Show ( int , char ) ;
    void Show ( char * , float ) ;
  2. 基类的成员函数在派生类重载。
    有 3 种编译区分方法:
    (1)根据参数的特征加以区分
    例如: void Show ( int , char ); 与
    void Show ( char * , float ); 不是同一函数,编译能够区分
    ( 2)使用“ :: ”加以区分
    例如: A :: Show ( );
    有别于 B :: Show ( );
    (3)根据类对象加以区分
    例如: Aobj . Show ( ) 调用 A :: Show ( )
    Bobj . Show ( ) 调用 B :: Show ( )

基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
直接用基类指针引用基类对象;
直接用派生类指针引用派生类对象;
用基类指针引用一个派生类对象;
用派生类指针引用一个基类对象。

例如:
虚函数与多态_第1张图片
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
#include
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() ;
   return 0;
}

虚函数与多态_第3张图片

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

#include
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() ;
     return 0; 
   }

虚函数与多态_第4张图片

虚函数
根据赋值兼容规则,可以将派生类的地址赋值给基类的指针。

考虑一个问题:
能否用这个指针访问派生类的成员函数?

#include 
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();
 return 0;     
} 

虚函数与多态_第5张图片

说明:说明:在编译阶段,基类指针对函数的操作只能绑定到基类的成员函数

根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用类(基类或派生类)的成员函数。
如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。
而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。从而实现运行过程的多态。

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

冠以关键字 virtual 的成员函数称为虚函数
实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本

/*指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员*/
#include
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() ;
   return 0;
}

虚函数与多态_第6张图片

#include
using namespace std ;
class  Base
{ 
  public :
       Base(char xx)  { x = xx; }
       virtual ()  { 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() ;
   return 0;
}

虚函数与多态_第7张图片

注意:
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
虚函数必须是类的成员函数
不能将友元说明为虚函数,但虚函数可以是另一个类的友元
析构函数可以是虚函数,但构造函数不能是虚函数
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
如果仅仅返回类型不同,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 ( )
} ;

虚析构函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象

#include
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 ;
    return0;
} 

虚函数与多态_第8张图片

#include
using namespace std ;
class A
 { 
   public:
      virtual  ~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 ;
    return0;
} 

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

#include 
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;
          }
};
int main()
{
     C objc;
     cout<<objc.funB(3)<<endl;
     B objb;
     cout<<objb.funB(3)<<endl;
     return 0;
}
//运行结果:
funA of class C called.
13.5
funA of class B called.
9
#include 
#include
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;
}

虚函数与多态_第10张图片
纯虚函数和抽象类
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。

  • 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本
  • 纯虚函数为各派生类提供一个公共界面
  • 纯虚函数说明形式:virtual 类型 函数名(参数表)= 0 ;
  • 一个具有纯虚函数的基类称为抽象类。
#include
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() ;
   return 0;
 }

虚函数与多态_第11张图片
虚函数与多态的应用

  • 虚函数和多态性使成员函数根据调用对象的类型产生不同的动作
  • 多态性特别适合于实现分层结构的软件系统,便于对问题抽象时定义共性,实现时定义区别
    虚函数与多态_第12张图片
    虚函数与多态_第13张图片
//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();
} 

感想:

时间过得好快啊,转瞬间12周的时间已经过去了,在上周我们的C++的理论课也已经结课了。在这12周的学习中,自己学到了很多也收获了很多。上学期虽然学习了Java但自己对一些知识理解并不深刻,只是划水应付了老师布置的作业,并没有学习到特别多的知识。这学期刚上C++这门课程时,感觉老师布置的作业和其他的老师不同,难度不是一个档次。刚开始有点抱怨,后来自己想清楚了,想要学习到真东西就应该这样。
老师通过几次布置作业的形式让我们完成了一个图书馆管理系统,在完成老师作业的过程中自己看了好几遍老师的课件,通过网络查询需要用到的知识。在这个过程中是特别枯燥的,但最后看到自己亲手做出的系统有一种以前没有的成就感,虽然自己做出的东西还不够好。这次虽然老师没有布置新的作业,只是让我们把上次图书管理系统的客户端改成用继承的方式完成。但我知道,面向对象编程有三个特性:封装、继承、多态。只有熟练地掌握并运用这三个特性才能说自己会使用面向对象的编程语言。
从这周开始就开始做课程设计了,课程设计说白了就是利用这学期学习到的知识独立地做出一个系统来。有了做图书管理系统的经验,我相信自己一定会做出一个不错的课程设计的。“行百里者半九十”,相信自己,继续加油!!!

你可能感兴趣的:(虚函数与多态)