C++__多态性与虚函数

多态性的概念

多态性(polymorphism)是面向对象程序设计的一个重要特征。利用多态性可以设计和实现一个易于扩展的系统。

  • C++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数
    • 在面向对象方法中的多态性: 向不同的对象发送同一个消息(函数名),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。

      优点:C++程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。

      多态性分为两类: 静态多态性和动态多态性。

      • 静态多态性:函数重载和运算符重载属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时的多态性。
      • 动态多态性:是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(virtual function)实现的。
    • 优点:C++程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。

      多态性分为两类: 静态多态性和动态多态性。

      • 静态多态性:函数重载和运算符重载属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时的多态性。
      • 动态多态性:是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(virtual function)实现的。
3 虚函数
  • 1 虚函数的作用
    • 虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数
    • 方法:由基类指针访问各层次中的同名函数。
    • 由虚函数实现的动态多态性就是: 同一类族中不同类的对象,对同一函数调用作出不同的响应
    • 多态性理解:起始地址相同的不同对象指针,调同名函数时,响应不同(调了各自对应的函数)
    • 实现方法:将同名函数声明为虚函数
    • 虚函数的使用方法是: 注意规则!!!

      (1) 在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。

         在类外定义虚函数时,不必再加virtual。
    • C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。

      如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数

      (3) 必须定义一个指向基类对象的指针变量并使它指向同一类族中需要调用该函数的对象。

      (4) 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。

      (2) 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。

      注意区别:

      函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者是横向重载,后者可以理解为纵向重载。

      但与重载不同的是: 同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)。
  • 2 静态关联与动态关联
    • 确定调用的具体对象的过程称为关联(binding)在这里是指把一个函数名与一个类对象捆绑在一起,建立关联。一般地说,关联指把一个标识符和一个存储地址联系起来。
    • 函数重载和通过对象名调用的虚函数,在编译时即可确定其调用的虚函数属于哪一个类,其过程称为静态关联(static binding),由于是在运行前进行关联的,故又称为早期关联(early binding)。函数重载属静态关联。
    • 而运行时的多态性,编译系统在编译该行时是无法确定调用哪一个类对象的虚函数的。因为编译只作静态的语法检查,光从语句形式是无法确定调用对象的。
    • 由于是在运行阶段把虚函数和类对象“绑定”在一起的,因此,此过程称为动态关联(dynamic binding)。这种多态性是动态的多态性,即运行阶段的多态性。
    • 在运行阶段,指针可以先后指向不同的类对象,从而调用同一类族中不同类的虚函数。由于动态关联是在编译以后的运行阶段进行的,因此也称为滞后关联(late binding)。
  • 3 在什么情况下应当声明虚函数
    • 使用虚函数时,有两点要注意:

      (1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用:是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。
      (2) 应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数
    • 需要说明的是: 使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtual function table,简称vtable),它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的,因此,多态性是高效的。
      • 多态性进一步理解:

      多态性是指在运行时,能根据其类型确认调用哪个函数的能力。

      只支持类而不支持多态,称基于对象的;如VB。

      只有支持多态,才成为面向对象的。

      好处分析:如计算学生的学费,有大学生,研究生,博士生等。从程序设计看,用采取继承方式设计。希望只有一个收费员(函数)可以收各种学生的费用。 而不是首先有一个管理者判断是什么学生?再分派给各个类型的收费员(函数)去收费。
  • 4 虚析构函数
    • 析构函数的作用是在对象撤销之前做必要的清理现场的工作。当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数 
    • 原因是:如果希望能执行派生类的析构函数,方法:可以将基类的析构函数声明为虚析构函数
    • 先调用了派生类的析构函数,再调用了基类的析构函数,符合人们的愿望。

    • 特点与好处:

      如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数。这样,如果程序中显式地用了delete运算符准备删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。

      一般即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。
    • 构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。
  • 4 纯虚函数与抽象类
    仅供派生而无实际意义的函数,故纯虚之

    纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是

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

    注意: ①纯虚函数没有函数体;②最后面的=0并不表示函数返回值为0,告诉编译系统这是纯虚函数; ③这是一个声明语句,最后应有分号。

    纯虚函数的作用:

    纯虚函数只有函数的名字而不具备函数的功能。它只是通知编译系统: 在这里声明一个虚函数,留待派生类中定义纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。

    如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。
  • 2 抽象类
    • 如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来生成对象。定义这些类的惟一目的是用它作为基类去建立派生类。

      这种不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class)由于它常用作基类,通常称为抽象基类(abstract base class)。
    • 凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。

      抽象类的条件:包含纯虚函数

      使用规则:

      如果在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用。这个派生类就不是抽象类,而是可以用来定义对象的具体类(concrete class)。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象
    • 虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。
    • 总结:

      抽象类的实际作用:定义指向抽象类的指针,实现多态性操作。即对各纯虚函数可实现多态性操作。

      (1) 一个基类如果包含一个或一个以上纯虚函数,就是抽象基类。抽象基类不能也不必要定义对象。

      (2) 抽象基类与普通基类不同,它不是现实存在的对象的抽象(例如圆形(Circle)就是千千万万个实际的圆的抽象),它可以没有任何物理上的或其他实际意义方面的含义

      (3) 在类的层次结构中,顶层或最上面的几层可以是抽象基类。抽象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象基类中声明。

      (4) 抽象基类是本类族的公共接口。或者说,从同一基类派生出的多个类有同一接口。

      (5) 区别静态关联和动态关联

      (6) 如果在基类声明了虚函数,则在派生类中凡是与该函数有相同的函数名、函数类型、参数个数和类型的函数,均为虚函数(不论在派生类中是否用virtual声明)。

      (7) 使用虚函数提高了程序的可扩充性。

      把类的声明与类的使用分离。这对于设计类库的软件开发商来说尤为重要。开发商设计了各种各样的类,但不向用户提供源代码,用户可以不知道类是怎样声明的,但是可以使用这些类来派生出自己的类。

      利用虚函数和多态性,程序员的注意力集中在处理普遍性,而让执行环境处理特殊性。
    • 虚基类与抽象基类的区别前者解决模糊二义性问题;后者解决继承中的多态性问题。实现时,前者由在继承方式前加virtual; 后者靠纯虚函数实现。

      虚函数与纯虚函数对比:纯在抽象类中,初始化为0的函数。没有实际意义,为派生类保留名字,方便该函数实现多态性。虚函数有实际意义,也是为实现多态性服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值