深度解析可变参数列表

深度理解计算可变参数宏 __VA_ARGS__ 的参数个数 网上很多关于计算可变参数宏 __VA_ARGS__ 的参数个数的帖子很多,但是都是写个宏,告诉别人就是这样写的,用就对了,但是都没有解释其中的道理。对于大多说人来说,知道这样用就够了,但是我喜欢探究其间的原理。通过跟老同事的探讨用于算是理解了。在此文字记录下,希望对你有所帮助。 源码1.c #define OSI_VA_NARGS_IMPL(_0, _1, _2, _3, N, ...) N #define OSI_VA_NARGS(...) OSI_VA_NARGS_IMPL(0, ##__VA_A 阅读详情

可变参数列表解析:

可变参数是指,函数部分的参数个数不是固定的,每次根据需求可以传入不同个数的参数。实际上,printf()函数;就是一个典型的可变参数函数,可以打印一个字符串,也可以打印多个字符串。

例如:

int main()
{
    printf("%d\n ",2);                                 //有一个参数
    printf("%s %d\n","sunshine",3);                    //有两个参数
    printf("%s  %s  %d\n","sunshine", "girl",3);       //有三个参数
    system("pause");
    return 0;
}

结果:这里写图片描述

下面以一个求平均值的代码来详细解释可变参数列表:
#include<stdarg.h>
average(int n, ...)   
{
    va_list arg;
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    for (i = 0; i < n; i++)
    {
        sum += va_arg(arg, int);
    }
    va_end(arg);
    return sum / n;
} 


int main()
{

    int av = average(3, 4, 5, 6);
    printf("%d\n", av);   //5
    av = average(4, 2,4,6,8);//5
    printf("%d\n", av);
    system("pause");
    return 0;
}

下面对是对以上代码的解释:

#include<stdarg.h>   //可变参数对应的头文件
average(int n, ...)   // 不知道几个参数,所以用...表示,叫做未知参数列表,n代表后面有多少个参数
{
    va_list arg;       
    // char* arg
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    //va_start_a(arg, n)   ((void)(arg = (cha*)&(n) + _INTSIZEOF(n)))   // 向上取一个整型的大小  如果n是一个字节,则 _INTSIZEOF(n) 为4
    //( arg = (char*)&(n) + 4 )   // 函数调用过程中,由于在栈空间开辟的过程中,先将函数参数右边的参数压栈,所以栈顶上的元素,是传的参数个数,因此,arg指向未知参数中的第一个参数 
    for (i = 0; i < n; i++)
    {
        sum += va_arg(arg, int);
        // va_arg(arg, int)   ( *(int*)((arg += 4 ) - 4 ))
        //sum = sum + ( *(int*)((arg += 4 ) - 4 ))  //这句代码写的相当巧妙,完成了两个动作。
        //1.arg移动4个字节,指向未知参数的下一个元素,再赋给arg,使得arg的位置发生改变  
        //2.同时 ((arg += 4 ) - 4 ) 中 要注意-4,回到了第一个元素的位置,但是由于没有赋值,arg所指的仍然是下一个位置
    }   
    va_end(arg);   
    //#define va_end(arg)    ((void)(arg = (char*)0))
    //((void)(arg = (char*)0))   
    return sum / n;
} 


int main()
{

    int av = average(3, 4, 5, 6);
    printf("%d\n", av);   //5
    av = average(4, 2,4,6,8);//5
    printf("%d\n", av);
    system("pause");
    return 0;
}
  • 声明一个va_list 类型的变量 arg,它用于访问参数列表的未确定部分。
  • 这个变量是调用 va_start 来初始化的。它的第一个参数是va_list的变量名,第二个参数是省略号前面最后一个有名字的参数(因为对于不同的函数来说,省略号之前可能会有多个参数)。初始化过程把arg变量设置为指向可变参数部分的第一个参数。
  • 为了访问参数,需要使用va_arg,这个宏接收两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中所有的可变参数都是整型。va_arg返回这个参数的值,并使用va_arg指向下一个可变参数。
  • 最后,当访问完毕最后一个可变参数之后,我们需要调用va_end。

可变参数列表的练习:

模拟实现库函数中的 printf() 函数,函数原型为 print(char *format, …)

void show(int n)
{
    if (n > 9)
    {
        show(n / 10);  //递归,拆分整型每一位
    }
    putchar(n % 10 + '0');   //整型+'0' 结果是 整型对应的字符,比如,整型4+'0' 对应是字符'4'
}


print(const char *format, ...)
{
    va_list arg;  
    va_start(arg, format);   //让arg指向未知参数列表前第一个有名字的参数(format),初始化过程
    while (*format)
    {
        switch (*format)
        {
              case 's':   //遇到s 说明是个字符串
                    {
                          char* ret = va_arg(arg, char*);  //取出的是 字符串首字符的地址
                          while (*ret)
                                {
                                     putchar(*ret);
                                     ret++;
                                 }
                    }
                   break;
        case 'd':      //遇到d 说明是个整型
                    {
                           int ret = (int)va_arg(arg, int);
                           show(ret);
                    }
                    break;
        case 'c':
                    {
                           char* ret = va_arg(arg, char*);
                           putchar(ret);
                    }
                    break;
        default:     // 既不是字符串,也不是整型,字符,就正常输出
                   {
                           putchar(*format);
                   }
                   break;
        }

        format++;
    }

    va_end(arg);
}

int main()
{
    print("s cccc d !~~\n", "beautiful", 'c', 'o','o', 'l',100);  //函数传参,需要传类型 和 内容
    system("pause");
    return 0;
}

输出结果:这里写图片描述

ps:补充一个知识点:库函数中printf() 函数的返回类型 是整型,返回值是打印在屏幕上字符的个数。

可变参数的注意事项:

  • 可变参数必须从头逐个访问。允许在访问了几个可变参数之后在中途停止,但是,不允许一开始就访问参数列表中间的参数。
  • 参数列表中至少有一个命名参数。如果连一个命名参数都没有,那就无法使用va_start。
  • 这些宏是无法直接判断每个参数的数量。
  • 这些宏无法判断每个参数的类型。
  • 如果在va_arg中指定了错误的类型,它就会按照错误的类型的往下查找,结果肯定是错误的。
我理解的C语言变参函数(va_arg,va_start,va_end)  va_arg va_start va_end 阅读详情

相关推荐

PCIe学习笔记(一)-------1.3 PCIe数据包(TLP,DLLP,PLP)

1,PCIe的层次结构 PICe设备在硬件电路上可以分为三个层次结构:事务层(Transaction Layer)、数据链路层(Data Link Layer)、物理层(Physical)。每一个层次又可以分为两个部分,分别为处理发送和处理接收的部分。 也可以分为四个层次,将事务层以上的层次称为软件层(Software Layer)。 2,PCIe数据包 前面已经说过PCIe之间是以数据包的形式来传输数据的,协议共定义了3类数据包。与处理层相关的是处理层数据包(TLP),与数据链路层相关的是数据

llxxyy507的博客 2万+

如何确定特定情况下可变参数函数的参数个数

1.心血来潮,想了解一下可变参数函数 这是vadefs.h函数中相关的定义     #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))     #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) -

Jimmy的博客 1928

Ubutntu下使用realsense d435i(二):获取某二维像素点的三维坐标

00 准备工作 准备工作包括安装驱动以及pyrealsense2,具体可参考上一篇: Ubutntu下使用realsense d435i(一):准备工作以及安装pyrealsense2 https://blog.youkuaiyun.com/gyxx1998/article/details/121461293 01 通过二维像素点获得物体三维坐标 这个在上一篇已经介绍了代码: realsense d435i获取某像素点三维坐标(计算深度和点云两种方法) https://blog.youkuaiyun.com/gyxx1998/

gyxx1998的博客 9943

C/C++中可变参数的详细介绍(va_list,va_start,va_arg,va_end)

转自:http://www.jb51.net/article/41868.htm 可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数。如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现 由于在C语言中没有函数重

gmq_syy的博客 869

零基础教程:用Python给试卷照片自动切题(有道OCR+PIL完整流程)

本文为零基础用户提供了一套完整的Python自动化切题方案。通过调用有道OCR API识别试卷照片中的题目坐标,并利用PIL库进行精准裁剪,将杂乱的试卷图片自动分割为独立的题目图像。教程详细涵盖了环境搭建、图像预处理、API调用、坐标解析与批量处理全流程,帮助教育工作者和技术爱好者高效处理大量试卷,实现工作流程自动化。

grpc6streamer的博客 1172

AD9253 LVDS 高速ADC驱动开发

对应:data clock时钟为500M DDR mode。具体内容:下文的参考博客写的很清楚,我就不详细写了。参考博客clock align模块不完整,本文新增这个模块。3、该款ADC工作在125Msps下,14bit - 2Lane - 1frame 模式。2、查阅Xilinx xapp524手册。1、查阅AD9253器件手册。

CAOXUN_FPGA的博客 2105

可变参数列表解析

通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数(不固定)。注意: a .可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。 b.参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无使用va_start。 c. 这些宏是无法直接判断实际存在参数的数量。 d.这些宏无法

@柠檬少年 704

OBS技术深度剖析:从开源直播工具到专业制作引擎的进化之路

本文深入剖析了开源直播软件OBS的技术架构与发展历程。从2012年诞生至今,OBS已成长为直播领域的事实标准,其成功源于模块化设计和开源协作模式。文章详细解析了OBS的核心技术:1) 分层架构与插件系统,2) GPU加速的渲染管线,3) 智能音频处理,4) 多平台编码优化。同时探讨了虚拟摄像头、浏览器源等高级功能的实现原理,并提供了性能优化方案和硬件配置建议。随着AI、WebRTC等新技术发展,OBS将持续引领实时内容创作工具的创新,为开发者和创作者提供更强大的技术支持。

程序猿老樊的博客 323

c/c++:计算可变参数宏 __VA_ARGS__ 的参数个数

如何计算可变参数__VA_ARGS__中的参数个数? google上找了一下,网上答案不少,仔细看看原理都是一样的,比如这篇stackoverflow上的问题,给出的答案真不少, 《C++ preprocessor __VA_ARGS__ number of arguments》 但是排名第一的最佳答案有bug,在参数为空的时候计算错误。经测试找到正确计算方式,自己写了一个,代码如下,gcc...

10km的专栏 1万+

2026 年第十六届MathorCup数学应用挑战赛题目C题中老年人群高血脂症的风险预警及干预方案优化 2026妈妈杯 详细思路、代码、模型

2026 年第十六届MathorCup数学应用挑战赛题目 C题中老年人群高血脂症的风险预警及干预方案优化 人口老龄化进程的持续加快,使得40岁及以上中老年人群成为 慢性病防控的核心群体,其中高血脂症的发病率呈逐年攀升态势, 已成为诱发冠心病、脑梗死等心脑血管疾病的重要危险因素,严重 威胁中老年群体的心血管健康与生活质量,也加重了基层医疗的慢 病管理负担。从中医体质学视角来看,体质禀赋与偏颇是疾病发生 发展的内在根基,其中痰湿体质为中老年高血脂症的高发体质类型, 该体质人群存在痰湿内蕴、脾胃运化失常、水湿代谢

YOLO 753

基于STM32L4XX、HAL库的 TMC2660C-PA 驱动程序设计

/ 默认灵敏度和电流。// 20/31最大电流。

zhusl6688的博客 383

C语言:深度解析可变参数列表

C语言的可变参数是通过将函数实现为可变参数的形式,可以使得函数可以接收一个以上的任意多个参数(不固定)。 一.举例 首先我们先来看一个例子:实现一个函数可以求任意个参数的平均值 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;stdarg.h&gt; int average(int n, ...) { int...

ETalien_的博客 370

【AutoCAD中实现双击文本编辑功能】

例如:以下是官方博客例子浅析先加入相关的头文件和库文件。在AutoCAD中有一个很重要的功能,就是双击图形尺寸或文本后会出现一个文本编辑框,用户可在该编辑框中修改文字。本文介绍了如何使用AcEdInplaceTextEditor来实现该功能。实现cad的消息监听。创建一个消息监听的类并在cad中注册来实现消息监听。主要是监听键盘输入。提示:以下是本篇文章正文内容,下面案例可供参考提示:这里对文章进行总结:例如:以上就是今天要讲的内容,本文仅仅简单介绍了的函数和方法。

我的博客 1148

【深度解析】C语言可变参数列表的奥秘:打造灵活的函数接口

本文介绍了C语言中实现可变参数列表的方法,通过<stdarg.h>头文件提供的宏(va_list、va_start、va_arg、va_end)来创建可接收不定数量参数的函数。文章详细讲解了定义函数、初始化参数列表、访问参数及清理资源的步骤,并提供了处理不同类型参数的扩展示例。此外,还探讨了参数个数检测和类型检测的高级技巧,如使用__VA_ARGS__和union结合void*的方法。掌握可变参数列表能显著提升C语言函数的灵活性和扩展性。

极客代码 359

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

C语言可变参数列表源码级深度解析

在C语言中,我们可以通过函数实现可变参数的形式,来使函数接受多个参数,接下我就深度刨析一下C语言中的可变参数的源码及其是如何实现的。 当让在这之前我们先要进行一下知识铺垫了,先要了解一下函数栈帧的部分知识,当然,有想要详细了解的朋友可以看我之前的一篇博客,地址在此奉上。https://blog.youkuaiyun.com/aixintianshideshouhu/article/details...

aixintianshideshouhu的博客 355

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

C++11核心特性深度解析:从列表初始化到可变参数模板的现代编程实践

C++作为高性能系统编程的核心语言,其现代化演进始终围绕提升开发效率、代码安全性与运行性能展开。C++11标准引入的一系列新特性,从根本上改变了编程范式。其中,统一的列表初始化语法通过花括号{}提供了类型安全的初始化方式,有效防止窄化转换,解决了传统初始化中的歧义问题。在类型系统层面,auto关键字实现了编译期类型推导,大幅简化了复杂模板类型的声明,而decltype则能精确获取表达式类型,两者结合为泛型编程和模板元编程提供了强大支撑。这些特性共同服务于现代C++工程实践,使得开发者能够编写出更简洁、安全且

weixin_30487201的博客 424

C++11新特性深度解析:统一初始化、类型推导与STL增强

在C++编程语言的发展历程中,类型系统和初始化机制始终是核心基础概念。C++11标准通过引入统一的列表初始化语法,解决了C++98/03时代多种初始化方式并存导致的混乱问题,其原理基于`std::initializer_list`模板类,实现了语法的一致性与安全性。这一改进的技术价值在于显著提升了代码的清晰度与安全性,有效防止了窄化转换和令人烦恼的解析歧义。同时,`auto`和`decltype`关键字将类型推导能力赋予编译器,减少了冗长类型声明的编码负担,**提升了开发效率**,并为模板元编程提供了更强支

weixin_34293911的博客 351

C++可变参数模板:从语法到实战的编程范式转变

可变参数模板是C++模板编程中的核心概念,它允许函数和类模板接受任意数量和类型的参数,实现了从固定参数列表到动态参数处理的思维跃迁。其原理基于模板参数包和函数参数包的声明与展开机制,通过递归展开、折叠表达式等技术实现参数包的编译期处理。这一技术为C++带来了强大的元编程能力,显著提升了代码的通用性和表达力,成为实现类型安全格式化、元组构造、依赖注入等高级设施的基础。在实际应用中,可变参数模板广泛应用于实现通用工厂函数、日志系统参数处理和编译期类型计算等场景,是现代C++库开发中不可或缺的“万能胶水”。

weixin_33976072的博客 425

【C语言】可变参数列表

可变参数列表,使用起来像是数组,学习过函数栈帧的话可以发现实际上他也就是在栈区定义的一块空间当中连续访问,不过他不支持直接在中间部分访问,但是可以访问一半就结束。这篇文章博主带大家来学习学习可变参数的一些特性。

jhao的博客 4162

深度解析Java可变参数类型以及与数组的区别

深度解析Java可变参数类型以及它与数组的区别

Java仗剑走天涯 1万+

【C语言深入】带你了解C语言中的可变参数列表

name我们是否能自定义一个参数可变的函数呢?

kk702392702的博客 2364

【C语言】深度剖析可变参数列表源码

   C语言中可变参数是一个比较有意思的实现,通过将函数实现为可变参数的形式,可以使得函数接收1个以上的任意多个参数。我们先来举个栗子: 实现一个函数可以求任意个参数的平均值。 #define _CRT_SECURE_NO_WARNINGS 1 #include &lt;stdio.h&gt; #include&lt;stdarg.h&gt; int average(int n, ...)...

Miss_Monster的博客 453

c语言深度理解(函数栈帧与可变参数列表)

自用笔记

凡事都是靠练习 995

【C语言深度剖析】第七篇:可变参数列表

文章目录一、基本使用二、原理 一、基本使用 首先看下面代码: #include <stdio.h> int GetMax(int x, int y) { if (x > y) { return x; } return y; } int main() { int x = 0; int y = 0; scanf("%d %d", &x, &y); int max = GetMax(x, y); printf("max = %d\n", ma..

Pass-by 252

《C语言深度剖析》第六章 函数栈帧与可变参数列表 p1可变参数列表(完结)( C语言从入门到入土(进阶篇)

目录 可变参数列表 1. 求两个数据中的最大值 2. 如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗? 3. 结果并未受影响,可是,我们解析的时候,是按照va_arg(arg, int)来解析的,这是为什么? 4. 注意事项 5. 原理 6. 先看看这几个宏的含义 7. 原理图 本章节文章是作者通过观看《C语言深度剖析》等各种资料总结的精华,基础部分省略了不少,是为了让大家能够更加深入了解C语言的魅力!因为为了避免与之前的文章发生赘述,所以就直接讲作者认为的精华

原来45的博客 1074
上一篇: 指针数组,数组指针,函数指针,函数指针数组,函数指针数组的指针
下一篇: 函数指针的应用(一)计算器
Name_seven
博客等级 码龄9年 12粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值