原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/131690070
qq技术交流群:921273910
当你在编写 C++ 函数时,有时候你会需要处理可变数量的参数。C++ 中提供了 头文件,其中包含了用于处理可变参数的函数和宏。本教程将向你介绍如何使用 来编写可变参数的函数。
引子:printf源码
从【编译、链接、装载十五】系统调用与API——printf源码分析可知printf的源码如下。
- 下载glibc源码
wget http://mirrors.ustc.edu.cn/gnu/libc/glibc-2.28.tar.gz
- 解压并查看,glibc实现的printf的源码在
glibc-2.28\stdio-common\printf.c
#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>
#undef printf
/* Write formatted output to stdout from the format string FORMAT. */
/* VARARGS1 */
int
__printf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
- 可知,printf的实现,就使用了可变参数 va_list、va_start、va_end
一、可变参数函数的基本原理
可变参数函数允许你在函数中接受不定数量的参数。C++ 使用 提供的函数和宏来访问这些参数。以下是可变参数函数的基本工作原理:
- 引入 头文件。
- 在函数定义中,在参数列表的最后一个参数之前添加省略号(…)来声明可变数量的参数。
- 使用 va_list 类型的对象来存储可变参数的信息。
- 使用 va_start 宏初始化 va_list 对象。
- 使用 va_arg 宏按顺序访问每个可变参数。
- 使用 va_end 宏清理 va_list 对象。
- 现在,让我们详细了解这些步骤。
二、可变参数的使用步骤
步骤 1: 引入 头文件
首先,我们需要引入 头文件,以便能够使用其中的函数和宏。在代码的开头添加以下语句:
#include <cstdarg>
步骤 2: 声明可变参数函数
在函数定义中,将省略号(…)放在参数列表的最后一个参数之前,以声明可变数量的参数。例如:
void printValues(int count, ...);
以上代码定义了一个名为 printValues 的函数,它接受一个整数 count,以及可变数量的参数。
步骤 3: 使用 va_list 类型
va_list 是一个类型,用于存储可变参数的信息。在函数内部,我们需要声明一个 va_list 类型的对象来处理参数。例如:
void printValues(int count, ...) {
va_list args;
// 其他代码
}
上述代码中,我们创建了一个名为 args 的 va_list 对象。
步骤 4: 初始化 va_list
在访问可变参数之前,我们需要使用 va_start 宏对 va_list 对象进行初始化。va_start 宏需要两个参数:va_list 对象和最后一个固定参数的名称。例如:
void printValues(int count, ...) {
va_list args;
va_start(args, count);
// 其他代码
}
这样做会将 args 对象设置为可变参数的初始状态。
步骤 5: 使用 va_arg 访问参数
现在,我们可以使用 va_arg 宏按顺序访问每个可变参数。va_arg 需要两个参数:va_list 对象和参数的类型。以下是一个示例:
void printValues(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
int number = va_arg(args, int);
// 使用 number 做进一步处理
}
// 其他代码
}
上述代码中,我们使用 for 循环和 va_arg 宏从 args 中获取了指定数量的整数参数。
请注意,我们需要根据参数的实际类型来选择正确的参数类型。此外,重复调用 va_arg 将按照参数的顺序依次访问每个可变参数。
步骤 6: 清理 va_list
在最后使用完可变参数后,我们应该使用 va_end 宏清理 va_list 对象。例如:
void printValues(int count, ...) {
va_list args;
va_start(args, count);
// 使用 va_arg 访问参数
va_end(args);
}
这样做会确保释放 va_list 对象所占用的资源,并使其处于正确的状态。
三、完整示例代码
以下是一个完整的示例代码,演示了如何编写一个函数 printValues,并打印出参数的值:
#include <iostream>
#include <cstdarg>
void printValues(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
int number = va_arg(args, int);
std::cout << "Number: " << number << std::endl;
}
va_end(args);
}
int main() {
printValues(4, 10, 20, 30, 40);
return 0;
}
在上述示例中,我们定义了一个 printValues 函数,它接受一个整数 count 和不定数量的参数。函数根据指定的数量打印出每个参数的值。
在 main 函数中,我们调用了 printValues 函数,并传递了一个整数和四个参数。函数会按照指定的数量打印出每个参数的值。
输出结果如下:
Number: 10
Number: 20
Number: 30
Number: 40
这就是使用 头文件来编写可变参数函数的基本步骤。你可以根据实际需求扩展和修改示例代码。
四、接受一个格式字符串和不定数量的参数
下面是一个完整的示例代码,演示了如何编写一个带有可变参数的函数,并打印出这些参数的值:
#include <iostream>
#include <cstdarg>
void printValues(const char* format, ...) {
va_list args;
va_start(args, format);
while (*format != '\0') {
if (*format == '%') {
format++;
switch (*format) {
case 'd': {
int value = va_arg(args, int);
std::cout << "Value: " << value << std::endl;
break;
}
case 'f': {
double value = va_arg(args, double);
std::cout << "Value: " << value << std::endl;
break;
}
case 's': {
const char* value = va_arg(args, const char*);
std::cout << "Value: " << value << std::endl;
break;
}
default:
break;
}
}
format++;
}
va_end(args);
}
int main() {
printValues("%d %f %s", 10, 3.14, "Hello");
return 0;
}
在上述示例中,我们定义了一个 printValues 函数,它接受一个格式字符串和不定数量的参数。函数根据格式字符串的指示,使用 va_arg 从 args 中按顺序获取每个可变参数,并将其打印出来。
在 main 函数中,我们调用了 printValues 函数,并传递了一个格式字符串和三个参数(整数、双精度浮点数和字符串)。函数会按照格式字符串中的指示打印出每个参数的值。
输出结果如下:
Value: 10
Value: 3.14
Value: Hello
五、snprintf 和 vsnprintf
当需要将格式化的字符串输出或存储到字符缓冲区时,可以使用 snprintf 和 vsnprintf 函数。
snprintf 函数是标准库中的一个函数,用于将格式化的字符串输出或存储到字符缓冲区中。它的原型如下:
int snprintf(char* buffer, size_t count, const char* format, ...);
- buffer:指向目标字符缓冲区的指针,用于存储格式化后的字符串。
count:字符缓冲区的最大长度,包括终止符 \0。
format:格式化字符串,定义了输出的格式。
…:可变参数列表,根据 format 中的格式化说明符进行替换。
snprintf 函数根据 format 字符串中的格式化说明符(如 %s、%d 等)进行相应的替换,并将结果按照指定格式存储到 buffer 中。如果成功,返回生成的字符串的长度,不包括终止符 \0;如果截断了输出,返回要求的总长度,即字符串的实际长度(不包括终止符 \0)加上被截断的字符数。
与之类似,vsnprintf 是一个类似的函数,用于将可变参数列表格式化为字符串并存储到字符缓冲区中。其原型如下:
int vsnprintf(char* buffer, size_t count, const char* format, va_list arglist);
arglist 是一个 va_list 对象,需要使用 va_start 宏和 va_end 宏来初始化和清理。
这两个函数在处理可变参数列表时非常有用,可以方便地进行字符串格式化操作,并提供了一定的安全性,以避免字符缓冲区溢出的问题。
以下是一个示例,演示了如何使用 snprintf 和 vsnprintf 函数:
#include <stdio.h>
#include <stdarg.h>
void formatString(char* buffer, size_t count, const char* format, ...) {
va_list args;
va_start(args, format);
// 使用 vsnprintf 将格式化的文本存储到临时缓冲区
char temp[100];
vsnprintf(temp, sizeof(temp), format, args);
// 使用 snprintf 将临时缓冲区的内容复制到目标缓冲区
snprintf(buffer, count, "%s", temp);
va_end(args);
}
int main() {
char buffer[100];
formatString(buffer, sizeof(buffer), "Hello, %s!\n", "World");
printf("%s", buffer);
return 0;
}
在这个示例中,我们定义了一个 formatString 函数,该函数使用 vsnprintf 将可变参数列表格式化为字符串,并将结果存储在临时缓冲区 temp 中。然后,我们使用 snprintf 将 temp 缓冲区的内容复制到目标缓冲区 buffer 中。文章来源:https://www.toymoban.com/news/detail-725025.html
在 main 函数中,我们调用 formatString 函数,将格式化的字符串 “Hello, World!” 存储在 buffer 中,并用 printf 输出结果。文章来源地址https://www.toymoban.com/news/detail-725025.html
到了这里,关于【c++随笔08】可变参数——va_list、va_start、va_end、va_arg的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!