我是C的新手,我想知道是否有可能创建一个可变参数函数并将变量指针传递给它并将数据写入变量?
我正在寻找的一个明显的例子是scanf
函数,它接受来自stdin的输入并将其写入变量。
以下是我想要做的示例:
void fun(int num, ...){
// insert 2 in a and "abc" in b
}
int main(void){
int a;
char *b;
fun(2, &a, &b);
}
更新我可以改变我的构造函数来获取变量模式而不是它们的数量,所以这里是修改后的代码:
void fun(char *fmt, ...){
// insert 2 in a and "abc" in b
}
int main(void){
int a;
char *b;
fun("dc", &a, &b);
}
答案 0 :(得分:6)
从stdarg man page(man 3 stdarg
)中显示的示例代码开始。略微修改了可读性,并添加了一个简单的main()
:
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
void foo(char *fmt, ...)
{
va_list ap;
int d;
char c, *s;
va_start(ap, fmt);
while (*fmt) {
switch (*(fmt++)) {
case 's':
s = va_arg(ap, char *);
printf("string %s\n", s);
break;
case 'd':
d = va_arg(ap, int);
printf("int %d\n", d);
break;
case 'c':
/* need a cast here since va_arg only
takes fully promoted types */
c = (char) va_arg(ap, int);
printf("char %c\n", c);
break;
}
}
va_end(ap);
}
int main(void)
{
char *s1 = "First";
char *s2 = "Second";
int d = 42;
char c = '?';
foo("sdcs", s1, d, c, s2);
return EXIT_SUCCESS;
}
如果编译并运行上述内容,则会输出
string First
int 42
char ?
string Second
正如liliscent和Jonathan Leffler评论的那样,关键是我们需要一种方法来描述每个可变参数的类型。 (在C中,类型信息在编译时基本上被丢弃,所以如果我们想要为一个参数支持多个类型,我们还必须显式地传递它的类型:类型(可变参数函数参数)在运行时不再存在。)
上面,第一个参数fmt
是一个字符串,其中每个字符描述一个可变参数(通过描述其类型)。因此,预期变量参数的数量与s
字符串中的d
,c
或fmt
字符的数量相同。
printf family of functions和scanf family of functions都使用%
来表示可变参数,后跟该参数的格式详细信息和类型说明。由于它们支持的格式非常复杂,实现它们的代码比上面的示例复杂得多,但逻辑非常相似。
在对问题的更新中,OP询问函数是否可以更改可变参数的值 - 或者更确切地说,变量参数指向的值,类似于scanf()函数族的工作方式。
因为参数是按值传递的,而va_arg()
会产生参数的值,而不是对参数的引用,所以我们在本地对值本身进行的任何修改(s
,{{调用者无法看到1}}或d
函数示例中的c
。但是,如果我们传递指向值的指针 - 就像foo()
函数那样 - 我们可以修改指针指向的值。
考虑上述scanf()
函数foo()
的略微修改版本:
zero()
请注意与void zero(char *fmt, ...)
{
va_list ap;
int *d;
char *c, **s;
va_start(ap, fmt);
while (*fmt) {
switch (*(fmt++)) {
case 's':
s = va_arg(ap, char **);
if (s)
*s = NULL;
break;
case 'd':
d = va_arg(ap, int *);
if (d)
*d = 0;
break;
case 'c':
/* pointers are fully promoted */
c = va_arg(ap, char *);
if (c)
*c = 0;
break;
}
}
va_end(ap);
}
的差异,尤其是foo()
表达式。 (我还建议分别将va_arg()
,d
和c
重命名为s
,dptr
和cptr
,以帮助提醒我们人们在阅读代码时不再是值本身,而是指向我们希望修改的值的指针。我省略了这个更改,以使函数尽可能与sptr
类似,以便于比较两者功能。)
有了这个,我们可以做例如
foo()
和 int d = 5;
char *p = "z";
zero("ds", &d, &p);
将被清除为零,d
将被清除为p
。
我们不限于每个案例中的单个NULL
。例如,我们可以修改上面的内容,为每个格式化字母设置两个参数,第一个是指向参数的指针,第二个是值:
va_arg()
您可以通过例如
使用的最后一个功能void let(char *fmt, ...)
{
va_list ap;
int *dptr, d;
char *cptr, c, **sptr, *s;
va_start(ap, fmt);
while (*fmt) {
switch (*(fmt++)) {
case 's':
sptr = va_arg(ap, char **);
s = va_arg(ap, char *);
if (sptr)
*sptr = s;
break;
case 'd':
dptr = va_arg(ap, int *);
d = va_arg(ap, int);
if (dptr)
*dptr = d;
break;
case 'c':
cptr = va_arg(ap, char *);
/* a 'char' type variadic argument
is promoted to 'int' in C: */
c = (char) va_arg(ap, int);
if (cptr)
*cptr = c;
break;
}
}
va_end(ap);
}
与int a;
char *b;
let("ds", &a, 2, &b, "abc");
具有相同的效果。请注意,我们不会将数据a = 2; b = "abc";
修改为;我们只是将b
设置为指向文字字符串b
。
在C11及更高版本中,有一个abc
关键字(参见例如this answer here),可以与预处理器宏一起使用,根据类型的表达式在表达式之间进行选择。参数(一个或多个)。
因为它在早期版本的标准中不存在,我们现在必须使用例如_Generic
,sin()
和sinf()
来返回其参数的正弦值,具体取决于参数(和期望的结果)是sinl()
,double
还是float
。在C11中,我们可以定义
long double
这样我们就可以调用#define Sin(x) _Generic((x), \
long double: sinl, \
float: sinf, \
default: sin)(x)
,编译器选择正确的函数变体:Sin(x)
等同于Sin(1.0f)
,sinf(1.0f)
等同于{{1}例如。
(上图,Sin(1.0)
表达式评估为sin(1.0)
,_Generic()
或sinl
之一;最终sinf
使宏评估为函数使用宏参数sin
作为函数参数调用。)
这与此答案的前一部分并不矛盾。即使使用(x)
关键字,也会在编译时检查类型。它基本上只是syntactic sugar在宏参数类型比较检查之上,这有助于编写特定于类型的代码;换句话说,一种switch1.case语句,它作用于预处理器宏参数 types ,在每种情况下只调用一个函数。
此外,x
并不适用于可变参数函数。特别是,您无法根据这些函数的任何可变参数进行选择。
但是,使用的宏很容易看起来像可变函数。如果您想进一步探索此类泛型,请参阅例如this“回答”我前段时间写过。