首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

《小弟我的第一本C++书》节选:6.3.3 用虚函数实现多态

2014-01-05 
《我的第一本C++书》节选:6.3.3用虚函数实现多态6.3.3 用虚函数实现多态在理解了面向对象的继承机制之后,我

《我的第一本C++书》节选:6.3.3 用虚函数实现多态
6.3.3 用虚函数实现多态

在理解了面向对象的继承机制之后,我们知道了在大多数情况下派生类是基类的“一种”,就像学生是人中的一种一样。换句话说,学生是人的一种,那么在使用“人”的时候,这个“人”可以是“学生”,而“学生”也可以应用在“人”的场合。比如可以问“教室里有多少人”,实际上问的是教室里有多少学生。这种用基类指代派生类的关系反映到C++中,就是基类指针可以指代派生类的对象,而派生类的对象也可以当成基类对象使用。这样的解释对大家来说是不是很抽象呢?没关系,可以回想生活中经常遇到的场景:“上车的人请买票”。在这句话中,涉及一个类:人,以及它的一个动作:买票。上车的人可能是老师、学生,也可能是工人、农民或者某个程序员,为什么售票员不说“上车的老师请买票”或者说“上车的工人请买票”,而仅仅说“上车的人请买票”就足够了呢?这是因为“人”是基类,虽然上车的人可能是老师、学生、公司职员等,但是他们都是“人”这个基类的派生类,所以这里就可以用基类“人”来指代所有派生类对象,如图6-12所示。

图6-12 “上车的人请买票” 下面用实际的程序来描述这个场景:

 
// “上车买票”演示程序
// 定义Human类,这个类有一个成员函数BuyTicket()表示买票的动作
class Human
{
// Human类的行为
public:
 // 买票
void BuyTicket()
 {
 cout<<"人买票。"<<endl;
 }
private:
};

// 从“人”派生两个类,分别表示老师和学生
class Teacher : public Human
{
};

class Student : public Human
{
};

// 在主函数中模拟上车买票的场景
int _tmain(int argc, _TCHAR* argv[])
{
 // 声明一个基类的指针
Human* pPassenger = NULL;
 // 车上上来一位老师
pPassenger = new Teacher(); 
 // 老师买票
pPassenger->BuyTicket(); 
 delete pPassenger;

 // 车上上来一位学生
pPassenger = new Student(); 
 // 学生买票 
pPassenger->BuyTicket(); 
 delete pPassenger;

 pPassenger = NULL;

 return 0;
}

在这段程序中,我们先定义了一个基类Human,它有一个行为(函数)是买票(BuyTicket)。然后定义了它的两个派生类Teacher和Student,通过继承,这两个派生类也拥有了买票的行为。在主函数中,假设第一个上车的人是老师,创建一个老师对象,让Human类型的指针pPassenger来指代这个对象,再利用pPassenger指针调用BuyTicket()函数,完成买票动作。又假设第二个上车的人是学生,创建一个学生对象,还是让pPassenger指针指代这个对象并调用它的BuyTicket()函数完成买票动作。最后,程序的输出结果是:


人买票。 人买票。

细心的你一定已经注意到,老师和学生买票的动作是一样的,因为都没有定义自己的BuyTicker()函数,但是借助继承机制都拥有了BuyTicket()这样一个行为,对BuyTicket()的调用实质上是调用基类Human类的BuyTicket()函数。虽然继承机制可以让派生类轻松地拥有基类的行为,但是在现实生活中,这种直接得来的行为不一定能满足需要。例如,虽然Teacher类和Student类都是从Human类继承的,但是它们的买票方式可能是不同的,比如老师需要投币买票,而学生则可以刷卡买票。也就是说,同一个动作,可能基类和派生类以及各个派生类之间的实现方式是不同的。这就像老“龙王”有9个儿子,这9个儿子却各不相同。为了满足各个派生类对类的行为进行自定义的需要,C++提供了虚(virtual)函数的机制。在基类的函数声明前加上virtual关键字,这个函数就成为虚函数,这样,如果派生类对虚函数作重新定义,那么派生类的虚函数实现将覆盖基类对应的虚函数的实现。如果通过基类指针调用虚函数,那么将调用这个指针所指向的具体对象的虚函数,以此来替代基类的虚函数。这样就提供了一个机会让派生类可以根据自己的实际情况重新定义基类的函数以满足实际的需要。现在,可以用虚函数的机制来修改上面的例子,让它更加符合实际:

 
// 经过虚函数机制改写后的“上车买票”演示程序

// 定义Human类,提供公有接口
class Human
{
// Human类的行为
public:
 // 这里将BuyTicket()函数声明为虚函数,
// 表示其派生类可以对这个虚函数进行重新定义以满足实际的需要 
virtual void BuyTicket() 
 {
 cout<<"人买票。"<<endl;
 }
private:
};

// 在派生类中对虚函数进行重新定义
class Teacher : public Human
{
public:
 // 根据实际情况重新定义基类的虚函数
virtual void BuyTicket() 
 {
 cout<<"老师投币买票。"<<endl;
 }
};

class Student : public Human
{
public: 
 // 根据实际情况重新定义基类的虚函数
virtual void BuyTicket() 
 {
 cout<<"学生刷卡买票。"<<endl;
 }
};

使用虚函数机制改写程序之后,虽然在主函数中使用的还是基类的指针,但是执行的却是基类指针所指代的实际的派生类对象的函数。例如第一个“pPassenger->BuyTicket()”语句,因为这时pPassenger指针指向的是一个Teacher对象,而实际上执行的是Teacher类中的BuyTicket()函数,所以最后输出了更加符合实际的结果:


老师投币买票。 学生刷卡买票。

这里我们注意到,Human类已经实现了BuyTicket()函数,如果它的派生类Teacher或者Student不实现这个函数,那么通过派生类调用BuyTicket()函数,最终还是调用基类Human这个已经实现的函数。如果想强制派生类定义某个函数,则可以在基类中将这个函数声明为纯虚函数,也就是基类不实现这个虚函数,它的所有实现都留给派生类来完成。例如:

 
// 使用纯虚函数
// 这样Human类就成为了一个抽象类,仅提供接口
class Human
{
// Human类的行为
public:
 // 声明BuyTicket()函数为纯虚函数
// 它的实现留待派生类来完成
virtual void BuyTicket() = 0; 
private:
};

当类中有纯虚函数时,这个类就成为了一个抽象类,它仅用于对外界提供公有接口。同时,因为这个类包含有尚未完工的纯虚函数,所以不能创建抽象类的具体对象。如果试图创建一个抽象类的对象,将产生一个编译错误。例如:

1
2
// 编译错误,不能创建抽象类的对象
Human aHuman;

如果某个类从抽象类派生,那么它必须实现其中的纯虚函数才能成为一个实体类,否则它将继续保持抽象类的特征。例如:

 
class Student : public Human
{
public: 
 // 实现基类中的纯虚函数,让Student类成为一个实体类
virtual void BuyTicket() 
 {
 cout<<"学生刷卡买票。"<<endl;
 }
};

面向对象的多态机制,为派生类修改基类的行为以满足实际情况的需要提供了一种可能。利用多态机制,可以为程序开发带来很多好处。
?接口统一,高度复用 应用程序不必为每个派生类编写具体的函数调用,只需要在基类中定义好接口就可,而具体实现再留给派生类自己去处理。这样就可以“以不变应万变”,大大提高了程序的可复用性(针对接口的复用)。
?向后兼容,灵活扩展 派生类的行为可以被基类的指针访问,可以很大程度上提高程序的可扩展性,因为一个基类的派生类可以很多,并且可以不断扩充。比如要增加一种乘客类型,只需要添加一个Human的派生类,实现自己的功能就可以了。在使用这个新创建的类的时候,无须修改程序代码中的调用形式 。

更多精彩章节,参考这里

[解决办法]

引用:

本来可以几句话说清楚的,结果说了一大堆。

不然怎么完成凑页数的宏伟大业。。《小弟我的第一本C++书》节选:6.3.3  用虚函数实现多态

热点排行