c++面向对象编程:多态性,虚函数,抽象类,纯虚函数

系列文章目录

第一章 c++继承
第二章c++指针
第三章c++运算符重载



前言

最近在准备研究生复试,学习c++,就当作是学习笔记了


一、类继承层次中对象之间的关系

1.从派生类对象调用基类函数

  • 基类指针指向基类对象
  • 派生类指针指向派生类对象
  • 基类指针指向派生类对象
    把派生类对象的地址赋给了基类指针。C++编译器允许这样“交叉赋值”,因为每个派生类对象都是一个基类对象。请注意,虽然事实上基类的指针指向了派生类的对象,但调用的仍是基类的成员函数(而不是派生类的成员函数)。被调用的功能取决于用来调用函数的句柄(如指针或者引用)类型,而不是句柄所指向的对象类型。调用对象类型功能是可行的,而非调用句柄类型功能。这对于实现多态性的行为是至关重要的。

2.将派生类指针指向基类对象

这是不允许的
通过一个派生类指针,可以为它所指向的对象(基类对象)调用派生类的成员函数和数据成员。然而,基类对象并没有提供派生类所独有的成员函数,和数据成员。这就会导致一些问题。

3.通过基类指针调用派生类的成员函数

利用基类指针,编译器只允许调用基类的成员函数。因此,如果基类指针指向了派生类对象,并且试图访问只在派生类中拥有的成员函数,那么就会产生编译错误。该句柄只能调用与它关联的类类型的成员函数。

经证实,C++编译器确实允许通过指向派生类对象的基类指针访问只在派生类中拥有的成员,只要显式地把这样的基类指针强制转换为派生类指针,这就是向下强制类型转换( downcasting)技术。
调用基类中声明的函数。向下强制类型转换技术允许程序通过指向派生类对象的基类指针,执行只有派生类才拥有的操作。经过向下强制类型转换之后,程序就可以调用基类中没有的派生类函数。向下强制类型转换具有潜在危险。

二、virtual函数和virtual析构函数

使用了virtual函数,调用成员函数的对象的类型而不是句柄的类型,决定调用哪个版本的virtual函数。

1.virtual函数

让程序根据任意给定时刻基类指针所指向的对象的类型,动态地(即在执行时)决定应该调用哪个派生类的成员函数,这就是多态行为。
virtual 函数的声明
要允许多态行为,必须在基类中把成员函数声明为virtual函数,在每个派生类中重写成员函数。从实现的角度看,重写一个函数与重新定义一个函数没什么不同(后者是我们至今一直在使用的方法)。

2.virtual函数的声明

在派生类中重写的函数和它重写的基类函数具有相同的签名和返回值类型(即原型)。如果没有把基类函数声明为virtual,那么可以重新定义这个函数。相反,如果将基类函数声明为virtual,那么可以重写此函数并导致多态性行为。要声明一个virtual 函数,必须在基类中该函数的原型前加上关键字virtual。例如
virtual void draw() const;
声明出现在基类 Shape中。上述的函数原型声明draw 函数是一个无参数的、无返回值的 virtual函数。这个函数声明为const,virtual函数不一定非要声明为const。
一旦一个函数声明为virtual,那么从整个继承层次的那一点起向下的所有类中,它将保持是virtu-al的,即使当派生类重写此函数时并没有显式地将它声明为virtual。

3.调用虚函数

  • 通过基类指针或引用调用虚函数
    如果程序通过指向派生类对象的基类指针或者指向派生类对象的基类引用调用virtual 函数,那么程序会根据所指对象的类型而不是指针类型,动态(即执行时)选择正确的派生类函数。在执行时(不是编译时)选择合适的调用函数称为动态绑定或迟绑定。

  • 通过对象名称调用虚函数
    当virtual 函数通过按名引用特定对象和使用圆点成员选择运算符的方式被调用时,调用哪个函数在编译时就已经决定了(称为静态绑定),所调用的virtual 函数正是为该特定对象所属的类定义的(或继承而来的)函数,这并不是多态性行为。因此,使用virtual函数进行动态绑定只能通过指针(以及即将看到的引用)句柄完成。

4.虚析构函数

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
如果基类析构函数声明为virtual,那么任何派生类的析构函数都是virtual并重写基类的析构函数。 virtual 析构函数如下:
virtual ~class() {}
现在,如果对一个基类指针用delete运算符来显式地删除它所指的类层次中的某个对象,那么系统会根据该指针所指对象调用相应类的析构函数。当一个派生类对象被销毁时,派生类对象中属于基类的部分也会被销毁,因此执行派生类和基类的析构函数是很重要的。基类的析构函数在派生类的析构函数执行之后自动执行。
如果一个类含有virtual函数,该类就要提供一个virtual析构函数,即使该析构函数并不一定是该类需要的。这可以保证当一个派生类的对象通过基类指针删除时,这个自定义的派生类析构函数(如果存在的话)会被调用。
构造函数不能是virtual函数,声明一个构造函数为virtual函数是一个编译错误。

5.final成员函数和类

基类的 virtual 函数在原型中声明为 final,那么,该函数在任何派生类中都不能被覆盖。这保证了基类 final 成员函数定义被所有基类对象和所有基类直接、非直接派生类的对象使用。

三、抽象类和纯虚函数

1.抽象类

当把一个类作为一个类型时,都假设程序将创建这种类型的对象。然而,在有些情况下,定义程序员永远不打算实例化任何对象的类是有用的。这样的类称为抽象类( abstract class)。因为通常抽象类在类的继承层次结构中作为基类,所以我们称它们为抽象基类。这些类不能用来实例化对象,因为抽象类是不完整的——其派生类必须在这些类的对象实例化前定义那些“缺少的部分”。
构造抽象类的目的是为其他类提供适合的基类。可以用来实例化对象的类称为具体类( concrete class)。这些类为它们声明的每一个成员函数定义或继承实现。抽象基类太宽泛以至于无法定义真实的对象,在可以考虑实例化对象之前,需要更加具体的内容。

2.纯虚函数

通过声明类的一个或多个virtual函数为纯virtual 函数,可以使一个类成为抽象类。一个纯virtual 函数是在声明时“初始化值为0”的函数,如下所示:
virtual void draw() const = 0;
“=0”称为纯指示符( pure specifier)。
纯virtual 函数不提供函数的具体实现,每个派生的具体类必须重写所有基类的纯 virtual 函数的定义,提供这些函数的具体实现。virtual 函数和纯virtual函数之间的区别是: virtual函数有函数的实现,并且提供派生类是否重写这些函数的选择权。相反,纯virtual函数并不提供函数的实现,需要派生类重写这些函数以使派生类成为具体类,否则派生类仍然是抽象类。当基类实现一个函数是没有意义的,并且程序员希望在所有具体的派生类中实现这个函数时,就会用到纯virtual函数。
抽象类为类层次结构中的各种类定义公共的通用接口。抽象类包含一个或多个纯virtual函数,这些函数必须在具体的派生类中重写。
未能在派生类中重写纯virtual函数会使得派生类也变成抽象的。
抽象类至少含有一个纯virtual函数。抽象类也可以有数据成员和具体的函数(包括构造函数和析构函数),它们被派生类继承时都符合继承的一般规则。
虽然我们不能实例化抽象基类的对象,但是可以使用抽象基类来声明指向抽象类派生出的具体类对象的pointers和references。典型地,程序使用这些指针和引用来多态地实现派生类对象。


猜你喜欢

转载自blog.csdn.net/weixin_43821215/article/details/123660780
今日推荐