如何在C中创建一个接口,该接口可以在具有不同名称字段的两个相同结构上工作



问题背景

考虑一个场景,其中我有两个结构。两者都包含三个字段用于双打唯一的区别是用于引用这些字段的名称。第一个结构用于非描述元组,组件将命名为x、y和z。第二个结构用于RGB值,组件将名称命名为红色、绿色和蓝色。这两种结构都需要为它们定义一个接口,以便可以执行矢量/分量加法和标量乘法等操作。

问题

问题是什么?复制并粘贴这两种结构的相同代码,只是为了更改命名方案(x与红色、y与绿色、z与蓝色),这似乎非常不雅理想情况下,可以在接口函数定义中引用结构的字段(x或red),而不必命名字段。这可能吗?如果没有,那么在不改变要求的情况下,我还有什么其他选择我包含了下面的结构定义,以及它们与我在这个问题中描述的可能不可能的接口的期望使用的一些示例。

不可描述的元组结构

typedef struct
{
/** The first coordinate */
double x;
/** The second coordinate */
double y;
/** The third coordinate */
double z;
} Tuple;

RGB元组结构

typedef struct
{
/** The first coordinate */
double red;
/** The second coordinate */
double green;
/** The third coordinate */
double blue;
} Tuple;

示例用法

Tuple * genericTuple = createVector( 1, 2, 3 ); // create a generic Tuple
printf("%lf", genericTuple->x); // should print 1
Tuple * rgbTuple = createColor( 1, 2, 3 ); // create an rgbTuple
printf("%lf", rgbTuple->red); // should also print 1

您的类型存在冲突

如果您试图在同一个函数中使用两种不同类型的Tuple,则不允许这样做。它将无法编译,因为第二个typedef将与第一个冲突。

为了便于论证,假设您愿意为这两种不同的类型赋予不同的名称,但您希望某些代码以某种方式将它们视为同一类型。允许这种情况发生的一种设计模式是Adapter模式。

适配器回答您的问题

尽管您以创建接口的形式提出了问题,但您所展示的是两个不同的接口,并且您希望两个接口即使在传递冲突类型时也能工作。这不是一个好的软件工程实践。

相反,你应该做些什么来避免";复制粘贴";编码是为了弄清楚如何创建或重用将与您的特定用例一起使用的通用代码。这可以通过重新考虑特定案例中的共性,也可以通过专门化一个更通用的用例接口。两者都可以通过应用适配器来实现。

我提供了以下两个应用方向的说明。但是,请记住,它们只是示例,并且有多种方法可以应用该模式。

根据特定用例改编Tuple

假设您有一个特定的类型来表示RGB值。

struct RGB {
double red;
double green;
double blue;
};

可以创建一个Tuple接口,以允许以相同的方式处理任意用例。例如,我们可以使用偏移。

struct Tuple {
const size_t (*adaptor)[3];
void *tuple;
};
struct Tuple make_tuple (void *tuple, const size_t (*adaptor)[3]) {
return (struct Tuple){adaptor, tuple};
}
void print_tuple (struct Tuple t) {
for (int i = 0; i < 3; ++i) {
printf("%lfn", *(double *)((char *)t.tuple + (*t.adaptor)[i]));
}
}

然后你可以这样使用它:

const size_t RGB_to_Tuple_Adaptor[3] = {
offsetof(struct RGB, red),
offsetof(struct RGB, green),
offsetof(struct RGB, blue)
};
void print_RGB (struct RGB *rgb) {
print_tuple(make_tuple(rgb, &RGB_to_Tuple_Adaptor));
}

Tuple中改编特定用例

相反,假设您有一个像这样定义的Tuple

struct Tuple {
double tuple[3];
};

然后,你会有一个打印功能,看起来像这样:

void print_tuple (struct Tuple *t) {
for (int i = 0; i < 3; ++i) {
printf("%lfn", t->tuple[i]);
}
}

要在您的用例中使用Tuple,您可以这样定义RGB

struct RGB {
struct Tuple tuple;
};
#define RGB_red(rgb)   RGB_to_Tuple(rgb)->tuple[0]
#define RGB_green(rgb) RGB_to_Tuple(rgb)->tuple[1]
#define RGB_blue(rgb)  RGB_to_Tuple(rgb)->tuple[2]
#define RGB_to_Tuple(rgb) (&(_Generic(&(rgb), 
struct RGB * : (&(rgb)),           
struct RGB **: (rgb),              
default: 0)->tuple))

然后你可以这样使用它:

void print_RGB (struct RGB *rgb) {
print_tuple(RGB_to_Tuple(rgb));
}
int main () {
struct RGB rgb;
RGB_red(rgb) = 1;
RGB_green(rgb) = 2;
RGB_blue(rgb) = 3;
print_RGB(&rgb);
}

这里的标准答案是使用联合。

一种可能性是结构的联合:

struct named_tuple
{
/** The first coordinate */
double red;
/** The second coordinate */
double green;
/** The third coordinate */
double blue;
};
struct coord_tuple
{
/** The first coordinate */
double x;
/** The second coordinate */
double y;
/** The third coordinate */
double z;
};
typedef union {
struct named_tuple named;
struct coord_tuple coord;
} Tuple;
// access:
tuple.named.red = 5;
tuple.coord.x = 42;

最新更新