您的问题有两个部分:需要一个带有可变数量参数且方便的打印函数;并且想要一种打印任意用户定义数据类型的好方法。
对于第一部分,您可以编写一个“可变参数”函数,与printf()
. 但是你有一个问题:没有办法计算有多少参数,而且你不知道参数的类型。您可以使函数更简单,而不是printf()
说每个参数都必须是指向字符串的指针,并且最后一个参数必须始终是NULL
指针;然后你可以编写一个简单的循环,直到它到达NULL
. 谷歌搜索c varargs
以了解更多信息。
如果您这样做,您可以创建一个宏,该宏始终扩展为以 aNULL
作为最后一个参数调用您的函数,以减少用户忘记将NULL
. 像这样的东西:
#define CONVENIENT_PRINT(...) \
my_convenient_print_function(__VA_ARGS__, NULL)
当然,有趣的部分是一些编译器在上述情况下工作得很好,但有些编译器不能。编写具有可变数量参数的宏不像 C 的某些其他部分那样可移植。但上述形式在 Microsoft C 和 GCC 和 Clang 中有效,因此您可能对它没问题。
对于第二部分,C 并没有给你太多帮助。我可以为这个问题提出两种可能的解决方案。一是要有严格的命名约定,方便用户判断调用什么函数;对于任何数据类型FOO
,总会有一个函数StringFromFoo()
,用户必须学会调用正确的函数。第二个是实现自己的面向对象类型,并确保每个对象都有一个指向方法函数ToString()
或其他调用的指针。像这样的东西:
typedef struct
{
char const *(*ToString)(void const *this);
} FOO_CLASS; // class struct
static FOO_CLASS _foo_class;
char const *FooToString(void const *this)
{
return NULL; // yeah I'm not really implementing this here
}
_foo_class.ToString = FooToString;
typedef struct
{
// member variables go here
int x;
float f;
// need a reference back to the class; just one
FOO_CLASS *pclass;
} FOO; // instance struct
int InitFoo(int x, float f, FOO **ppfooNew)
{
FOO *pfoo = malloc(sizeof(FOO));
if (!pfoo)
{
*ppfooNew = NULL;
return ENOMEM;
}
pfoo->x = x;
pfoo->f = f;
pfoo->pclass = &_foo_class;
*ppfooNew = pfoo;
return 0; // success!
}
// in your code:
FOO *pfoo;
char const *str;
if (InitFoo(x, f, &pfoo))
goto crash_and_burn;
str = pfoo->pclass->ToString(pfoo);
哇,这比面向对象的语言更烦人!那是面向对象的代码,但 C 对你一点帮助都没有;你必须自己做所有的工作。
注意我们如何使用pfoo
查找类,然后在类中找到方法函数,然后我们仍然必须pfoo
作为方法函数的参数(this
指针)传入。C++ 为您完成了这一切。
至少您可以为调用方法函数的常见工作制作一个宏:
#define STR(instance) \
(instance)->pclass->ToString(instance)
str = STR(pfoo);
对于这个简单的示例,我们在类中只有一个函数。对于一个真实的例子,你可能会有更多的功能!对于单个函数,您可以将其放入实例中,它的工作量会减少,但这不会扩展,如果您完全这样做,您应该始终如一地这样做。
所以,我认为大多数人会采用命名约定解决方案,并称其为足够好。我以部分面向对象的风格编写了一个大型项目,我认为它有助于使代码更易于管理。即使在面向对象的风格中,我仍然有很多命名约定。
PS你可能已经注意到上面的代码实际上并没有实现ToString()
,实现它实际上会有点烦人。字符串会去哪里?如果您的需求非常简单,您可以在ToString()
函数内部使用静态缓冲区,但这不是一个好的解决方案……否则您需要更改ToString()
以传入缓冲区,或者调用它malloc()
以获取新的字符串缓冲区;然后处理可能的失败malloc()
;然后在处理完字符串后以某种方式调用free()
(或者只是可耻地泄漏内存!)。这是 C++ 将为您做的更多事情……您可以创建 a std::string
,将其传递,当它超出范围时,将调用析构函数。