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


    原创作者:郑同学的笔记
    原创地址: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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 可知,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 
    
    • 1

    步骤 2: 声明可变参数函数

    在函数定义中,将省略号(…)放在参数列表的最后一个参数之前,以声明可变数量的参数。例如:

    void printValues(int count, ...);
    
    • 1

    以上代码定义了一个名为 printValues 的函数,它接受一个整数 count,以及可变数量的参数。

    步骤 3: 使用 va_list 类型

    va_list 是一个类型,用于存储可变参数的信息。在函数内部,我们需要声明一个 va_list 类型的对象来处理参数。例如:

    void printValues(int count, ...) {
        va_list args;
        // 其他代码
    }
    
    • 1
    • 2
    • 3
    • 4

    上述代码中,我们创建了一个名为 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);
        // 其他代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样做会将 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 做进一步处理
        }
    
        // 其他代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述代码中,我们使用 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样做会确保释放 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在上述示例中,我们定义了一个 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在上述示例中,我们定义了一个 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, ...);
    
    • 1
    • 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);
    
    • 1

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这个示例中,我们定义了一个 formatString 函数,该函数使用 vsnprintf 将可变参数列表格式化为字符串,并将结果存储在临时缓冲区 temp 中。然后,我们使用 snprintf 将 temp 缓冲区的内容复制到目标缓冲区 buffer 中。

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

  • 相关阅读:
    (最简单,详细,直接上手)uniapp/vue中英文多语言切换
    wordpress网站搭建(centos stream 9)
    chatgpt-web发布之docker打包流程
    sql---慢查询和语句耗时
    【零基础入门MyBatis系列】第六篇——在Web中应用MyBatis
    【水果派不吃灰】Raspberry Pi树莓派小常识
    SpringSecurity
    CAT1 4G+以太网开发板232数据通过4G模块TCP发到服务器
    Python爬虫第一章(图片爬取与API接口爬取)
    《系统之美》笔记
  • 原文地址:https://blog.csdn.net/junxuezheng/article/details/131690070