【c++随笔08】可变参数——va_list、va_start、va_end、va_arg

【c++随笔08】可变参数——va_list、va_start、va_end、va_arg

  • 引子:printf源码
  • 一、可变参数函数的基本原理
  • 二、可变参数的使用步骤
    • 步骤 1: 引入 头文件
    • 步骤 2: 声明可变参数函数
    • 步骤 3: 使用 va_list 类型
    • 步骤 4: 初始化 va_list
    • 步骤 5: 使用 va_arg 访问参数
    • 步骤 6: 清理 va_list
  • 三、完整示例代码
  • 四、接受一个格式字符串和不定数量的参数
  • 五、snprintf 和 vsnprintf

原创作者:郑同学的笔记
原创地址: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 
#include 
#include 

#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++ 使用 提供的函数和宏来访问这些参数。以下是可变参数函数的基本工作原理:

  1. 引入 头文件。
  2. 在函数定义中,在参数列表的最后一个参数之前添加省略号(…)来声明可变数量的参数。
  3. 使用 va_list 类型的对象来存储可变参数的信息。
  4. 使用 va_start 宏初始化 va_list 对象。
  5. 使用 va_arg 宏按顺序访问每个可变参数。
  6. 使用 va_end 宏清理 va_list 对象。
  7. 现在,让我们详细了解这些步骤。

二、可变参数的使用步骤

步骤 1: 引入 头文件

首先,我们需要引入 头文件,以便能够使用其中的函数和宏。在代码的开头添加以下语句:

#include 

步骤 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 
#include 

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 
#include 

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 
#include 

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 中。

在 main 函数中,我们调用 formatString 函数,将格式化的字符串 “Hello, World!” 存储在 buffer 中,并用 printf 输出结果。

你可能感兴趣的:(C++随笔,c++,可变参数,va_list,va_start)