将函数变成数据
最后更新于:2022-04-01 06:25:51
## 将函数变成数据
再来看一下经过大幅简化的 `create_chain_node` 函数:
~~~
struct chain_node *
create_chain_node(void)
{
struct pair *left_hole = pair(pair_for_double_type(1.0, 1.0), malloc_double(0.5));
struct pair *right_hole = pair(pair_for_double_type(9.0, 1.0), malloc_double(0.5));
struct pair *holes = pair(left_hole, right_hole);
struct pair *body = pair_for_double_type(10.0, 1.0);
struct pair *shape = pair(body, holes);
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = shape;
return ret;
}
~~~
这个函数对于我们的示例而言,没有什么问题,但是它只能产生特定形状的链节,这显然不够通用。如果我们想更换一下链节的形状,例如将原来的带两个小孔的矩形铁片换成带两个小孔的椭圆形铁片,那么我们将不得不重写一个`create_elliptic_chain_node` 函数。当我们这样做的时候,很容易发现 `create_elliptic_chain_node` 函数中同样需要下面这段代码:
~~~
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = shape;
return ret;
~~~
如果我们要生产 100 种形状的链节,那么上述代码在不同的链节构造函数的实现中要重复出现 100 次,这样肯定不够好,因为会出现 500 行重复的代码。太多的重复的代码,这是对程序猿的最大的羞辱。
面向对象的程序猿可能会想到,我们可以为 `chain_node` 做一个基类,然后将上述共同的代码封装到基类的构造函数,然后在各个 `chain_node` 各个派生类的构造函数中制造不同形状的链节……在你要将事情搞复杂之前,建议先看一下这样的代码:
~~~
void *
rectangle_shape(void)
{
struct pair *left_hole = pair(pair_for_double_type(1.0, 1.0), malloc_double(0.5));
struct pair *right_hole = pair(pair_for_double_type(9.0, 1.0), malloc_double(0.5));
struct pair *holes = pair(left_hole, right_hole);
struct pair *body = pair_for_double_type(10.0, 1.0);
return pair(body, holes);
}
struct chain_node *
create_chain_node(void *(*fp)(void))
{
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = fp();
return ret;
}
~~~
看到了吧,我将 `create_chain_node` 函数原定义中负责创建链节形状的代码全部的抽离了出去,将它们封装到`rectangle_shape` 函数中,然后再让 `create_chain_node` 函数接受一个函数指针形式的参数。这样,当我们需要创建带两个小孔的矩形形状的链节时,只需:
~~~
struct chain_node *rect_chain_node = create_chain_node(rectangle_shape);
~~~
如果我们像创建带两个小孔的椭圆形状的链节,可以先定义一个 `elliptic_shape` 函数,然后将其作为参数传给`create_chain_node`,即:
~~~
struct chain_node *elliptic_chain_node = create_chain_node(elliptic_shape);
~~~
这样做,岂不是要比弄出一大堆类与继承的代码更简洁有效吗?
在 C 语言中,函数名也是一种指针,它引用了函数代码所在内存空间的基地址。所以,我们可以将 `rectangle_shape` 这样函数作为参数传递给 `create_chain_node` 函数,然后在后者中调用前者。
由于我们已经将 `chain_node` 结构体中的 `shape` 指针定义为 `void *` 指针了,因此对于 `create_chain_node` 函数所接受的函数,其返回值是 `void *` 没什么问题。不仅没问题,更重要的是 `void *(*fp)(void)` 对所有不接受参数且返回指针类型数据的函数的一种抽象。这意味着对于链节的形状,无论它的形状有多么特殊,我们总是能够定义一个不接受参数且返回指针的函数来产生这种形状,于是 `create_chain_node` 函数就因此具备了无限的扩展能力。
如果阿基米的德还活着,也许他会豪放的说,给我一个函数指针与一个 `void *`,我就能描述宇宙!