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

这篇具有很好参考价值的文章主要介绍了【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 <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++ 使用 提供的函数和宏来访问这些参数。以下是可变参数函数的基本工作原理:

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

二、可变参数的使用步骤

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

在 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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】C++11语法 ~ 可变参数模板

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年02月19日
    浏览(25)
  • C++ 11新特性之可变参数模板

    概述         随着C++ 11标准的发布,C++语言获得了许多强大的新特性,其中一项显著提升灵活性和实用性的创新便是可变参数模板。这一特性极大地扩展了模板在处理不定数量类型或值参数时的能力,为开发者提供了更为强大且灵活的泛型编程工具。 工作机制       

    2024年02月22日
    浏览(30)
  • 【C】【C++】可变参数、不定参函数的使用

    C 语言中的可变参数写法: ... 1.1 可变宏函数 以日志举例,我们写入日志时只需要输入关键信息,行号文件等由宏函数补全 这其中,我们需要输入的信息是格式不定的,需要用到可变参数 输出结果: C语言库中的宏 __FILE__ :字符串,记录当前文件名 __LINE__ :整型,记录当前

    2024年02月09日
    浏览(28)
  • 【C++】C++11新特性重点:可变参数+lambda

    C++11新特性第二篇重点 文章目录 上一篇的补充 一、可变参数模板 二、lambda函数 总结 上一篇我们重点讲解了右值引用+移动语义,关于移动构造和移动赋值还有一些需要补充的知识: 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任

    2024年02月09日
    浏览(38)
  • 【C++杂货铺】C++11新特性——可变参数模板

    C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模板和函数模板中只能含固定数量的模板参数,可变模板参数无疑是一个巨大的改进。然而由于可变模板参数比较抽象,使用起来需要一定的技巧,所以之一块还是比较晦涩的。本

    2024年02月03日
    浏览(29)
  • 【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器

    我们紧接着上一节的讲解来进行 C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧

    2024年04月11日
    浏览(69)
  • 【C++】C++11右值引用|新增默认成员函数|可变参数模版|lambda表达式

    在C++11之前,我们只有引用的概念,没有接触到所谓的左值引用或者是右值引用这种概念,从C++11开始,增加了右值引用的概念,那么现在我们将对引用进行一个概念上的区分。在此之前我们所说的引用都是左值引用,对于左值引用相关的内容,可以去看一看博主之前写的文章

    2024年02月15日
    浏览(46)
  • 后端接受List类型参数报错:Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token

      今天和前端调接口时报了\\\"Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token\\\"错误  其实我想要的是这种类型的参数 但是前端传的是这种类型 前端传过来的更像是一个对象而不是一个列表,我们后端不能直接接受它 报错时后端的接受格式 我们后端想把它变成lis

    2024年02月05日
    浏览(32)
  • Scala的集合操作之可变数组和不可变数组,可变List集合与不可变List集合,可变Set与不可变Set操作,可变和不可变Map集合和元组操作

    for推导式的用法 Scala中的for推导式是一种用于对集合进行迭代和转换的强大工具。它提供了一种简洁的语法来处理集合中的元素,并生成新的集合或执行特定的操作。 for推导式的基本语法如下: 其中, pattern 是一个模式,用于解构集合中的元素, collection 是要遍历的集合。

    2024年02月10日
    浏览(42)
  • 39 C++ 模版中的参数如果 是 vector,list等集合类型如何处理呢?

    在前面写的例子中,模版参数一般都是 int,或者一个类Teacher,假设我们现在有个需求:模版的参数要是vector,list这种结合类型应该怎么写呢? map情况下的处理,好像不管咋写都有build error,这块先剩下,如果有网友知道怎么写,请帮忙在留言中指导一下

    2024年01月25日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包