我想用这个原型制作一个函数:
char *combine(const char *string, ...)
它结合了命令行给出的参数,并将结果返回给main
。
我的功能目前看起来像这样:
char *combine(const char *mj, ...)
{
va_list pl;
va_start(pl, mj);
while(mj != NULL) {
printf("%s",mj);
mj = va_arg(pl, char *);
}
va_end(pl);
return pl;
}
例如,如果用户使用以下命令运行此程序:
./a.out one two three four five six seven
输出应该是:
Parameter: onetwothreefourfivesixseven (length of parameter: 27)
现在,如果我用它运行我的程序,输出主要是随机元素,参数只是空的。
什么是错的,我需要修复什么,所以它显示正确的事情?
该程序需要使用以下main
代码:
int main(int argc, char *argv[]) {
int i = 0;
char *mj = malloc(1);
mj[0] = '\0';
for(i = 1; (i+3) <= argc; i += 3)
{
char *tmp = mj;
mj = combine(tmp, argv[i], argv[i+1], argv[i+2], NULL);
free(tmp);
}
if((i+2) == argc)
{
char *tmp = mj;
mj = combine(mj, argv[i], argv[i+1], NULL);
free(tmp);
}
else if((i+1) == argc)
{
char *tmp = mj;
mj = combine(mj, argv[i], NULL);
free(tmp);
}
printf("Parameter: %s (length: %lu)\n", mj, (unsigned long)strlen(mj));
free(mj);
return 0;
}
把va_list
想象成一种不透明的类型,就像“从不介意它是什么”一样。 “函数”va_start()
,va_arg()
和va_end()
实际上不是函数,而是宏。同样,它们实际上是什么以及它们实际上如何工作并不是那么重要。 (“不要在窗帘后面注意那个男人!”)你现在需要知道的是如何使用它们。
假设您正在编写自己的函数:
char *combine(const char *mj, ...)
有一个论点const char *mj
,然后......可能还有其他一些。那么如果没有标识符,你如何引用这些参数,如果你甚至不知道它们有多少?这就是va_list
的用途。可以把它想象成一个可以滚动浏览这个参数列表的游标,并且可以以某种方式告诉你下一个参数是什么。
首先,你必须使用va_start
进行设置:
va_start(pl, mj);
这意味着使用va_list pl
作为最后已知的显式参数来设置mj
。
第一次使用va_arg()
和pl
时,它会在mj
之后给你第一个参数。你还需要在va_arg()
中指定你期望下一个参数的预期类型,所以在你的情况下,调用将是va_arg(pl, char *)
。每次你使用va_arg()
时,它都会推进这个“光标”,这样下一次调用va_arg()
会给你下一个参数。因此,每次使用va_arg()
时,您都应该使用此值进行操作,因为您不会再次使用它。 (除非,也许你重新开始。)
那么你怎么知道你是否在列表的最后?好吧,你只需知道某种程度。 va_arg()
不会告诉你你在列表的末尾,如果你继续使用它,它会很乐意继续走到尽头。通常,您将使用最后一个已知参数(在您的情况下为mj
)以某种方式指定有多少参数,或者您可以在参数列表中查找一些sentinel值。
当您决定完成后,请务必使用
va_end(pl);
在此之后,va_list pl
无效,不应再次提及。 (除非你想从另一个va_start()
开始。)
说明如何使用它的一个例子是经典的printf()
,它使用变量参数。从man pages,我们知道printf()
被宣布为
int printf(const char *format, ...);
考虑一下,例如函数调用
printf("The value of %s is %ld\n", lbl, x);
在内部,printf()
将使用一些va_list
,我们称之为vl
。 format
是最后一个已知的论点,所以它将用于va_start(vl, format)
。 printf()
将解析这个format
字符串,找到%s
并期望接下来有一个char *
参数。然后它将调用va_arg(vl, char *)
,以某种方式使用此值,并继续解析format
字符串。然后它找到%ld
并期望一些long int
,所以它调用va_arg(vl, long int)
并在打印时使用此值。在更多地解析format
之后,它到达格式字符串的末尾,并且最终使用va_end(vl)
停止。 (如果您正在关注,也许现在您可以弄清楚为什么始终在printf
格式字符串中使用正确的长度修饰符和转换说明符非常重要。)
这就是如何使用va_list
。现在回到你的代码:请记住,va_start(pl, mj);
没有为mj
分配任何东西,因为你可能会想到。它只使用va_list pl
作为最后已知的显式参数来设置mj
。你也因为某种原因重新分配mj
(那部分没有意义)。您根本不想尝试连接任何字符串。看起来你只是想打印它们。从动态分配的某个大小的缓冲区开始,逐个追加字符串,并根据需要重新分配。 (这并不难,但是第一次这样做时看起来并不重要。如何实现它超出了这个问题的范围。)
va_list
没有“组合”任何东西,它一次只给你一个函数的参数(它更像是把它分开)。你必须自己组合,va_list
就是如何获得成功。