最近在校内网广泛流传一篇笑话,说是一个 IT 男去相亲的时候问了别人三个问题,其中第一个问题是:“C++ 中的虚函数和纯虚函数有什么区别?”结果那个女孩子没答上来。

考虑到明天俱乐部里刚好要讲到这个问题,我就先在这里写了吧。

虚函数

首先介绍 C++ 里的虚函数。首先假设 B 继承了 A, 也就是说 A 是 B 的父类,并且 A 里定义了某个函数,B 也定义了一个相同名字、相同参数的函数,此时虚函数的作用就是,当从一个 A 的指针指向的 B 的对象调用这个函数的时候,能够正确地调用 B 中的这个名字的函数,而不是父类 A 中的这个函数。

其实上面那一段看不懂是相当正常的,我的表达水平有点差。不过如果用代码的话,意思应该相当清楚:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

class A {
public:
    void foo() {
        cout << 'A' << endl;
    }
};

class B : public A {
public:
    void foo() {
        cout << 'B' << endl;
    }
};

int main() {
    A *p = new B();
    p->foo(); // 这里调用的是 A::foo() 而不是 B::foo(), 也就是说会输出 A.
    return 0;
}

可能有的同学对 A *p = new B(); 表示有点糊涂:怎么 B 的对象可以给 A 类型的指针呢?其实这样是可以的,可以的原因,就是因为 A 是 B 的父类。就好像人类是男人和女人的父类一样,你总不能说一个男人或者女人不是人类吧?所以,子类的指针是可以转换成父类的指针的。(反之则不一定。)

那么,下一句 p->foo(); 调用的却是 A 的 foo, 这是我们不希望的。此时的解决方案是,在 class A 的 foo 的声明前加上 virtual, 也就是把 foo 声明成虚函数:

1
2
3
4
5
6
class A {
public:
    virtual void foo() {
        cout << 'A' << endl;
    }
};

其实如果你在父类的某个函数处声明了 virtual, 即使在子类中没有 virtual, 编译器也会认为它是虚函数,而且应该不报错,而且从效果上和本质上,都是一样的。但是我还是推荐在子类中也显式地写上 virtual.

相信看过代码之后大家也不难理解那段晦涩的文字描述了。

既然说到虚函数和纯虚函数的区别,这里还要说一些虚函数的 FAQ:

  1. 一个类有虚函数,并会让它能实例化一个对象。
  2. 若父类有一个虚函数,子类没有重写父类的这个函数,那么调用的时候,也会调用到父类的这个函数。

纯虚函数

纯虚函数是在类的声明里声明为 = 0 的函数(具体见下面的示例代码)。纯虚函数有如下特点:

  1. 如果一个类定义了纯虚函数,那么这个类叫虚基类。
  2. 如果一个类定义了纯虚函数,那么将不能在这个类中实现该函数,而只能在继承该类的类中实现。
  3. 如果一个类是虚基类,那么这个类不能实例化对象。
  4. 如果一个类是虚基类,继承它的类并没有实现所有的纯虚函数,那么这个继承它的类也是虚基类。

纯虚函数也能让基类指针所指的子类对象调用到正确的函数。

以下是纯虚函数的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

using namespace std;

class A {
public:
    virtual void foo() = 0; // 声明了一个纯虚函数,A 变成了虚基类,不能实例化对象。
};

class B : public A {
public:
    virtual void foo() {
        cout << 'B' << endl;
    }
};

int main() {
    A *p = new B();
    p->foo();
    return 0;
}

总结

最后总结一下,虚函数和纯虚函数的共同点就是能够让基类指针指向的子类对象调用到正确的函数。它们的区别,是定义了纯虚函数的类,不能实现这些函数;定义了纯虚函数的类,或者继承了虚基类而未实现所有的纯虚函数的类,也就是虚基类,不能实例化对象。

再给一个示例代码,代码中有一个虚基类 Shape, 派生出了圆形、矩形和三角形等图形,使用统一接口计算图形的面积。然后定义了一个函数,能够把一个 Shape 指针数组中所指向的所有不同的图形的面积总和计算出来。这个代码应该算是充分利用了所谓多态了吧,希望对大家有帮助。

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <iostream>
#include <cstdlib>
#include <fstream>
#include <vector>

using namespace std;

///////////////////////////////////////////////////////////////////////////////

class Shape {
public:
    virtual float GetArea() const = 0;
};

///////////////////////////////////////////////////////////////////////////////

class Rectangle : public Shape {
public:
    Rectangle(float w, float h);
public:
    float GetArea() const;
protected:
    float width;
    float height;
};

Rectangle::Rectangle(float w, float h)
    : width(w)
    , height(h)
{
}

float Rectangle::GetArea() const {
    return width * height;
}

///////////////////////////////////////////////////////////////////////////////

class Triangle : public Shape {
public:
    Triangle(float a, float h);
public:
    float GetArea() const;
protected:
    float bottom;
    float height;
};

Triangle::Triangle(float a, float h)
    : bottom(a)
    , height(h)
{
}

float Triangle::GetArea() const {
    return bottom * height / 2.f;
}

///////////////////////////////////////////////////////////////////////////////

class Circle : public Shape {
public:
    Circle(float r);
public:
    float GetArea() const;
protected:
    float radius;
};

Circle::Circle(float r)
    : radius(r)
{
}

float Circle::GetArea() const {
    return 3.14159265f * radius * radius;
}

///////////////////////////////////////////////////////////////////////////////

float CalculateTotalArea(const vector<Shape *> &foo) {
    vector<Shape *>::const_iterator it;
    float area = 0.f;
    for (it = foo.begin(); it != foo.end(); ++it)
        area += (*it)->GetArea();
    return area;
}

void MainInput(istream &is, vector<Shape *> &shapes) {

    int shapeId = 0;

    float a, b;
    Shape *p = NULL;

    while (is >> shapeId) {

        switch (shapeId) {

        case 1: // Rectangle
            is >> a >> b;
            p = new Rectangle(a, b);
            break;

        case 2: // Triangle
            is >> a >> b;
            p = new Triangle(a, b);
            break;

        case 3: // Circle
            is >> a;
            p = new Circle(a);
            break;

        default:
            return;
        }

        shapes.push_back(p);
    }
}

void ReleaseVector(vector<Shape *> &foo) {
    Shape *p = NULL;

    while (!foo.empty()) {
        p = foo.back();
        delete p;
        foo.pop_back();
    }
}

int main(int argc, char *argv[]) {

    vector<Shape *> shapes;

    cout << "This program can calculate the total area of rectangle, triangle and circle." << endl;
    cout << "1 stands for rectangle, 2 stands for triangle, 3 stands for circle." << endl;
    cout << endl;

    ifstream fin("test.txt");

    MainInput(fin, shapes);

    cout << CalculateTotalArea(shapes) << endl;

    ReleaseVector(shapes);

    return EXIT_SUCCESS;
}

原创文章,转载请注明来源:http://euyuil.com/2363/virtual-or-pure-in-cplusplus/