C++(四十一) — 多态、虚函数、虚析构函数、纯虚函数
1、多态
面向对象程序设计中,多态性表现为:
(1)重载多态:函数重载、运算符重载;
(2)运行多态:通过基类的指针(或引用)调用不同派生类的同名函数,表现出不同的行为;
(3)模板多态:参数多态,通过一个模板得到不同的函数或不同的类,具有不同的特性和不同的行为;
2、同名覆盖与重载
(1)override(同名覆盖)
在类的继承中才会出现,多个函数的原型是相同的。
(2)overload(重载)
在同一作用域范围内,由参数个数或类型不同的多个同名函数构成。
3、虚函数
原因:通过指针调用成员函数时,只能访问到基类的同名成员函数。在同名覆盖现象中,通过某个类的对象(指针及引用)调用同名函数,编译器会将该调用静态联编到该类的同名函数,也就是说,通过基类对象指针是无法访问派生类的同名函数的,即使这个指针是用派生类对象来初始化的。
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
虚函数给基类指针访问派生类同名函数的一个机会;
指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
可以说,基类声明的虚函数,在派生类中也是虚函数,即使不再使用virtual关键字。
虚函数实现多态的原理:
- 当类中声明虚函数时,编译器会在类中生成一个虚函数表,用于存储类成员函数的指针,由编译器自动生成和维护;
- 存在虚函数时,每个对象都有一个指向虚函数表的指针(对于派生类的虚函数表,先放父类,后方子类,函数覆盖时就用子类的同名函数代替父类的);
- 编译器确定是否为虚函数,如是则根据对象的指针,找到所指虚函数表查找函数并调用。
动态联编:一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
虚函数只能借助于指针或者引用来达到多态的效果。但会为程序引入较大的开销,实际中应尽量避免。
注意:
虚函数在基类中声明,构造函数、静态成员函数不可以为虚函数,但析构函数可以。
使用角度:虚函数通过父类指针调用子类的成员函数,而构造函数在创建时自动调用,无需父类指针;
存储角度:虚函数对应一个指向虚函数表的指针,虚函数表通过构造函数初始化,若构造函数为虚,则需要通过虚函数表来找到虚构造函数的入口地址,而此时无虚函数表,所以构造函数不能为虚函数。
4、虚析构函数
只有虚析构函数,没有虚构造函数。
创建派生类对象时,调用基类构造->派生类构造->派生类析构->基类析构。
如果用new运算符动态创建派生类对象,并以此对象地址初始化基类指针,构造没问题,但用delete运算符删除派生类对象时,由于指针是指向基类的,通过静态联编,调用基类析构函数,不调用派生类析构函数,使得派生类无法执行某些清理工作,例如:派生类中申请的内存没机会还给系统。
虚析构函数:基类设置虚析构函数,派生类都是。此时使用基类对象指针销毁派生类对象时,会通过动态联编调用派生类析构函数,完成派生类的清理工作。
5、纯虚函数
如下声明表示一个函数为纯虚函数:
class A
{
public:
virtual void foo()=0; // =0 标志一个虚函数为纯虚函数,没有函数体,不可实例化,不可被调用。
};
一个函数声明为纯虚后,纯虚函数的意思是:我是一个抽象类!不要把我实例化!纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。
纯虚函数的引入,是出于两个目的:
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。实际上是限制了派生类的功能,规范接口,把实现留给子类。
2、为了效率,不是程序执行的效率,而是为了编码的效率。
6、运行时多态的应用实例
(1)头文件 shape.h
#pragma once //#ifdef SHAPE_H #include <iostream> using namespace std; class Shape { public: virtual double getArea() const = 0;//纯虚函数 void print() const; virtual ~Shape() {} //虚析构函数 }; class Circle :public Shape { public: Circle(int xv= 0, int yv= 0, double rv= 0.0); double getArea() const; void print() const; protected: int x, y; double r; }; class Rectangle :public Shape { public: Rectangle(int av= 0, int bv = 0); double getArea() const; void print() const; protected: int a, b; }; //#endif // DEBUG
(2)函数定义 shape.c
#include <iostream> using namespace std; #include "shape.h" void Shape::print() const { cout << "Base class Object" << endl; } Circle::Circle(int xv, int yv, double rv) { x = xv; y = yv; r = rv; } double Circle::getArea() const { return 3.14*r*r; } void Circle::print() const { cout << "center is" << x << " " << y << endl; } Rectangle::Rectangle(int av, int bv) { a = av; b = bv; } double Rectangle::getArea() const { return a*b; } void Rectangle::print() const { cout << "h is " << a << " " << b << endl; }
3、测试文件
#include <iostream> using namespace std; #include "shape.h" void creat_object(Shape **ptr); void display_area(Shape *ptr); void delete_object(Shape *ptr); void main() { Shape *shape_ptr; creat_object(&shape_ptr); display_area(shape_ptr); delete_object(shape_ptr); system("pause"); } void creat_object(Shape **ptr) { char type; *ptr = nullptr; do { cout << "创建对象:" << endl; cin >> type; switch (type) { case 'c': { int xx, yy; double rr; cout << "请输入圆心及半径:"; cin >> xx >> yy >> rr; *ptr = new Circle(xx, yy, rr); break; } case 'r': { int aa, bb; cout << "请输入矩形的长和宽:"; cin >> aa >> bb; *ptr = new Rectangle(aa,bb); break; } default:cout << "请重新选择\n" << endl; } } while (*ptr == nullptr); } void display_area(Shape *ptr) { cout << ptr->getArea() << endl; } void delete_object(Shape *ptr) { delete(ptr); }