va_list/va_start/va_end/var_arg可变参数的使用

news2025/2/2 6:05:15

个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)

做日志打印或其它可变参数处理时,通常我们会想到使用va_list/va_start/va_end做可变参数的收集和处理。使用这种方式处理可变参数比较通用,同时适用于c与c++中。

1. 关于va_list的理解

va_list是一个变参的结构,通常它的结构中,会有一个起始参数的地址。

就像我们经常见到的main函数中的那样:argc-argcount参数个数,argv-argvalues参数指针数组
int (int argc, char** argv)
我们通过argc知道了参数的个数,通过argv获取到每个参数的指针。

va_list初始化后,提供一个类似argv的变量,用于做参数的遍历处理,但实际情况比这个复杂许多,内部提供的可能是参数位于寄存器上地址与偏移、参数位于栈地址与偏移。
va_list初始化后,通常并不能用于获取参数个数,不能提供类似argc的获取;参数个数需要额外的判断,例如vprintf基于formart的%d,%s等来获取需要输入的参数个数。

对于va_list结构,标准库没有提供直接读取va_list内部信息的函数,对于不同操作系统结构可能会不同,另外可以使用 va_arg 逐个提取参数,或者使用 va_copy 复制 va_list。

相关对va_list的处理函数
va_start:初始化一个可变参数列表var_list内部的指针;
va_arg:获取下一个参数的值
va_end:清理一个可变参数列表var_list;
va_copy(C99+):复制参数列表状态

2. 关于va_start/va_end

通常var_start与var_end成对出现,va_start负责初始化一个var_list可变参数列表指针,va_end负责使用完清理。

void va_start(va_list ap, prev_param);
void va_end(va_list ap);
参数ap:一个 va_list 类型的变量,用于存储可变参数列表的内部状态。
参数prev_param:可变参数列表之前的最后一个固定

var_start与var_end使用时,要特别注意的一个点是,成对出现与使用,并且如果使用过一次var_list后,需要重新初始化才能再次使用;原因是因为使用过的va_list内部的指针偏移被修改过了,再次使用的话,偏移未重置,读取的参数可能就有问题了或越界了。

一个使用var_start与var_end两次的样例,形如如下的效果:
先使用一次vsnprintf获取输出字符串长度,然后再申请字符串,然后再调用一遍vsnprintf把字符串输出到buffer中。

std::string LogToStr(const char* format, ...) {
      // init va_list
      va_list valist;
      va_start(valist, format);
      
      // fill buffer 
      int length = vsnprintf(nullptr, 0, format, valist);
      std::string buffer;
      if (length > 0) {
              // destory and reinit valist 
              va_end(valist);
              va_start(valist, format);
              
              // alloc size and fill buffer 
              buffer.resize(length + 1);
              length = vsnprintf(&buffer[0], length + 1, format, valist);
      }
      
      // destory valist and return buffer
      va_end(valist);
      return buffer;
}

3. 接受va_list的系统函数

直接接受va_list的系统函数也比较多,常见的有vprintf, vfprintf, vsnprintf等:
int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vsnprintf(char* buffer, size_t size, const char* format, va_list ap);
使用vprintf,打印到标准输出的样例:

void print_formatted(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

使用vfprintf,输出到文件的样例:

 void log_to_file(FILE* file, const char* format, ...) {
     va_list args;
     va_start(args, format);
     vfprintf(file, format, args);
     va_end(args);
 }

使用vsnprintf,输出到定长字符串的样例:

  void safe_format_string(char* buffer, size_t size, const char* format, ...) {
      va_list args;
      va_start(args, format);
      vsnprintf(buffer, size, format, args);
      va_end(args);
  }
函数/宏名称作用
vprintf将格式化字符串输出到标准输出。
vfprintf将格式化字符串输出到指定文件流。
vsprintf将格式化字符串存储到指定缓冲区。
vsnprintf将格式化字符串存储到指定缓冲区,并确保不超出缓冲区大小。
vscanf从标准输入读取格式化数据。
vfscanf从指定文件流读取格式化数据。
vsscanf从指定字符串读取格式化数据。
vfwprintf将格式化后的宽字符串输出到指定文件流。
vswprintf将格式化后的宽字符串存储到指定缓冲区。
vsyslog将格式化日志输出到系统日志。
vsnprintf_s安全版本的 vsnprintf,确保不超出缓冲区大小。

除了这一些能接受va_list的函数来直接调用外,还可以直接遍历va_list使用。
可以使用var_arg来一个一个取参数使用:
type va_arg(va_list ap, type);

int SumValues(const int count, ...) {
  va_list args;
  va_start(args, count);
  int sum = 0;
  for (int i=0; i<count; i++){
    sum += va_arg(args, int);
  }
  va_end(args);
  return sum;
}

其它,stl库模版实现可变参数

另外还一种stl库中带的可变参数模板形式:

template<typename... Args>
void myFunction(Args... args) {
    int count = sizeof...(args);
    // 处理args
}

template<typename... Args>
auto sum(Args... args) {
    return (args + ...);
}

int main() {
    std::cout << sum(1, 2, 3, 4) << std::endl;  // 输出: 10
    std::cout << sum(1.5, 2.5, 3.5) << std::endl;  // 输出: 7.5
    return 0;
}

个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2289651.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【linux网络(4)】传输层协议详解(上)

目录 前言1. UDP协议报文详解2. TCP协议的报文格式3. TCP的确认应答机制4. TCP的连接管理机制1. TCP三次握手的过程2. TCP四次挥手的过程 5. 总结 前言 上一篇文章介绍了应用层中最重要的http协议&#xff0c;本篇文章将讲解传输层的两个协议: TCP和UDP. 由于UDP是一种简洁的协…

【esp32-uniapp】uniapp小程序篇02——引入组件库

一、引入组件库&#xff08;可自行选择其他组件库&#xff09; 接下来介绍colorUI、uview plus的安装&#xff0c;其他的安装可自行查找教程 1.colorUI weilanwl/coloruicss: 鲜亮的高饱和色彩&#xff0c;专注视觉的小程序组件库 下载之后解压&#xff0c;将\coloruicss-ma…

【机器学习】自定义数据集,使用scikit-learn 中K均值包 进行聚类

一、K 均值算法简介 K 均值算法的目标是将数据集划分为 K 个簇&#xff0c;使得每个数据点属于离它最近的簇中心&#xff08;centroid&#xff09;所代表的簇。 K均值聚类算法步骤 ① 初始化&#xff1a; 随机选择原始数据的K个数据点作为初始质心&#xff08;聚类中心&…

进阶数据结构——高精度运算

目录 前言一、高精度运算的定义与背景二、高精度运算的实现方式三、高精度运算的算法实现四、高精度运算的应用场景五、代码模版&#xff08;c&#xff09;六、经典例题1.[高精度加法](https://www.lanqiao.cn/problems/1516/learning/?page1&first_category_id1&name…

设计模式Python版 原型模式

文章目录 前言一、原型模式二、原型模式示例三、原型管理器 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式&#xff1a;关注类和对…

用 JavaScript 打造交互式表格:添加与删除行功能

前言 在网页开发中&#xff0c;创建交互式表格是很常见的。今天我们通过一个示例&#xff0c;来展示如何使用 HTML、CSS 和 JavaScript 实现一个能够动态添加和删除行的表格&#xff0c;并详细解释其中 JavaScript 部分的代码逻辑。 功能展示 初始状态&#xff1a;页面加载后…

Linux02——Linux的基本命令

目录 ls 常用选项及功能 综合示例 注意事项 cd和pwd命令 cd命令 pwd命令 相对路径、绝对路径和特殊路径符 特殊路径符号 mkdir命令 1. 功能与基本用法 2. 示例 3. 语法与参数 4. -p选项 touch-cat-more命令 1. touch命令 2. cat命令 3. more命令 cp-mv-rm命…

服务器虚拟化实战:架构、技术与最佳实践

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 服务器虚拟化是现代 IT 基础设施的重要组成部分&#xff0c;通过虚拟化技术可以提高服务器资源利用率、降低硬件成本&am…

AI大模型开发原理篇-1:语言模型雏形之N-Gram模型

N-Gram模型概念 N-Gram模型是一种基于统计的语言模型&#xff0c;用于预测文本中某个词语的出现概率。它通过分析一个词语序列中前面N-1个词的出现频率来预测下一个词的出现。具体来说&#xff0c;N-Gram模型通过将文本切分为长度为N的词序列来进行建模。 注意&#xff1a;这…

Python从零构建macOS状态栏应用(仿ollama)并集成AI同款流式聊天 API 服务(含打包为独立应用)

在本教程中,我们将一步步构建一个 macOS 状态栏应用程序,并集成一个 Flask 服务器,提供流式响应的 API 服务。 如果你手中正好持有一台 MacBook Pro,又怀揣着搭建 AI 聊天服务的想法,却不知从何处迈出第一步,那么这篇文章绝对是你的及时雨。 最终,我们将实现以下功能: …

leetcode 2080. 区间内查询数字的频率

题目如下 数据范围 示例 这题十分有意思一开始我想对每个子数组排序二分结果超时了。 转换思路&#xff1a;我们可以提前把每个数字出现的位置先记录下来形成集合&#xff0c; 然后拿着left和right利用二分查找看看left和right是不是在集合里然后做一个相减就出答案了。通过…

深入了解 SSRF 漏洞:原理、条件、危害

目录 前言 SSRF 原理 漏洞产生原因 产生条件 使用协议 使用函数 漏洞影响 防御措施 结语 前言 本文将深入剖析 SSRF&#xff08;服务端请求伪造&#xff09;漏洞&#xff0c;从原理、产生原因、条件、影响&#xff0c;到防御措施&#xff0c;为你全面梳理相关知识&am…

11.QT控件:输入类控件

1. Line Edit(单行输入框) QLineEdit表示单行输入框&#xff0c;用来输入一段文本&#xff0c;但是不能换行。 核心属性&#xff1a; 核心信号&#xff1a; 2. Text Edit(多行输入框) QTextEdit表示多行输入框&#xff0c;也是一个富文本 & markdown编辑器。并且能在内容超…

Cesium+Vue3教程(011):打造数字城市

文章目录 Cesium打造数字城市创建项目加载地球设置底图设置摄像头查看具体位置和方向添加纽约建筑模型并设置样式添加纽约建筑模型设置样式划分城市区域并着色地图标记显示与实现实现飞机巡城完整项目下载Cesium打造数字城市 创建项目 使用vite创建vue3项目: pnpm create v…

Windows系统本地部署deepseek 更改目录

本地部署deepseek 无论是mac还是windows系统本地部署deepseek或者其他模型的命令和步骤是一样的。 可以看: 本地部署deepsek 无论是ollama还是部署LLM时候都默认是系统磁盘&#xff0c;对于Windows系统&#xff0c;我们一般不把应用放到系统盘&#xff08;C:&#xff09;而是…

基于Python的药物相互作用预测模型AI构建与优化(下.代码部分)

四、特征工程 4.1 分子描述符计算 分子描述符作为量化分子性质的关键数值,能够从多维度反映药物分子的结构和化学特征,在药物相互作用预测中起着举足轻重的作用。RDKit 库凭借其强大的功能,为我们提供了丰富的分子描述符计算方法,涵盖了多个重要方面的分子性质。 分子量…

[Python学习日记-79] socket 开发中的粘包现象(解决模拟 SSH 远程执行命令代码中的粘包问题)

[Python学习日记-79] socket 开发中的粘包现象&#xff08;解决模拟 SSH 远程执行命令代码中的粘包问题&#xff09; 简介 粘包问题底层原理分析 粘包问题的解决 简介 在Python学习日记-78我们留下了两个问题&#xff0c;一个是服务器端 send() 中使用加号的问题&#xff0c…

origin如何在已经画好的图上修改数据且不改变原图像的画风和格式

例如我现在的.opju文件长这样 现在我换了数据集&#xff0c;我想修改这两个图表里对应的算法里的数据&#xff0c;但是我还想保留这图像现在的形式&#xff0c;可以尝试像下面这样做&#xff1a; 右击第一个图&#xff0c;出现下面&#xff0c;选择Book[sheet1] 选择工作簿 出…

5.3.2 软件设计原则

文章目录 抽象模块化信息隐蔽与独立性衡量 软件设计原则&#xff1a;抽象、模块化、信息隐蔽。 抽象 抽象是抽出事物本质的共同特性。过程抽象是指将一个明确定义功能的操作当作单个实体看待。数据抽象是对数据的类型、操作、取值范围进行定义&#xff0c;然后通过这些操作对数…

【ArcGIS遇上Python】批量提取多波段影像至单个波段

本案例基于ArcGIS python,将landsat影像的7个波段影像数据,批量提取至单个波段。 相关阅读:【ArcGIS微课1000例】0141:提取多波段影像中的单个波段 文章目录 一、数据准备二、效果比对二、python批处理1. 编写python代码2. 运行代码一、数据准备 实验数据及完整的python位…