我想要逐步解释如何解析可变参数函数的参数
这样,当调用 va_arg(ap, TYPE);
时,我会传递所传递参数的正确数据类型。
目前我正在尝试编写 printf
代码。
我只是寻找解释,最好带有简单的示例,但不是 printf
的解决方案,因为我想解决我自己。
以下是三个看起来像我正在寻找的示例:
- https://stackoverflow.com/a/1689228/3206885
- https://stackoverflow.com/a/5551632/3206885
- https://stackoverflow.com/a/1722238/3206885
我了解 typedef
、struct
、enum
和 union
的基础知识,但无法理解出一些实际的应用案例,例如链接中的示例。
它们的真正含义是什么?我无法理解它们是如何工作的。
如何将数据类型从联合传递到 va_arg
(如链接示例中所示)?怎样搭配呢?
使用 %d
、%i
等修饰符...或参数的数据类型?
这是我到目前为止所得到的:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "my.h"
typedef struct s_flist
{
char c;
(*f)();
} t_flist;
int my_printf(char *format, ...)
{
va_list ap;
int i;
int j;
int result;
int arg_count;
char *cur_arg = format;
char *types;
t_flist flist[] =
{
{ 's', &my_putstr },
{ 'i', &my_put_nbr },
{ 'd', &my_put_nbr }
};
i = 0;
result = 0;
types = (char*)malloc( sizeof(*format) * (my_strlen(format) / 2 + 1) );
fparser(types, format);
arg_count = my_strlen(types);
while (format[i])
{
if (format[i] == '%' && format[i + 1])
{
i++;
if (format[i] == '%')
result += my_putchar(format[i]);
else
{
j = 0;
va_start(ap, format);
while (flist[j].c)
{
if (format[i] == flist[j].c)
result += flist[i].f(va_arg(ap, flist[i].DATA_TYPE??));
j++;
}
}
}
result += my_putchar(format[i]);
i++;
}
va_end(ap);
return (result);
}
char *fparser(char *types, char *str)
{
int i;
int j;
i = 0;
j = 0;
while (str[i])
{
if (str[i] == '%' && str[i + 1] &&
str[i + 1] != '%' && str[i + 1] != ' ')
{
i++;
types[j] = str[i];
j++;
}
i++;
}
types[j] = '\0';
return (types);
}
最佳答案
您无法从 va_list
获取实际类型信息。您可以从 format
中获取所需内容。您似乎没有想到的是:没有一个参数知道实际类型是什么,但是format
代表了调用者对类型应该<的想法/em> 是。 (也许进一步提示:如果调用者提供的格式说明符与传入的可变参数不匹配,实际 printf 会做什么?它会注意到吗?)
您的代码必须解析“%”格式说明符的格式字符串,并使用这些说明符分支到读取具有特定硬编码类型的 va_list
。例如, (伪代码) if (fspec was "%s") { char* str = va_arg(ap, char*);打印出str; }
。没有提供更多细节,因为您明确表示您不需要完整的解决方案。
您永远不会将类型作为运行时数据,并将其作为值传递给va_arg
。 va_arg 的第二个参数必须是在编译时引用已知类型的文字硬编码规范。 (请注意,va_arg
是一个在编译时展开的宏,而不是在运行时执行的函数 - 您不能让函数将类型作为参数。)
您的一些链接建议通过枚举跟踪类型,但这只是为了您自己的代码能够根据该信息进行分支;它仍然不是可以传递给 va_arg 的东西。您必须有单独的代码段,按字面意思表示 va_arg(ap, int)
和 va_arg(ap, char*)
,因此无法避免 switch
或一系列 if
。
您想要使用联合和结构制作的解决方案将从如下开始:
typedef union {
int i;
char *s;
} PRINTABLE_THING;
int print_integer(PRINTABLE_THING pt) {
// format and print pt.i
}
int print_string(PRINTABLE_THING pt) {
// format and print pt.s
}
通过采用显式的 int
或 char*
参数,这两个专用函数可以单独正常工作;我们进行联合的原因是为了使函数能够正式采用相同类型的参数,以便它们具有相同的签名,以便我们可以定义一个单一类型,这意味着指向该种函数的指针:
typedef int (*print_printable_thing)(PRINTABLE_THING);
现在,您的代码可以具有 print_printable_thing
类型的函数指针数组,或将 print_printable_thing
作为结构体字段之一的结构体数组:
typedef struct {
char format_char;
print_printable_thing printing_function;
} FORMAT_CHAR_AND_PRINTING_FUNCTION_PAIRING;
FORMAT_CHAR_AND_PRINTING_FUNCTION_PAIRING formatters[] = {
{ 'd', print_integer },
{ 's', print_string }
};
int formatter_count = sizeof(formatters) / sizeof(FORMAT_CHAR_AND_PRINTING_FUNCTION_PAIRING);
(是的,这些名称都是故意 super 冗长的。您可能希望在实际程序中使用较短的名称,甚至在适当的情况下使用匿名类型。)
现在您可以使用该数组在运行时选择正确的格式化程序:
for (int i = 0; i < formatter_count; i++)
if (current_format_char == formatters[i].format_char)
result += formatters[i].printing_function(current_printable_thing);
但是将正确的内容放入 current_printable_thing
的过程仍然需要分支以获取具有正确硬编码类型的 va_arg(ap, ...)
。编写完成后,您可能会发现自己实际上并不需要联合或结构数组。
关于printf - 从 C 中的可变参数函数的参数确定类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27475510/