复习六—虚函数与多态性

一、静态联编
(1)
联编是指一个程序模块、代码之间互相关联的过程。
静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。
动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。switch 语句和 if 语句是动态联编的例子。
(2)
重载函数要根据类型、参数进行匹配,一般在编译阶段进行静态联编
普通成员函数重载可表达为两种形式::
a.在一个类说明中重载
例如:  

  void Show ( int , char ) ;
    void Show ( char * , float ) ;


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


二、类指针的关系
基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
 直接用基类指针引用基类对象;
 直接用派生类指针引用派生类对象;
 用基类指针引用一个派生类对象;
 用派生类指针引用一个基类对象。 例如:

class A
{
 };
class B:public A
{
};

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 类自定义的元素 (除非用了显式类型转换)

例题一:使用基类指针引用派生类对象

#include<iostream>
#include<cstring>
using namespace std;
class A_class
{
    char name[20];
public:
    void put_name(char*s)
    {
        strcpy(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(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("5556789");//用派生类对象调用派生类的成员函数
    ((B_class*)A_p)->show_phone();//用基类指针强类型转换调用派生类的成员函数
}

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

例题二:日期时间程序。在派生类中调用基类同名成员函数

#include<iostream>
#include<cstring>
using namespace std;
class Date
{
protected:
    int year,month,day;
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<<endl;
    }
};
class DateTime:public Date
{
   int hours,minutes,seconds;
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();//对this指针作类型转换调用基类成员函数
        cout<<hours<<" "<<minutes<<" "<<seconds<<endl;
    }
};
int main()
{
    DateTime dt(2001,3,12,2,11,18);
    dt.Print();
}

三、虚函数和动态联编

冠以关键字 virtual 的成员函数称为虚函数
实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本
基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员

(1)、基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员

例题三:
//演示基类指针的移动

#include<iostream>
using namespace std;
class Base
{
public:
    Base(char xx) {x=xx;}
    void who() {cout<<" "<<x<<"\n";}
protected:
    char x;
};
class First_d:public Base
{
public:
    First_d(char xx,char yy):Base(xx){y=yy;}
    void who(){cout<<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<<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();
}

输出结果:
 

A
 T
 E
T o
E N D

(2)、虚函数和基类指针注意:
 一个虚函数,在派生类层面相同的重载函数都保持虚特性
 虚函数必须是类的成员函数
 不能将友元说明为虚函数,但虚函数可以是另一个类的友元
 析构函数可以是虚函数,但构造函数不能是虚函数
 在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
 如果仅仅返回类型不同,C++认为是错误重载
 如果函数原型不同,仅函数名相同,丢失虚特性
例题四
虚函数重载特性

#include<iostream>
using namespace std;
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 ( )
} ;

(3)、虚析构函数(构造函数不能是虚函数,析构函数可以是虚的,虚析构函数用于指引delete运算符正确析构动态对象)例题五A,普通析构函数在删除动态派生类对象的调用情况

#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*bp=new B;//用派生类指针建立派生类的动态对象
    cout<<"delete first object\n";
    delete ap;//析构由基类指针建立的派生类对象没有调用派生类析构函数
     cout<<"delete second object\n";
    delete bp;//析构由派生类指针建立的派生类对象正确调用派生类析构函数
}

输出结果:
delete first object
A::~A() is called.
delete second object
B::~B() is called.
A::~A() is called.例题五B

#include<iostream>
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*bp=new B;//用派生类指针建立派生类的动态对象
    cout<<"delete first object\n";
    delete ap;//析构由基类指针建立的派生类对象没有调用派生类析构函数
     cout<<"delete second object\n";
    delete bp;//析构由派生类指针建立的派生类对象正确调用派生类析构函数
}

输出结果:

delete first object
B::~B() is called.
A::~A() is called.
delete second object
B::~B() is called.
A::~A() is called.

四、纯虚函数和抽象类

纯虚函数说明形式:
      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 ;            // 纯虚函数
} ;
      …...
hape  x ;            // error,抽象类不能建立对象
shape  *p ;        // ok,可以声明抽象类的指针
shape  f ( ) ;        // error, 抽象类不能作为函数返回类型
void  g ( shape ) ;        // error, 抽象类不能作为传值参数类型
shape  & h ( shape &) ;    // ok,可以声明抽象类的引用

五、虚函数与多态的应用

例题六计算雇员工资

#include<iostream>
#include<string>
#include<cstring>
#include<iomanip>
using namespace std;
class Employee
{
public:
Employee(const long,const char*);
virtual~Employee();//虚析构函数
const char*getname() const;
const long getnumber() const;
virtual double earnings()const=0;//纯虚函数,用来计算月薪
virtual void print() const;//输出编号,姓名
protected:
long number;
char name[20];
};
Employee::Employee(const long k,const char*str)
{
number=k;
strcpy(name,str);
}
Employee::~Employee()
{
name[0]='\0';
}
const char*Employee::getname() const
{
return name;
}
const long Employee::getnumber() const
{
return number;
}
void Employee::print()const
{
cout<<number<<setw(20)<<name;
}
//管理人员领取固定月薪
class Manager:public Employee
{
public:
Manager(const long,const char*,double=0.0);
~Manager(){}
void setmonthlysalary(double);//设置月薪
virtual double earnings() const;//计算管理人月薪
virtual void print()const;
private:
double monthlysalary;//月薪
};
Manager::Manager(const long k,const char*str,double sal):Employee(k,str)
{
setmonthlysalary(sal);
}
void Manager::setmonthlysalary(double sal)
{
monthlysalary=sal>0?sal:0;
}
double Manager::earnings() const
{
return monthlysalary;
}
void Manager::print()const
{
Employee::print();
cout<<setw(16)<<"Manager"<<endl;
cout<<"\t $"<<monthlysalary<<endl;
}
//计时工人的月薪为基本工作时间的薪水加上加班费
class HourlyWorker:public Employee
{
public:
HourlyWorker(const long,const char*,double=0.0,int=0);
~HourlyWorker(){}
void setwage(double);//设置时薪
void sethours(int);//设置工时
virtual double earings()const;//计算计时工的月薪
virtual void print()const;//输出计时工月薪
private:
double wage;
double hours;
};
HourlyWorker::HourlyWorker(const long k,const char*str,double w,int h):Employee(k,str)
{
setwage(w);
sethours(h);
}
void HourlyWorker::setwage(double w)
{
wage=w>0?w:0;
}
void HourlyWorker::sethours(int h)
{
hours=h>=0&&h<=16*31?h:0;
}
double HourlyWorker::earings()const
{
if(hours<=8*22)
return wage*hours;
else
return wage*(8*22)+(hours-8*22)*wage*1.5;
}
void HourlyWorker::print()const
{
Employee::print();
cout<<setw(16)<<"Hours worker\n";
cout<<"\t wageperhours"<<wage<<"hours"<<hours;
cout<<"earned $"<<earnings()<<endl;
}
//计件工人
class PieceWorker:public Employee
{
public:
PieceWorker(const long,const char*,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;
};
PieceWorker::PieceWorker(const long k,const char*str,double w,int q):Employee(k,str)
{
setwage(w);
setquantity(q);
}
void PieceWorker::setwage(double w)
{
wageperpiece=w>0?w:0;
}
void PieceWorker::setquantity(int q)
{
quantity=q>0?q:0;
}
double PieceWorker::earnings()const
{
return quantity*wageperpiece;
}
void PieceWorker::print()const
{
Employee::print();
cout<<setw(16)<<"Piece worker\n";
cout<<"\t wageperpiece"<<wageperpiece<<"quantity"<<quantity;
cout<<"earned $"<<earnings()<<endl;
}
int main()
{
cout << setiosflags(ios::fixed|ios::showpoint) << setprecision(2) ;
Manager m1 ( 10135, "Cheng ShaoHua", 1200 ) ;
Manager m2 ( 10201, "Yan HaiFeng");
m2.setmonthlysalary ( 5300 ) ;
HourlyWorker h1 ( 30712, "Zhao XiaoMing", 5, 8*20 );//???h1,h2显示没有定义,不知道为啥
HourlyWorker h2 ( 30649, "Gao DongSheng");
h2.setwage ( 4.5 ) ;
h2.sethours ( 10*30 ) ;
PieceWorker p1 ( 20382, "Xiu LiWei", 0.5, 2850 ) ;
PieceWorker p2 ( 20496, "Huang DongLin" ) ;
p2.setwage ( 0.75 ) ;
p2.setquantity ( 1850 ) ;
Employee *basePtr;
basePtr=&m1; basePtr->print();
basePtr=&m2; basePtr->print();
basePtr=&h1; basePtr->print();
basePtr=&h2; basePtr->print();
basePtr=&p1; basePtr->print();
basePtr=&p2; basePtr->print();
}

六、异质链表

把不同类对象统一组织在一个数据结构中,可以定义抽象类指针数组或链表。
由于这种表中具有不同类类型元素(它们都有共同的基类),所以称为“异质表”。 
例题(接上)

void test2()
{ Employee * employ[6] ;//数组元素是基类指针
  int i;
  employ[0] = new Manager( 10135, "Cheng ShaoHua", 1200 ) ;
  employ[1] = new Manager( 10201, "Yan HaiFeng",5300 ) ;
  employ[2] = new HourlyWorker( 30712, "Zhao XiaoMing", 5, 8*20 ) ;
  employ[3] = new HourlyWorker( 30649, "Gao DongSheng", 4.5, 10*30 ) ;
  employ[4] = new PieceWorker( 20382, "Xiu LiWei", 0.5, 2850 ) ;
  employ[5] = new PieceWorker(20496, "Huang DongLin", 0.75, 1850) ;
  cout << setiosflags(ios::fixed|ios::showPoint) << setprecision(2) ;
  for( i = 0; i < 5; i ++ )
     employ[i] -> print() ;
  for( i = 0; i < 5; i ++ )
     cout << employ[i]->getName() << "  " << employ[i] -> earnings() << endl ;//利用多态性,访问不同派生类成员函数
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

季沐晴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值