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

问题描述 投票:0回答:1

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

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

va_list没有“组合”任何东西,它一次只给你一个函数的参数(它更像是把它分开)。你必须自己组合,va_list就是如何获得成功。

© www.soinside.com 2019 - 2024. All rights reserved.