面向对象编程(OOP)并不是一种特定的语言或者工具,它只是一种设计方法、设计思想。 OOP表现出来的三个最基本的特性就是封装、继承与多态。

在本文中我们将讨论C语言如何简单地实现OOP的基础功能,并且关注C++是如何实现OOP的,对于C++的讨论不涉及过多的语法细节,不涉及访问权限的讨论(全部使用public)。

封装

封装就是在形式上将数据和操作数据的方法打包在一起,然后提供部分接口给外部访问,隐藏内部的实现细节。 但是C语言没有直接提供将内部信息隐藏的机制,我们只能使用其它的间接方案:

  • 君子协定,约定只通过固定的接口进行访问;
  • 利用动态库的符号部分导出的特点将动态库的部分信息隐藏;
  • 利用static变量和函数对源文件外部不可见的性质,将部分信息隐藏

下面我们只关注数据和操作方法的打包,因为C语言的结构体只含有数据成员,我们需要通过函数指针在结构体中加上操作数据的函数,例如

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
#include <stdio.h>

typedef struct Point {
double x;
double y;
void (*show)(const struct Point *const);
void (*add)(struct Point *const, const struct Point *const);
} Point;

void show_point(const Point *const self) {
printf("(x, y) = (%f, %f)\n", self->x, self->y);
}

void add_point(Point *const self, const Point *const obj) {
self->x += obj->x;
self->y += obj->y;
}

Point make_point(double x, double y) {
Point obj;
obj.x = x;
obj.y = y;
obj.show = show_point;
obj.add = add_point;
return obj;
}

int main() {
Point p1 = make_point(1.0, 2.0);
p1.show(&p1);

Point p2 = make_point(3.0, 4.0);

p1.add(&p1, &p2);
p1.show(&p1);

return 0;
}

运行结果为

1
2
(x, y) = (1.000000, 2.000000)
(x, y) = (4.000000, 6.000000)

这里我们定义了Point对象,提供了一个它的构造函数make_point,在其中进行对象的初始化,并且自动用成员函数指针指向对应的函数, 然后就可以通过p.add(...)p.show(...)来调用对象的方法函数,就像访问对象的数据一样。 值得注意的是,我们必须显式地将当前对象自身的地址&p传递过去,才能保证在调用时不会产生对象的复制,修改始终是针对当前对象自身的。

我们也可以不在结构内中定义成员函数指针,放弃.风格的方法调用形式,直接使用普通的函数调用形式,例子改写如下

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
#include <stdio.h>

typedef struct Point {
double x;
double y;
} Point;

void show_point(const Point *const self) { printf("(x, y) = (%f, %f)\n", self->x, self->y); }

void add_point(Point *const self, const Point *const obj) {
self->x += obj->x;
self->y += obj->y;
}

Point make_point(double x, double y) {
Point obj;
obj.x = x;
obj.y = y;
return obj;
}

int main() {
Point p1 = make_point(1.0, 2.0);
show_point(&p1);

Point p2 = make_point(3.0, 4.0);

add_point(&p1, &p2);
show_point(&p1);

return 0;
}

将两个版本的C语言实现对比,我们可以发现它们其实各有优缺点:

  • 第一个版本,每一个对象在内存中都存储了函数指针(实际上是调用函数表),这可能导致额外的空间占用:如果实例化 \(m\) 个对象,每个对象有 \(n\) 个成员函数,那么就要占用 \(8mn\) 的内存。但是空间上的代价带来的好处是:我们可以直接通过修改对象的函数指针达到动态多态的目的,这甚至比C++的虚函数和虚表更灵活,C++的虚表是针对每一个类型的,但是这里的调用函数表是针对每一个对象的。
  • 第二个版本,放弃在每一个对象中存储函数指针,这节约了空间占用,但是从语法的角度来说,并没有实现数据和方法的严格绑定,对数据和方法的访问形式是不一样的,对方法的访问就和普通函数调用一样,只是在第一个参数传递了指向对象自身的指针。

在实践中两种方案都有被采用,也可能混合使用。

那么C++到底是怎么做的呢?C++在设计上遵顼Zero overhead 原则,这意味着在提供某种特性、功能或抽象的同时,不对程序或系统引入额外的性能开销或资源消耗。 显然第一个版本不符合这个原则,C++提供的实现其实与C语言实现的第二个版本相当。

通过C++的类重写前面的例子

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
#include <iostream>

class Point {
public:
double x;
double y;

Point(double x, double y) : x(x), y(y) {}

void show() const {
std::cout << "(x, y) = (" << x << ", " << y << ")" << std::endl;
}

void add(const Point &other) {
x += other.x;
y += other.y;
}
};

int main() {
Point p1(1.0, 2.0);
p1.show();

Point p2(3.0, 4.0);

p1.add(p2);
p1.show();

return 0;
}

从使用者的角度来看,这更像C语言实现的第一个版本

1
2
3
4
5
6
7
8
9
10
11
12
// c version 1
int main() {
Point p1 = make_point(1.0, 2.0);
p1.show(&p1);

Point p2 = make_point(3.0, 4.0);

p1.add(&p1, &p2);
p1.show(&p1);

return 0;
}

只是C++在语法上提供了直接的构造函数,并不需要我们提供专门的make_point函数, 并且C++的普通成员函数调用会自动将this指针传递给方法,并不需要显式传递&p,并且通过引用传递进一步隐藏了指针参数的使用。

但是在编译器实现的角度,这更像C语言实现的第二个版本

1
2
3
4
5
6
7
8
9
10
11
12
// c version 2
int main() {
Point p1 = make_point(1.0, 2.0);
show_point(&p1);

Point p2 = make_point(3.0, 4.0);

add_point(&p1, &p2);
show_point(&p1);

return 0;
}

在编译器看来,类的成员函数和普通的外部函数实质没什么区别,除了成员函数总是会自动将指向当前对象自身的this指针作为真正意义上的第一个参数传递给函数。这里this既不需要出现在参数列表中,也不需要出现在调用语句中。

继承

为了简化问题的讨论,我们只考虑单继承关系。

C语言实现的继承关系其实很简单,将基类作为派生类的一个成员,在后面加上派生类的新成员即可。这里的成员顺序是重要的,我们希望保证在内存的角度上,派生类对象只是在基类对象之后加上了额外的数据。对于多继承和虚继承这里的数据顺序就可能复杂多了,但是我们不做讨论。

在第一个版本的基础上继续

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
#include <stdio.h>

typedef struct Point {
double x;
double y;
void (*show)(const struct Point *const);
void (*add)(struct Point *const, const struct Point *const);
} Point;

void show_point(const Point *const self) {
printf("(x, y) = (%f, %f)\n", self->x, self->y);
}

void add_point(Point *const self, const Point *const obj) {
self->x += obj->x;
self->y += obj->y;
}

Point make_point(double x, double y) {
Point obj;
obj.x = x;
obj.y = y;
obj.show = show_point;
obj.add = add_point;
return obj;
}

//----------------------------------------------------------------------------//

typedef struct WeightedPoint {
Point p;
double w;
void (*show)(const struct WeightedPoint *const);
void (*add)(struct WeightedPoint *const, const struct WeightedPoint *const);
} WeightedPoint;

void show_weighted_point(const WeightedPoint *const self) {
printf("(x, y, w) = (%f, %f, %f)\n", self->p.x, self->p.y, self->w);
}

void add_weighted_point(WeightedPoint *const self, const WeightedPoint *const obj) {
self->p.add(&(self->p), &(obj->p));
// or
// add_point(&(self->p), &(obj->p));

self->w += obj->w;
}

WeightedPoint make_weighted_point(double x, double y, double w) {
WeightedPoint obj;
obj.p = make_point(x, y);
obj.w = w;
obj.show = show_weighted_point;
obj.add = add_weighted_point;
return obj;
}

//----------------------------------------------------------------------------//

int main() {
Point p1 = make_point(1.0, 2.0);
p1.show(&p1);

Point p2 = make_point(3.0, 4.0);

p1.add(&p1, &p2);
p1.show(&p1);

WeightedPoint wp1 = make_weighted_point(1.0, 2.0, 3.0);
wp1.show(&wp1);

WeightedPoint wp2 = make_weighted_point(3.0, 4.0, 5.0);

wp1.add(&wp1, &wp2);
wp1.show(&wp1);

return 0;
}

运行结果如下

1
2
3
4
(x, y) = (1.000000, 2.000000)
(x, y) = (4.000000, 6.000000)
(x, y, w) = (1.000000, 2.000000, 3.000000)
(x, y, w) = (4.000000, 6.000000, 8.000000)

解释一下这里的新内容:

  • 我们定义了WeightedPoint对象,它包含一个Point对象,这相当于继承关系:WeightedPoint继承了Point
  • 我们提供了派生类的构造函数make_weighted_point,在其中调用了基类的构造函数make_point,并且加上了对额外的权重数据的初始化。
  • 我们还重写了p.add(...)p.show(...)方法,对于show()方法直接完全重写,对于add()方法则调用了基类的同名方法,在此基础上添加了权重的相加。

第二个版本的实现也是类似的,移除所有的成员函数指针,直接通过普通函数调用

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
#include <stdio.h>

typedef struct Point {
double x;
double y;
} Point;

void show_point(const Point *const self) {
printf("(x, y) = (%f, %f)\n", self->x, self->y);
}

void add_point(Point *const self, const Point *const obj) {
self->x += obj->x;
self->y += obj->y;
}

Point make_point(double x, double y) {
Point obj;
obj.x = x;
obj.y = y;
return obj;
}

//----------------------------------------------------------------------------//

typedef struct WeightedPoint {
Point p;
double w;
} WeightedPoint;

void show_weighted_point(const WeightedPoint *const self) {
printf("(x, y, w) = (%f, %f, %f)\n", self->p.x, self->p.y, self->w);
}

void add_weighted_point(WeightedPoint *const self, const WeightedPoint *const obj) {
add_point(&(self->p), &(obj->p));
self->w += obj->w;
}

WeightedPoint make_weighted_point(double x, double y, double w) {
WeightedPoint obj;
obj.p = make_point(x, y);
obj.w = w;
return obj;
}

//----------------------------------------------------------------------------//

int main() {
Point p1 = make_point(1.0, 2.0);
show_point(&p1);

Point p2 = make_point(3.0, 4.0);

add_point(&p1, &p2);
show_point(&p1);

WeightedPoint wp1 = make_weighted_point(1.0, 2.0, 3.0);
show_weighted_point(&wp1);

WeightedPoint wp2 = make_weighted_point(3.0, 4.0, 5.0);

add_weighted_point(&wp1, &wp2);
show_weighted_point(&wp1);

return 0;
}

使用C++重写上面的例子

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>

class Point {
public:
double x;
double y;

Point(double x, double y) : x(x), y(y) {}

void show() const {
std::cout << "(x, y) = (" << x << ", " << y << ")" << std::endl;
}

void add(const Point &other) {
x += other.x;
y += other.y;
}
};

class WeightedPoint : public Point {
public:
double weight;

WeightedPoint(double x, double y, double weight)
: Point(x, y), weight(weight) {}

void show() const {
std::cout << "(x, y, w) = (" << x << ", " << y << ", " << weight << ")"
<< std::endl;
}

void add(const WeightedPoint &other) {
Point::add(other);
weight += other.weight;
}
};

int main() {
Point p1(1.0, 2.0);
p1.show();

Point p2(3.0, 4.0);

p1.add(p2);
p1.show();

WeightedPoint wp1(1.0, 2.0, 3.0);
wp1.show();

WeightedPoint wp2(3.0, 4.0, 5.0);

wp1.add(wp2);
wp1.show();

return 0;
}

这与C语言实现的第二个版本在原理上仍然是一致的。

多态

我们主要关注动态多态的C语言模拟和C++实现,C语言两个版本的实现在面对动态多态的需求时,面临的局面是完全不一样的:

  • 第一个版本因为每一个对象都有一组函数指针(函数调用表),我们可以直接修改函数指针实现调用不同的具体方法,这甚至与继承完全无关;
  • 第二个版本因为对象没有存储任何的调用信息,我们必须加入额外信息,这里参考C++的虚函数方案,对每一个类型添加一个虚函数表,对每一个对象添加一个虚表指针。这个版本的实现最接近C++编译器真正采用的方案。

我们反过来从C++的代码开始,示例代码如下

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
#include <iostream>

class Point {
public:
double x;
double y;

Point(double x, double y) : x(x), y(y) {}

virtual void show() const {
std::cout << "show from point\n";
std::cout << "(x, y) = (" << x << ", " << y << ")\n";
}

void hello() const { std::cout << "hello from point\n"; }
};

class WeightedPoint : public Point {
public:
double w;

WeightedPoint(double x, double y, double w) : Point(x, y), w(w) {}

void show() const override {
std::cout << "show from weighted point\n";
std::cout << "(x, y, w) = (" << x << ", " << y << ", " << w << ")\n";
}

void hello() const { std::cout << "hello from weighted point\n"; }
};

void test(const Point *base) {
std::cout << "test:\n";
base->hello();
base->show();
}

int main() {
Point *base = new Point(1.0, 2.0);
test(base);
delete base;

WeightedPoint *derived = new WeightedPoint(1.0, 2.0, 3.0);
test(derived);
delete derived;

return 0;
}

运行结果如下

1
2
3
4
5
6
7
8
test:
hello from point
show from point
(x, y) = (1, 2)
test:
hello from point
show from weighted point
(x, y, w) = (1, 2, 3)

注意这里的hello()show()得到的结果是不一样的,前者是静态绑定,后者是动态绑定(因为show()是虚函数),这里不展开讨论。 下面分别从两个角度对这个例子使用C语言进行模拟。

基于对象的函数调用表

基于第一个方案的实现如下

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
#include <stdio.h>
#include <stdlib.h>

typedef struct Point {
double x;
double y;
void (*hello)(const struct Point *const);
void (*show)(const struct Point *const); // virtual
} Point;

void show_point(const Point *const self) {
printf("show from point\n");
printf("(x, y) = (%f, %f)\n", self->x, self->y);
}

void hello_point(const Point *const self) { printf("hello from point\n"); }

void init_point(Point *const self, double x, double y) {
self->x = x;
self->y = y;

self->hello = hello_point;
self->show = show_point;
}

//----------------------------------------------------------------------------//

typedef struct WeightedPoint {
Point p;
double w;
void (*hello)(const struct WeightedPoint *const);
void (*show)(const struct WeightedPoint *const);
} WeightedPoint;

void show_weighted_point(const WeightedPoint *const self) {
printf("show from weighted point\n");
printf("(x, y, w) = (%f, %f, %f)\n", self->p.x, self->p.y, self->w);
}

void hello_weighted_point(const WeightedPoint *const self) {
printf("hello from weighted point\n");
}

void virtualoverwrite_show(const Point *const self) {
((WeightedPoint *)self)->show((WeightedPoint *const)self);
}

void init_weighted_point(WeightedPoint *const self, double x, double y, double w) {
init_point(&(self->p), x, y);
self->w = w;

self->hello = hello_weighted_point;

self->show = show_weighted_point;
self->p.show = virtualoverwrite_show;
}

//----------------------------------------------------------------------------//

void test(Point *base) {
printf("test:\n");
base->hello(base);
base->show(base);
}

int main() {
Point *base = (Point *)malloc(sizeof(Point));
init_point(base, 1.0, 2.0);
test((Point *)base);
free(base);

WeightedPoint *derived = (WeightedPoint *)malloc(sizeof(WeightedPoint));
init_weighted_point(derived, 1.0, 2.0, 3.0);
test((Point *)derived);
free(derived);

return 0;
}

运行结果与C++的代码一致。

解释一下,这里我们对hello()show()进行了不同的处理:

  • 对于hello()方法,base->hello的调用效果完全取决于当前的basePoint *指针还是Derived *指针。
  • 对于show()方法,我们不仅将WeightedPoint版本的实现绑定到派生类的*show指针,还修改了派生类包含的基类对象中的*show指针,将其指向一个类型转换接口:virtualoverwrite_show,在其中将Point *指针完全转换为WeightedPoint *指针再进行处理,达到基类指针调用派生类方法的目的。

事实上在这种方案下,我们完全不需要继承关系就可以实现运行时多态的效果,例如

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
#include <stdio.h>
#include <stdlib.h>

typedef struct Point {
double x;
double y;
double w;
void (*hello)(const struct Point *const);
void (*show)(const struct Point *const);
} Point;

void show_point(const Point *const self) {
printf("show from point\n");
printf("(x, y) = (%f, %f)\n", self->x, self->y);
}

void hello_point(const Point *const self) { printf("hello from point\n"); }

void init_point(Point *const self, double x, double y) {
self->x = x;
self->y = y;
self->w = 0;

self->hello = hello_point;
self->show = show_point;
}

void show_weighted_point(const Point *const self) {
printf("show from weighted point\n");
printf("(x, y, w) = (%f, %f, %f)\n", self->x, self->y, self->w);
}

void hello_weighted_point(const Point *const self) {
printf("hello from weighted point\n");
}

void init_weighted_point(Point *const self, double x, double y, double w) {
self->x = x;
self->y = y;
self->w = w;

self->hello = hello_point;
self->show = show_weighted_point;
}

//----------------------------------------------------------------------------//

void test(Point *base) {
printf("test:\n");
base->hello(base);
base->show(base);
}

int main() {
Point *tmp1 = (Point *)malloc(sizeof(Point));
init_point(tmp1, 1.0, 2.0);
test(tmp1);
free(tmp1);

Point *tmp2 = (Point *)malloc(sizeof(Point));
init_weighted_point(tmp2, 1.0, 2.0, 3.0);
test(tmp2);
free(tmp2);

return 0;
}

这里对hello()show()的处理其实是一致的,在初始化函数中自由地调整函数指针即可

1
2
3
4
5
6
7
8
self->hello = hello_point;
self->show = show_weighted_point;

// or
self->hello = hello_weighted_point;
self->show = show_weighted_point;

// ...

基于类型的虚函数表

基于第二个方案,虽然我们并没有将普通函数的调用通过结构体内部的函数指针实现,但是在涉及到虚函数动态调用时, 还是不得不为每一个对象加上一个虚表指针vtable,以指向当前类型正确的调用函数,还要加上额外的类型标识符TypeInfo,如果没有类型标识,show_dynamic就不知道应当强制转换为哪一个类型以适配函数调用。

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
#include <stdio.h>
#include <stdlib.h>

typedef enum { TYPE_POINT, TYPE_WEIGHTED_POINT } TypeInfo; // 类型标识符

typedef struct PointMeta {
double x;
double y;
} PointMeta;

typedef struct Point {
TypeInfo type;
void *const *vtable; // 虚表指针
PointMeta data;
} Point;

void hello_point(const Point *const self) { printf("hello from point\n"); }

void show_point(const Point *const self) {
printf("show from point\n");
printf("(x, y) = (%f, %f)\n", self->data.x, self->data.y);
}

void init_pointmeta(PointMeta *const self, double x, double y) {
self->x = x;
self->y = y;
}

void *const point_vtable[] = {(void *)&show_point}; // 虚函数表

void init_point(Point *const self, double x, double y) {
init_pointmeta(&(self->data), x, y);
self->type = TYPE_POINT;
self->vtable = point_vtable;
}

//----------------------------------------------------------------------------//

typedef struct WeightedPointMeta {
PointMeta p;
double w;
} WeightedPointMeta;

typedef struct WeightedPoint {
TypeInfo type;
void *const *vtable; // 虚表指针
WeightedPointMeta data;
} WeightedPoint;

void hello_weighted_point(const WeightedPoint *const self) {
printf("hello from weighted point\n");
}

void show_weighted_point(const WeightedPoint *const self) {
printf("show from weighted point\n");
printf("(x, y, w) = (%f, %f, %f)\n", self->data.p.x, self->data.p.y,
self->data.w);
}

void init_weighted_pointmeta(WeightedPointMeta *const self, double x, double y,
double w) {
init_pointmeta(&(self->p), x, y);
self->w = w;
}

void *const weighted_point_vtable[] = {
(void *)&show_weighted_point}; // 虚函数表

void init_weighted_point(WeightedPoint *const self, double x, double y,
double w) {
init_weighted_pointmeta(&(self->data), x, y, w);
self->type = TYPE_WEIGHTED_POINT;
self->vtable = weighted_point_vtable;
}

//----------------------------------------------------------------------------//

void show_dynamic(const Point *const self) {
if (self->type == TYPE_POINT) {
((void (*)(const Point *const))self->vtable[0])((const Point *const)self);
}
else { // self->type == TYPE_WEIGHTED_POINT
((void (*)(const WeightedPoint *const))self->vtable[0])(
(const WeightedPoint *const)self);
}
}

void test(Point *base) {
printf("test:\n");
hello_point(base);
show_dynamic(base);
}

int main() {
Point *base = (Point *)malloc(sizeof(Point));
init_point(base, 1.0, 2.0);
test((Point *)base);
free(base);

WeightedPoint *derived = (WeightedPoint *)malloc(sizeof(WeightedPoint));
init_weighted_point(derived, 1.0, 2.0, 3.0);
test((Point *)derived);
free(derived);

return 0;
}

解释一下:

  • 首先,我们引入了新的结构体PointMetaWeightedPointMeta,它们只含有数据成员,并且在继承关系中,后者包含前者作为普通成员。
  • 在这两个结构体之外,加上类型识别和虚表指针才构成了完整的PointWeightedPoint类。在C++编译器的具体实现中,类型识别信息也被塞进了类型的虚表之中,即在数据成员之外只多出一个虚表指针,这里为了简化将它们拆开了。
  • hello()方法的调用必然是静态的:我们只提供了hello_point()hello_weighted_point两个明确的接口
  • show()方法支持静态调用:我们提供了show_pointshow_weighted_point两个明确的接口,也支持动态调用,我们提供了show_dynamic这个接口,下面重点分析它的实现。

show_dynamic这个接口虽然接收的是基类指针,但是它会根据类型识别信息,获取虚函数表中的函数指针并进行相应的类型转换,然后再调用。 具体调用的既可能是show_point,也可能是show_weighted_point

1
2
3
4
5
6
7
8
9
void show_dynamic(const Point *const self) {
if (self->type == TYPE_POINT) {
((void (*)(const Point *const))self->vtable[0])((const Point *const)self);
}
else { // self->type == TYPE_WEIGHTED_POINT
((void (*)(const WeightedPoint *const))self->vtable[0])(
(const WeightedPoint *const)self);
}
}

运行结果与C++的代码一致。

注:为了尽可能和C++的实现保持一致,上面所有的C语言代码中的指针类型在允许的情况下都加上了很繁琐的const修饰,有时加了两层const,看着非常繁琐。