# 十二. 虚函数与多态性

# 1. 多态性概述

指的是不同对象对于同样的消息会产生不同的行为

而消息在C++语言中指的就是函数的调用,不同的函数可以具有多种不同的功能,而多态就是允许用同一个函数名的调用来实现不同的功能

多态性的体现

C++ 允许将一个对象的指针赋值给它的父类指针变量。而当通过父类指针调用一个虚函数时,则会调用子类中最后被重写的那个版本,这样对于同一段通过指针调用某个虚函数的代码,就会因为实际指向的对象不同,而调用不同函数,这就是所谓的多态性。

同理,通过引用调用一个虚函数,也会有这样的效果。

按照实现的时机,多态可以分为两类

  • 编译时多态

    指的是在程序编译过程中决定同名操作与对象的绑定关系,也称为静态绑定,典型的技术有函数重载、运算符重载、模板

    优点:程序运行的时候函数调用速度快、效率较高

    缺点:编程不够灵活

  • 运行时多态

    指的是在程序运行过程中动态地确定同名操作与具体对象的绑定关系,也称为动态绑定,主要通过使用继承和虚函数来实现

    在编译、连接过程中不确定绑定关系,程序运行之后才确定

    优点:编程更加灵活、系统易于扩展

    缺点:比静态绑定的函数调用速度慢些

    实现条件:

    1. 有虚函数
    2. 符合赋值兼容规则
    3. 由指针或引用去调用虚函数

# 2. 虚函数

格式:class 类名

{ … …

virtual 类型 成员函数名(参数表); }

… … };

虚函数必须存在于类的继承环境之中才有意义

虚函数在继承机制下具有向下传递性,即基类的某类函数是虚函数,则派生类的该类函数也是虚函数(不写virtual也是),比如基类析构函数是虚函数,那么派生类析构函数也是虚函数

继续用上一章最后一个例子,从上章中该例中的输出结果中可以看出:不管基类的对象、指针、引用指向的是基类对象还是派生类对象,最后的show函数调用的都是基类中的show函数,没有调用过派生类中的

修改:

在shape类和circle类的show和Area函数的首部类型前加virtual,变为虚函数。主函数如下:

int main()
{
    Shape s1,s2(2,2);//建立了两个基类的对象s1和s2
    s1 = s2;//同一类型的对象可以赋值
    s1.show();
    Circle c(30,30,8);//建立了一个对象圆,中心点在(30,30),半径是8
    s1 = c;//基类的对象等于派生类的对象
    s1.show() ;
    Shape *s3 = &s2;//基类的指针指向基类的对象
    s3->show();
    
    cout<<"---------------------------"<<endl;
    Shape *s4 = &c;//基类的指针指向派生类的对象
    s4->show();
    s4->Area();
    Shape &s5 = s2;//基类的引用指向基类的对象
    s5.show();
    Shape &s6 = c;//基类的引用等于派生类的对象
    s6.show();
    s6.Area();   
}
输出结果:
    圆形的中心点是:(2,2)
    圆形的中心点是:(30,30)
    圆形的中心点是:(2,2---------------------------
    该图形是圆//调用的是circle的函数
    圆形的中心点是:(30,30)
    圆的面积是200.96
    圆形的中心点是:(2,2)
    该图形是圆
    圆形的中心点是:(30,30)
    圆的面积是200.96
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 虚析构函数

构造函数不能声明为虚函数,因为在执行构造函数时,对象还没有完成建立,谈不上函数与对象的绑定

  • 如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数
  • 专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显示地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理

:

#include<iostream>
using namespace std;
class Base
{
    int *pBase;
    public:
    Base()
    {
        pBase = new int;
        cout<<"Construct Base."<<endl;
    }
    ~Base()
    {
        delete pBase;
        cout<<"Deconstruct Base."<<endl;
    }
};
class Derived:public Base
{
    char *pDerived;
    public:
    Derived()
    {
        pDerived = new char;
        cout<<"Construct Derived."<<endl;
    }
    ~Derived()
    {
        delete pDerived;
        cout<<"Deconstruct Derived."<<endl;
    }
};
int main()
{
    Base *pb;//定义基类指针
    pb = new Derived();//基类指针指向新生成的派生类堆对象
    delete pb;//释放对象
    return 0;
}
输出结果:
    Construct Base.
    Construct Derived.
    Deconstruct Base.
//只析构了Base,没有析构Derived,因为pb指针是Base类型,所以调用Base的析构函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

:

#include<iostream>
using namespace std;
class Base
{
    int *pBase;
    public:
    Base()
    {
        pBase = new int;
        cout<<"Construct Base."<<endl;
    }
    virtual ~Base()//虚析构函数
    {
        delete pBase;
        cout<<"Deconstruct Base."<<endl;
    }
};
class Derived:public Base
{
    char *pDerived;
    public:
    Derived()
    {
        pDerived = new char;
        cout<<"Construct Derived."<<endl;
    }
    virtual ~Derived()//虚析构函数,子类可以是虚析构函数,也可以不是
    {
        delete pDerived;
        cout<<"Deconstruct Derived."<<endl;
    }
};
int main()
{
    Base *pb;//定义基类指针
    pb = new Derived();//基类指针指向新生成的派生类堆对象
    delete pb;//释放对象
    return 0;
}
输出结果:
    Construct Base.
    Construct Derived.
    Deconstruct Derived.
    Deconstruct Base.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 3. 纯虚函数和抽象类

纯虚函数定义格式:virtual 类型 函数名()= 0;

  • 包含有纯虚函数的类是抽象类
  • 抽象类不能实例化,即不能定义对象
  • 抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,因而不再是抽象类。反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类

:

#include<iostream>
using namespace std;
class Shape
{
    public:
    virtual void area()//虚函数
    {
        cout<<"不知道怎么算面积"<<endl;
    }
};
class Circle:public Shape
{
    int radius;
    public:
    Circle(int r)
    {
        radius = r;
    }
    void area()//重写父类area函数
    {
        cout<<"area="<<3.14*radius*radius<<endl;
    }
};
class Rectangle:public Shape
{
    int width,height;
    public:
    Rectangle(int w,int h)
    {
        width = w;
        height = h;
    }
    void area()//重写父类area函数
    {
        cout<<"area="<<width*height<<endl;
    }
};

void getArea(Shape *p)//工具函数,调用传入的类指针,指向的计算面积的函数
{
    p->area();
}
int main()
{
    Shape s;//此时Shape类不是抽象类,Area函数不是纯虚函数,可以实例化对象
    s.Area();
    Circle c(1);
    Rectangle r(4,5);
    getArea(&c);
    getArea(&r);
    return 0;
}
输出结果:
    不知道怎么算面积//此时可以看到基类中虚函数体里不需要内容
    area=3.14
    area=20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

:

#include<iostream>
using namespace std;
class Shape
{
    public:
    virtual void area() = 0//纯虚函数,为了提供统一接口,实现多态
    
};
class Circle:public Shape
{
    int radius;
    public:
    Circle(int r)
    {
        radius = r;
    }
    void area()//重写父类纯虚函数
    {
        cout<<"area="<<3.14*radius*radius<<endl;
    }
};
class Rectangle:public Shape
{
    int width,height;
    public:
    Rectangle(int w,int h)
    {
        width = w;
        height = h;
    }
    void area()//重写父类纯虚函数
    {
        cout<<"area="<<width*height<<endl;
    }
};

void getArea(Shape *p)//工具函数,调用传入的类指针说指向的计算面积的函数
{
    p->area();
}
int main()
{
    //? Shape s;//此时Shape类是抽象类,Area函数是纯虚函数,不可以实例化对象
    //? s.Area();
    Circle c(1);
    Rectangle r(4,5);
    getArea(&c);
    getArea(&r);
    return 0;
}
输出结果:
    area=3.14
    area=20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
上次更新时间:: 2/9/2025, 9:12:58 PM