AI智能摘要
可变参数是C/C++语言中允许函数接收可变数量和类型参数的特性,常见于printf、scanf等函数。通过头文件提供的va_list、va_start、va_arg和va_end等宏,可安全访问和处理可变参数。C99引入的__VA_ARGS__宏支持在宏定义中使用可变参数,结合##__VA_ARGS__可避免空参时的多余逗号。此外,vprintf等函数可用于封装自定义变参处理函数,实现灵活的格式化输出。
— 此摘要由AI分析文章内容生成,仅供参考。
![]()
1 什么是可变参数
可变参数是 C 语言和 C++ 语言中的一个特性,它允许函数定义可变数量和可变类型的参数。例如,printf、scanf等函数的实现就使用了可变参数,以下是printf和scanf的函数声明:
int printf(const char *, ...); int scanf(const char *, ...);
在形参列表里,省略号...即为可变参数列表,用于表示可变参数。
2 如何处理可变参数
在C/C++语言中,提供了头文件<stdarg.h>用于处理可变参数。通过引用该头文件,我们可以使用在该头文件中提供的一些可变参数处理宏和处理函数。
2.1 可变参数宏
C99标准新增的__VA_ARGS__预定义标识符即为可变参数宏,其用于在宏定义中代表可变参数列表。通过它,我们可以在宏定义时传入可变参数,并在宏展开时将这些参数插入到指定的位置。如下所示,我们定义了一个打印调试信息的宏,且在每次输出时都自动添加换行符,使用__VA_ARGS__来接收和展开可变参数:
#include <stdio.h>
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) \
do { \
printf(fmt "\n", ##__VA_ARGS__); \
} while (0)
#else
#define DEBUG_PRINT(fmt, ...)
#endif
在上面这个例子中,我们需要额外注意##运算符。##运算符的具体用法在《[C/C++]# 和 ## 运算符详解》这篇文章中有介绍,其是记号粘贴运算符,用于将两个记号粘连为一个记号,但是在此处,其还有一个额外的用法,即用于去掉当可变参数宏__VA_ARGS__为空时,多余产生的逗号。
2.2 可变参数解析宏
对于可变参数函数,C/C++语言提供了以下可变参数解析宏用于处理函数参数:
- va_list:一种用于存储可变参数信息的类型,用于声明一个用于访问函数可变参数的变量
- va_start(ap, param):用于初始化
va_list类型的变量,使其指向函数参数列表中的第一个可变参数 - va_arg(ap, type):用于访问
中的下一个可变参数,并将其转换为指定的类型va_list - va_end(ap):用于清理
变量,在调用va_list之后必须调用va_startva_end
通过灵活使用这些宏,可以省去解析可变参数的麻烦,以下是一个简单的使用示例:
#include <stdio.h>
#include <stdarg.h>
void print_numbers(int count, ...) {
va_list args;
va_start(args, count); // 初始化 va_list 变量
for (int i = 0; i < count; i++) {
int num = va_arg(args, int); // 获取下一个 int 类型的参数
printf("%d\n", num);
}
va_end(args); // 清理 va_list 变量
}
int main() {
print_numbers(3, 10, 20, 30); // 打印 10, 20, 30
return 0;
}
2.3 可变参数函数
C/C++语言提供了一些可以直接处理可变参数的函数,例如vprintf、、vsprintf、vsnprintf和vfprintf等,下面讲解vsfscanfvprintf函数的使用示例,其余函数的用法也基本大差不差。
vprintf函数的函数原型如下所示:
#include <stdio.h> int vprintf(const char *format, va_list args);
- 形参:
- format:这是一个格式化字符串,指定了输出的格式
- args:这是一个
va_list类型的参数,表示一个变参列表
- 返回值:返回输出的字符数(不包括终止的空字符),如果发生错误,则返回一个负数
vprintf函数的使用示例如下所示,演示了如何实现一个自定义的打印函数:
#include <stdio.h>
#include <stdarg.h>
// 自定义函数,接受一个格式化字符串和一个可变参数列表
void my_print(const char *format, ...) {
va_list args;
va_start(args, format); // 初始化 va_list 类型的变量 args
vprintf(format, args); // 使用 vprintf 输出格式化字符串
va_end(args); // 清理 va_list 类型的变量
}
int main() {
my_print("Hello, %s! You have %d new messages.\n", "Alice", 5);
return 0;
}
太好了,正好项目里要写日志库,这段DEBUG_PRINT抄走用了👍
想起大学时第一节C课被va_list支配的恐惧😂
当年写printf模拟器,调试到凌晨三点😭
大佬能不能再开一篇讲C++11的变参模板?老宏看得我眼花,想洗洗眼睛
终于搞明白va_list怎么用了,以前一直懵
DEBUG_PRINT这个宏定义太实用了,直接搬进项目
大学作业写printf模拟器,被va_start搞到吐🤮
##__VA_ARGS__那个逗号处理真的细,差点翻车
想看更多vsnprintf的安全用法,容易溢出啊
vsnprintf溢出问题确实头疼,有没有安全写法示例?
每次用va_end都提心吊胆,怕没清理干净内存
这文章比教材讲得清楚多了,收藏了慢慢看
教材写得像天书,这篇接地气多了,必须点赞