为结构体建立方法

我们可以为结构体建立数据操作的函数集,这些函数可称为方法。考虑矩形结构体:

struct RECT
{
    int left, top;
    int right, bottom;
};

假设我们的绘图程序在很多地方都需要知道矩形的宽度与高度,两行代码就能解决。

struct RECT r = {0, 0, 300, 200}
int w = r.right - r.left;
int h = r.bottom - r.top;

更进一步,为数据建立操作函数集是一种更优的选择(函数可复用、可减少重复代码,维护单一),比如为RECT建立width/height方法。

int width(struct RECT *r)
{
    return r->right - r->left;
}

int height(struct RECT *r)
{
    return r->bottom - r->top;
}

这些方法仅仅是一个结构体内部字段的操作,我们甚至可以建立结构体对象之间的操作方法,比如合并两个矩形:

struct RECT combine(struct RECT *r1, struct RECT *r2)
{
    struct RECT r;
    r.left   = min(r1->left, r2->left);
    r.top    = min(r1->top, r2->top);
    r.right  = max(r1->right, r2->right);
    r.bottom = max(r1->bottom, r2->bottom);
    return r;
}

显然,根据需要我们可以建立更多的与RECT相关方法。这里重点要理解的是:我们把结构体数据与其操作方法关联了起来,而不像之前一样,仅仅是把结构体当作数据的集合。现在,结构加方法,即数据以及建立在数据上的一组操作,构成了对结构类型的完整抽象。

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

//
// 上面列出的代码
//

void print(const char *name, struct RECT *r)
{
    printf("%s (%d,%d,%d,%d) [%dx%d]\n",
           name,
           r->left, r->top, r->right, r->bottom,
           width(r), height(r));
}

int main()
{
    struct RECT r1 = { 0, 0, 100, 100 };
    struct RECT r2 = { 20, 40, 200, 400 };
    struct RECT r3 = combine(&r1, &r2);

    print("r3", &r3);
    return 0;
}

glimix.com

ZYF

单一定义规则(ODR)

函数名width太具有普适性了。假定我们在同一个.c文件中,还需要为结构Foo关联一个width()方法,如:

struct Foo
{
    int v;
};

int width(struct Foo *f)
{
    return f->v;
}

再次编译程序便会出现如下错误:

glimix.com

这是因为我们在同一个编译单元(.c)中存在两个相同的名称width,尽管它们需要的参数类型有天壤之别。

int width(struct RECT *r) { ... }
int width(struct Foo *f) { ... }

这违背了C语言的ODR规则,即:在每个翻译单元中一个名称只允许有一个定义。这个规则适用于任何函数、用户自定义类型等。

// 假设下面这些定义都位于某个.c文件中

int a;
float a;                            // 可行,编译器警告warning C4142: "a": 类型的良性重定义
                                    // 即对 int a; 覆盖定义了,所以不建议这么做。

int size();                         // 正确
float size(float x);                // 这里是函数声明,而不是定义。

int size() { return 0; }
float size(float) { return 0.0f; }  // error C2084: 函数“int size()”已有主体
                                    // 违背ODR规则

struct Foo { int v; };              // 定义类型Foo两次
struct Foo { int v; };              // error C2011: “Foo”:“struct”类型重定义
                                    // 违背ODR规则

为了避免这种错误,我们对于所需要的方法,可以加上结构体的名称修饰,如:

int rect_width(struct RECT *r) { ... }
int foo_width(struct Foo *f) { ... }

解决了名称问题之后,我们就可以依赖完整的结构体抽象(数据+方法)构造工程化的应用程序了。

ZYF

陕ICP备2025078817号-1 陕公网安备61011202001108号