如何在C99中重现类方法的行为?

时间:2019-02-05 01:42:50

标签: c pointers struct

我有一个简单的代码来计算多边形的周长

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

struct point { float x; float y; };

struct polygon
{
    int sides;
    struct point vertex[20];                // No more than 20 sided polygon
    float (*perimeter)(struct polygon * p);
};

struct polygon * polygon_init(int sides)
{
    struct polygon *poly;
    poly = malloc(sizeof *poly);
    poly->sides = sides;
    return poly;
}

void polygon_destroy(struct polygon * poly)
{
    free(poly);
}

float distance(struct point p1, struct point p2)
{
    return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

// Assuming points are ordered ;-)
float perimeter(struct polygon * poly)
{
    float p = 0;
    for (int i = 0; i < poly->sides - 1; i++)
        p += distance(poly->vertex[i], poly->vertex[i+1]);
    return p + distance(poly->vertex[poly->sides - 1], poly->vertex[0]);
}

int main(void)
{
    struct polygon *p1 = polygon_init(3);
    struct point t[3] = { {0, 0}, {.5, 1}, {1, 0}};
    memcpy(p1->vertex, t, sizeof(t));
    p1->perimeter = &perimeter;
    printf("perimeter = %.2f\n", p1->perimeter(p1));
    polygon_destroy(p1);
    return 0;
}

您会注意到,我使用函数指针来分配“自定义”函数来计算周长(p1->perimeter = &perimeter;)。这样,我可以根据分配给p1的结构来“抽象”该呼叫。

但是值得注意的是,我将对结构本身的引用作为参数传递给perimeter()的{​​{1}}函数,这看起来很多余。

我想知道是否有一种方法可以调用p1->perimeter(p1),而不是在p1->perimeter()函数上方和内部进行调用(知道我正在引用perimeter()),表示将计算p1本身的周长。

一言以蔽之:我正在尝试伪造类方法的行为。

3 个答案:

答案 0 :(得分:4)

惯用的解决方案不是将函数指针公开给最终用户,而是使用常规函数调用在内部进行指针查找。

float polygon_perimeter(struct polygon *p)
{
    return p->perimeter(p);
}

这很不错,因为它与polygon_init()polygon_destroy()平行:所有函数都在“类”之外,而不是仅仅因为它是多态的而通过成员指针调用了该特定函数。

这还避免了struct polygon内部泄漏给用户。如果您需要通过不透明指针和函数调用库来完成对“类”的所有访问,则有很多好处。没有成员可以直接访问,因此struct的布局可以自由更改,而不会冒API损坏的风险。

如果您走那条路线,那么只需在polygon.h中提供一个空的前向声明,就可以完全隐藏其内部:

// polygon.h
struct polygon;

答案 1 :(得分:4)

  

我想知道是否可以致电p1->perimeter(),   而是在perimeter()函数上方和内部进行调用   自动地(知道我指的是p1),说会   计算p1本身的周长。

C除了通过函数自变量之外,没有提供令人满意的方法来传递perimeter()进行操作的对象。您可以通过多种机制将指向struct polygon的指针存储在函数知道要查找的地方,但是这些机制都很杂乱,并且/或者不是线程安全的,并且它们都需要某种类型的机制。功能选择或功能调用之外的操作或其他代码。

->运算符根本不会在其结果中传递任何有关其(左)操作数的信息,即使这样做,C函数调用表达式也仅通过函数参数将信息传递给被调用函数。

@JohnKugelman的答案已经提出了解决此类问题的惯用C方法:将对象作为参数传递。通常,这样做是代替通过操作对象的成员来选择函数,从而避免了您在问题中指出的冗余。

朝相反的方向前进,可以在C11中玩一些有趣的游戏,以后可以使用generic selection根据参数的类型选择要调用的函数。通常将其包装在通用宏中。也许那会降低将功能记录为应该对其进行操作的对象的成员对您的重要性。例如:

#define perimeter(x) _Generic((x),          \
    struct circle *:   circle_perimeter,    \
    struct polygon *:  poly_perimeter,      \
    struct path *:     path_perimeter)(x)

然后,当您调用perimeter(a_shape_pointer)时,会调用与参数类型相对应的函数。

答案 2 :(得分:0)

在polygon_init中初始化它:

poly->perimeter = perimeter;

如果对象接口变大,则可能希望实现适当的虚拟表(VT),这只是一个类的函数指针的结构。然后,每个对象都存储指向VT的指针,而不是单独存储所有功能,并且对于任何给定的类,只需要一个常量VT。这样,对象的大小就不会随着存在的虚拟方法的数量而变化。