如何使用不确定数量的参数和va_list来组合参数的功能?

时间:2016-12-11 16:04:20

标签: c parameters variadic-functions

我想用这个原型制作一个函数:

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;
}

1 个答案:

答案 0 :(得分: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,我们称之为vlformat是最后一个已知的参数,因此它将在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); 不会<{1}}分配任何内容,因为您可能会想到。它仅使用mj作为最后一个已知的显式参数来设置va_list pl。由于某种原因,您也在重新分配mj(该部分没有意义)。您根本不想尝试连接任何字符串。看起来你只是想打印它们。从动态分配的某个大小的缓冲区开始,逐个追加字符串,并根据需要重新分配。 (这并不难,但是第一次这样做时看起来并不重要。如何实现它超出了这个问题的范围。)

mj没有“组合”任何东西,它只能一次为你提供一个函数的参数(更像是它将事情分开)。你必须自己进行组合,va_list就是如何搞定。