通常使用函数指针来完成此操作。换句话说,保存数据的简单结构and指向操作该数据的函数的指针。在 Bjarne S 出现之前几年我们就开始做这类事情了。
因此,例如,在通信类中,您将有一个打开、读取、写入和关闭调用,这些调用将在结构中作为四个函数指针进行维护,以及对象的数据,例如:
typedef struct {
int (*open)(void *self, char *fspec);
int (*close)(void *self);
int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
// And the data for the object goes here.
} tCommsClass;
tCommsClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;
tCommsClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;
这些函数指针的初始化实际上是在“构造函数”中,例如rs232Init(tCommClass*)
,它将负责设置该特定对象的默认状态以匹配特定的类。
当您从该类“继承”时,只需更改指针以指向您自己的函数即可。每个调用这些函数的人都会通过函数指针来完成它,从而为您提供多态性:
int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");
有点像手动配置vtable,用 C++ 的说法。
您甚至可以通过将指针设置为 NULL 来拥有虚拟类 - 该行为与 C++ 略有不同,因为您可能会在运行时获得核心转储,而不是在编译时获得错误。
这是演示它的一段示例代码:
#include <stdio.h>
// The top-level class.
typedef struct _tCommClass {
int (*open)(struct _tCommClass *self, char *fspec);
} tCommClass;
// Function for the TCP class.
static int tcpOpen (tCommClass *tcp, char *fspec) {
printf ("Opening TCP: %s\n", fspec);
return 0;
}
static int tcpInit (tCommClass *tcp) {
tcp->open = &tcpOpen;
return 0;
}
// Function for the HTML class.
static int htmlOpen (tCommClass *html, char *fspec) {
printf ("Opening HTML: %s\n", fspec);
return 0;
}
static int htmlInit (tCommClass *html) {
html->open = &htmlOpen;
return 0;
}
// Test program.
int main (void) {
int status;
tCommClass commTcp, commHtml;
// Same base class but initialized to different sub-classes.
tcpInit (&commTcp);
htmlInit (&commHtml);
// Called in exactly the same manner.
status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
status = (commHtml.open)(&commHtml, "http://www.microsoft.com");
return 0;
}
这会产生输出:
Opening TCP: bigiron.box.com:5000
Opening HTML: http://www.microsoft.com
所以你可以看到不同的函数被调用,具体取决于子类。