为结构体建立方法
我们可以为结构体建立数据操作的函数集,这些函数可称为方法。考虑矩形结构体:
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;
}

单一定义规则(ODR)
函数名width太具有普适性了。假定我们在同一个.c文件中,还需要为结构Foo关联一个width()方法,如:
struct Foo
{
int v;
};
int width(struct Foo *f)
{
return f->v;
}
再次编译程序便会出现如下错误:

这是因为我们在同一个编译单元(.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) { ... }
解决了名称问题之后,我们就可以依赖完整的结构体抽象(数据+方法)构造工程化的应用程序了。
陕公网安备61011202001108号