环境:Ubuntu gcc11.3.0
可变参数是什么
可变参数是指函数可以接收不确定个数的参数,…表示可变参数包,它必须要写在正常参数表的后面,例如我们熟知的printf
函数定义:
int printf(const char *format, ...);
printf
的第一个参数为char* fmt
,后面则是不定参数包。printf
的用法都知道就不赘述。这里贴一个使用示例:printf("%d,%d,%d",1,2,3);
之所以会使用不定参数就是因为我们并不知道用户具体会传入几个参数,所以在函数体内部应该如何进行解包呢?
如何解包对参数进行使用
C语言中要对可变参数进行使用,需要使用其提供的几个宏:
其中,va_list
是一个在 C 语言中用于处理不定参数的类型。它定义在 <stdarg.h>
头文件中,并提供了一组宏函数来访问不定参数列表,其中最常用的是 va_start
、va_arg
和 va_end
。
下面是这些宏函数的详细介绍:
-
va_list
:va_list
是一个用于保存可变参数信息的类型。你可以将其声明为一个变量,通常命名为ap
。 -
va_start
:va_start
宏函数用于初始化va_list
变量。它接受两个参数,第一个参数是va_list
变量名,第二个参数是最后一个命名参数。va_start
宏函数在初始化va_list
变量后,使其指向可变参数列表中的第一个参数。 -
va_arg
:va_arg
宏函数用于访问可变参数列表中的每个参数。它接受两个参数,第一个参数是va_list
变量名,第二个参数是参数的类型。va_arg
宏函数返回指定类型的参数,并将va_list
指针指向下一个参数。 -
va_end
:va_end
宏函数用于清理va_list
变量。它接受一个参数,即va_list
变量名。va_end
宏函数在使用完可变参数列表后调用,以确保清理资源。
下面是一个简单的示例,演示了如何使用 va_list
相关的宏函数来实现一个自定义的可变参数函数:
#include <stdio.h>
#include <stdarg.h>
void print_integers(int num, ...)//函数中必须包含命名参数,否则va_start无法确认可变参数的起始位置
{
va_list ap;
va_start(ap, num);
for (int i = 0; i < num; i++) {
int value = va_arg(ap, int);
printf("%d ", value);
}
va_end(ap);
}
int main()
{
print_integers(3, 1, 2, 3); // 输出:1 2 3
return 0;
}
在上面的示例中,print_integers
函数接受一个整数参数 num
,后面跟着不定数量的整数参数。它使用 va_list
相关的宏函数来访问并打印这些整数参数。首先,它调用 va_start
来初始化 va_list
变量 ap
,然后使用一个循环和 va_arg
宏函数来访问每个整数参数,并使用 printf
函数打印出来。最后,它调用 va_end
来清理 va_list
变量。
这就是C语言如何使用可变参数列表的方法。
其他情况
以printf
函数为例,虽然上方已经对可变参数包进行了解包并使用,我们可能传入各种类型的数据,此时像上方代码一样在va_arg()
中第二个参数固定的填写一个int
有点不切实际,因为类型代表着内存中的寻址大小,统一格式显然会致使解析可变参数列表出现问题,无法解析所有元素。
为了解决这个问题,使用C语言提供的函数vasprintf()
:
vasprintf()
函数是C语言中的一个可变参数函数,用于将可变参数按照指定的格式化字符串进行格式化,并将结果存储在动态分配的字符串中(记得手动释放该字符串)。
参数解析:
-
strp
:一个指向字符指针的指针,用于接收格式化后的字符串。该指针将被设置为指向动态分配的内存块,其中包含格式化后的字符串。 -
format
:一个字符串,用作格式化的模板。它可以包含普通字符和格式化指示符。 -
ap
:一个va_list
类型的参数,包含了可变参数列表。需要注意的是,
vasprintf()
函数在C标准中并不是标准函数,但它在许多系统中都可用,并且非常有用。如果你的系统不支持vasprintf()
函数,你可以尝试使用vsnprintf()
函数来实现类似的功能。
此处我黏贴一个自己实现的printf()函数代码展示可变参数列表的使用
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
void MyPrint(const char* fmt, ...){
va_list ap;
va_start(ap, fmt);
char *buffer;
vasprintf(&buffer,fmt,ap);
printf("%s",buffer);
va_end(ap);
free(buffer);//一定要释放,否则会内存泄漏,vasprintf把数据写进了堆空间。
}
int main(){
MyPrint("%s - %d\n","李四",1); // 输出:李四 - 1
return 0;
}
注意事项
- 可变参数列表前可以有多个命名参数。可变参数列表的定义可以出现在固定参数之后,允许在函数声明或定义中同时存在命名参数和可变参数。
#include <stdio.h>
#include <stdarg.h>
///
//可变参数列表前有多个命名参数的情况
void print_info(const char* format, int count, ...) {
va_list args;
va_start(args, count);
printf("Format: %s\n", format);
printf("Count: %d\n", count);
for (int i = 0; i < count; i++) {
int num = va_arg(args, int);
printf("Argument %d: %d\n", i+1, num);
}
va_end(args);
}
int main() {
print_info("Printing integers:", 3, 10, 20, 30);
return 0;
}
//在上面的示例中,`print_info()`函数接受三个参数:`format`、`count`和可变参数列表。
//`format`参数是一个字符串,用于指定打印格式。`count`参数表示后续可变参数的个数。
//在函数内部,我们使用`va_start()`函数初始化`va_list`变量,并通过`va_arg()`函数遍历可变参数列表。
- 可变参数列表前必须要有命名参数,否则无法确认可变参数列表的起始位置。
同时,也无法确认可变参数列表的结束位置。