C++ 基础知识点 九 第10章 多态性与虚函数

来源:互联网 发布:thinking in java pdf 编辑:程序博客网 时间:2024/05/16 10:42

第10章  多态性与虚函数

【内容提要】

多态性的概念;

函数和运算符的重载;

虚函数和抽象类。

【重点与难点】

10.1 多态性的概念

在面向对象的概念中,多态性是指不同对象接收到相同消息时,根据对象类的不同产生不同的动作。

由静态联编支持的多态性称为编译时的多态性或静态多态性,也就是说,确定同名操作的具体操作对象的过程是在编译过程中完成的。C++用函数重载和运算符重载来实现编译时的多态性。

由动态联编支持的多态性称为运行时的多态性活动太多态性,也就是说,确定同名操作的具体操作对象的过程是在运行过程中完成的。C++用继承和虚函数来实现运行时的多态性。

10.2 函数和运算符的重载

10.2.1 函数重载

面向对象程序设计中,函数的重载表现为两种情况:第一种是参数个数或类型有所差别的重载,第二种是函数的参数完全相同但属于不同的类。

10.2.2 运算符重载

C++预定义的运算符只是对基本数据类型进行操作,而对于自定义的数据类型比如类,却没有类似的操作。为了实现对自定义类型的操作,就必须自己编写程序来说明某个运算符作用在这些数据类型上时,应该完成怎样的操作,这就要引入运算符重载的概念。

        运算符的重载形式有两种,一种是重载为类的成员函数,一种是重载为类的友元函数。将运算符重载为它将要操作的类的成员函数,称为成员运算符函数。实际使用时,总是通过该类的某个对象访问重载的运算符。

成员运算符函数的定义:

    在类内声明的一般形式为:

    <返回类型> operator<运算符>(参数表);

    在类外定义的一般形式为:

    <返回类型> <类名∷>operator<运算符>(参数表)

    {

      函数体

    }

其中,operator是定义运算符重载函数的关键字;运算符是要重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。

        将重载的运算符函数定义为类的友元函数,称为友元运算符函数。友元运算符函数不是类的成员,它在类内声明原型,在类外定义函数本身。由于它不是类的成员函数,不属于任何一个类对象,所以没有this指针,因此,重载双目运算符时要有两个参数,重载单目运算符时只要一个参数就可以了。

友员运算符函数的定义:

    在类内声明的一般形式为:

    friend<返回类型>operator<运算符>(参数表);

    在类外定义的一般形式为:

    <返回类型> operator<运算符>(参数表)

    {

      函数体

    }

其中,friend是声明友元函数的关键字, operator是定义运算符重载函数的关键字;运算符是要重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。

        几种典型运算符的重载

①         加法运算符“+”的重载  . * :: ? :不能重载

②    “++”和“--”的重载

③    赋值运算符“=”的重载

④    函数调用运算符“()”的重载

⑤    下标运算符“[ ]”的重载

10.3 虚函数和抽象类

虚函数是重载的另一种形式,实现的是动态的重载,即函数调用与函数体之间的联系是在运行时才建立,也就是动态联编。

10.3.1 虚函数的定义和使用

虚函数的定义是在基类中进行的,即把基类中需要定义为虚函数的成员函数声明为virtual。当基类中的某个成员函数被声明为虚函数后,它就可以在派生类中被重新定义。在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数和类型、参数的顺序都必须与基类中的原型完全一致。

   虚函数定义的一般形式为:

   virtual<函数类型><函数名>(参数表)

   {

     函数体

}

使用虚函数时应注意如下问题:

①    虚函数的声明只能出现在类声明的函数原型的声明中,不能出现在函数体实现的时候,而且,基类中只有保护成员或公有成员才能被声明为虚函数。

②    在派生类中重新定义虚函数时,关键字virtual可以写也可不写,但在容易引起混乱时,应写上该关键字。

③    动态联编只能通过成员函数来调用或通过指针、引用来访问虚函数,如果用对象名的形式来访问虚函数,将采用静态联编。

④    虚函数必须是所在类的成员函数,不能是友元函数或静态成员函数。但可以在另一个类中被声明为友元函数。

⑤    构造函数不能声明为虚函数,析构函数可以声明为虚函数。

⑥    由于内联函数不能在运行中动态确定其外治,所以它不能声明为虚函数。

10.3.2 纯虚函数和抽象类

抽象类是一种特殊的类,它为一族类提供统一的操作界面,建立抽象类就是为了通过它多态地使用其中的成员函数。抽象类是带有纯虚函数的类。

一个抽象类至少带有一个纯虚函数。纯虚函数是在一个基类中说明的虚函数,它在该基类中没有具体的操作内容,要求各派生类在重新定义时根据自己的需要定义实际的操作内容。纯虚函数的一般定义形式为:

    virtual<函数类型><函数名>(参数表)=0;

纯虚函数与普通虚函数的定义的不同在于书写形式上加了“=0”,说明在基类中不用定义该函数的函数体,它的函数体由派生类定义。

如果一个类中至少有一个纯虚函数,这个类就成为抽象类。它的主要作用是为一个族类提供统一的公共接口,以有效地发挥多态的特性。使用时应注意以下问题:

①    抽象类只能用作其它类的基类,不能建立抽象类的对象。因为它的纯虚函数没有定义功能。

②    抽象类不能用作参数类型、函数的返回类型或显式转换的类型。

③    可以声明抽象类的指针和引用,通过它们,可以指向并访问派生类对象,从而访问派生类的成员。

④    若抽象类的派生类中没有给出所有纯虚函数的函数体,这个派生类仍是一个抽象类。若抽象类的派生类中给出了所有纯虚函数的函数体,这个派生类不再是一个抽象类,可以声明自己的对象。

 

【典型例题】

例题1.        下面关于虚函数和函数重载的叙述不正确的是(  )。

(a)虚函数不是类的成员函数

(b)虚函数实现了C++的多态性

(c)函数重载允许非成员函数,而虚函数则不行

(d)函数重载的调用根据参数的个数、序列来确定,而虚函数依据对象确定

解答:

函数重载和虚函数是C++中实现多态性的两种手段,但是它们的实现机制是不一样的;函数重载依据调用时的参数进行区分,而虚函数则根据对象实际的指向确定调用的版本。答案为:a。

 

例题2.        (  )是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的版本。

(a)纯虚函数     (b)虚析构函数      (c)虚构造函数     (d)静态成员函数

解答:

抽象类中的纯虚函数没有具体的定义,需要在抽象类的派生类中定义。因此,纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的版本。答案为:a

 

例题3.        实现运行时的多态性要使用(  )。

(a)构造函数      (b)析构函数      (c)重载函数     (d)虚函数

解答:

动态联编要在程序运行时才能确定调用哪个函数。虚函数是实现动态联编的必要条件之一,没有虚函数一定不能实现动态联编。答案为:d。

 

例题4.        关于虚函数的描述中,(  )是正确的。

(a)派生类的虚函数与基类的虚函数具有不同的参数个数和类型

(b)基类中说明勒虚函数后,派生类中其对应的函数一定要说明为虚函数

(c)虚函数是一个成员函数

(d)虚函数是一个static类型的成员函数

解答:

为实现某种功能而假设的函数称为虚函数。虚函数是用关键字virtual进行说明。虚函数是动态联编的基础。虚函数只能是类中的一个成员函数,但不能是静态成员;派生类的虚函数与基类的虚函数具有相同的参数个数和类型,当派生类的虚函数与基类中的对应的虚函数的参数不同时,派生类的虚函数将丢失虚特性,变为重载函数;对于基类中的虚函数,在派生类中自然是虚函数,可不必说明。答案为:c。

例题5.        下面的程序中,有错误的语句是__________。

class A //①

{

public:    //②

      A()

{

       func();  //③

}

virtual void func()=0;  //④

};

解答:

在成员函数内可以调用纯虚函数,但在构造函数或析构函数内调用一个纯虚函数将导致程序运行错误,因为没有为纯虚函数定义代码。答案为:③

 

 

例题6.        运行下列程序的结果为__________________。

#include<iostream.h>

class base

{

public:

      void display1(){cout<<"base::display1()"<<endl;}

      virtual void display2(){cout<<"base::display2()"<<endl;}

};

classderived:public base

{

public:

      void display1(){cout<<"derived::display1()"<<endl;}

      void display2(){cout<<"derived::display2()"<<endl;}

 

};

void main()

{

      base * pbase;

      derived d;

      pbase=&d;

      pbase->display1 ();

      pbase->display2();

}

解答:

本题主要考查有关多态性的相关知识。在基类base中,定义了一个函数display1()和虚函数display2();在派生类derived中,重写了函数display1(),而且重新定义的虚函数display2()。由于基类指针pbase指向的是派生类的一个对象,因而会调用派生类的display2()版本,但是对于一般的成员函数display1(),仍然遵循一般的调用规则,只调用基类的display1()版本。本题答案为:

base::display1()

derived::display2()

 

例题7.        下面的程序的输出结果为an animal  a person   ananimal   a person,请将程序补充完整。

#include<iostream.h>

class animal{

public:

      ______①_____ void speak(){cout<<"Ananimal"<<"  ";}

};

classperson:public animal{

public:

      void speak(){cout<<"a person"<<"  ";}

};

void main()

{

      animal a,_______②_______;

      person p;

      a.speak();

      p.speak();

      pa=&a;

      pa->speak();

      ________③_______;

      pa->speak();

}

解答:

本题主要考查对多态性的理解与应用。本题通过虚函数实现多态性,所以在基类中应定义虚函数;为了实现多态性,必须定义基类的指针,然后将它指向各个派生类的对象。本题答案为:①virtual、②*pa、③pa=&p

0 0
原创粉丝点击