广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C/C++中可变参数的用法详细解析
  • 650
分享到

C/C++中可变参数的用法详细解析

C可变参数 2022-11-15 22:11:33 650人浏览 泡泡鱼
摘要

可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()

可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()等函数的关键之处,也可以用可变参数来对任意数量的数据进行求和,求平均值带来方便(不然就用数组或每种写个重载)。在C#中有专门的关键字parame,但在C,c++并没有类似的语法,不过幸好提供这方面的处理函数,本文将重点介绍如何使用这些函数。

第一步 可变参数表示
用三个点…来表示,查看printf()函数和scanf()函数的声明:
int printf(const char *, ...);
int scanf(const char *, ...);

这三个点用在宏中就是变参宏(Variadic Macros),默认名称为__VA_ARGS__。如:
#define WriteLine(...) { printf(__VA_ARGS__); putchar('\n');}
再WriteLine("Morewindows");

考虑下printf()的返回值是表示输出的字节数。将上面宏改成:
#define WriteLine (...) printf(__VA_ARGS__) + (putchar('\n') != EOF ? 1: 0);
这样就可以得到WriteLine宏的返回值了,它将返回输出的字节数,包括最后的'\n'。如下例所示i和j都将输出12。


       int i = WriteLine("MoreWindows");
       WriteLine("%d", i);
       int j = printf("%s\n", "MoreWindows");
       WriteLine("%d", j);

第二步 如何处理va_list类型
函数内部对可变参数都用va_list及与它相关的三个宏来处理,这是实现变参参数的关键之处。
在<stdarg.h>中可以找到va_list的定义:
typedef char *  va_list;
再介绍与它关系密切的三个宏要介绍下:va_start(),va_end()和va_arg()。

同样在<stdarg.h>中可以找到这三个宏的定义:
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_end(ap)      ( ap = (va_list)0 )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
其中用到的_INTSIZEOF宏定义如下:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

来分析这四个宏:
va_end(ap)这个最简单,就是将指针置成NULL。
va_start(ap,v)中ap = (va_list)&v + _INTSIZEOF(v)先是取v的地址,再加上_INTSIZEOF(v)。_INTSIZEOF(v)就有点小复杂了。( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )全是位操作,看起来有点麻烦,其实不然,非常简单的,就是取整到sizeof(int)。比如sizeof(int)为4,1,2,3,4就取4,5,6,7,8就取8。对x向n取整用C语言的算术表达就是((x+n-1)/n)*n,当n为2的幂时可以将最后二步运算换成位操作——将最低 n - 1个二进制位清 0就可以了。

va_arg(ap,t)就是从ap中取出类型为t的数据,并将指针相应后移。如va_arg(ap, int)就表示取出一个int数据并将指针向移四个字节。

因此在函数中先用va_start()得到变参的起始地址,再用va_arg()一个一个取值,最后再用va_end()收尾就可以解析可变参数了。

第三步 vfprintf()函数和vsprintf()函数
vfprintf()这个函数很重要,光从名字上看就知道它与经常使用的printf()函数有很大的关联。它有多个重载版本,这里讲解最常用的一种:
函数原型


int vfprintf(

   FILE *stream,

   const char *fORMat,

   va_list argptr

);


第一个参数为一个FILE指针。FILE结构在C语言的读写文件必不可少。要对屏幕输出传入stdout。
第二个参数指定输出的格式。
第三个参数是va_list类型,这个少见,但其实就是一个char*表示可变参参数的起始地址。
返回值:成功返回输出的字节数(不包括最后的'\0'),失败返回-1。
vsprintf()与上面函数类似,就只列出函数原型了:

int vsprintf(

   char *buffer,

   const char *format,

   va_list argptr

);


还有一个int _vscprintf(const char *format, va_list argptr );可以用来计算vsprintf()函数中的buffer字符串要多少字节的空间。

代码范例
下面就给出了自己实现的printf()函数(注1)与WriteLine()函数
 


 int Printf(char *pszFormat, ...) 
{
       va_list   pArgList;

       va_start(pArgList, pszFormat);
       int nByteWrite = vfprintf(stdout, pszFormat, pArgList);
       va_end(pArgList);

       return nByteWrite;
}

int WriteLine(char *pszFormat, ...)
{
       va_list   pArgList;

       va_start(pArgList, pszFormat);
       int nByteWrite = vfprintf(stdout, pszFormat, pArgList);
       if (nByteWrite != -1)
              putchar('\n'); //注2
       va_end(pArgList);

       return (nByteWrite == -1 ? -1 : nByteWrite + 1);
}

调用与printf()函数相同。
再给出一个用可变参数来求和,遗憾的在C,C++中无法确定传入的可变参数的个数(printf()中是通过扫描'%'个数来确实参数的个数的),因此要么就要指定个数,要么在参数的最后要设置哨兵数值:
设置哨兵数值:

const int GUARDNUMBER = 0; //哨兵标识
//变参参数的个数无法确定,在printf()中是通过扫描'%'个数,在这通过设置哨兵标识来确定变参参数的终止
int MySum(int i, ...)
{
       int sum = i;
       va_list argptr;

       va_start(argptr, i);
       while ((i = va_arg(argptr, int)) != GUARDNUMBER)
              sum += i;
       va_end(argptr);

       return sum;
}

可以这样的调用:   printf("%d\n", MySum(1, 3, 5, 7, 9, 0));
但不可以直接传入一个0:   printf("%d\n", MySum(0)); //error
指定个数:

int MySum(int nCount, ...)
{
       if (nCount <= 0)
              return 0;

       int sum = 0;
       va_list argptr;

       va_start(argptr, nCount);
       for (int i = 0; i < nCount; i++)
              sum += va_arg(argptr, int);
       va_end(argptr);

       return sum;
}

调用时第一个参数表示后面参数的个数如:
       printf("%d\n", MySum(5, 1, 3, 5, 7, 9));
       printf("%d\n", MySum(0));

代码所用的头文件:
#include <stdarg.h>
#include <stdio.h>

可变参数的使用方法远远不止上述几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的'%'符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃。

可变参数的原形理涉及到调用函数时参数的入栈问题,这个下次再开一篇进行专门的探讨。

注1.网上有不用vfprintf()自己解析参数来实现printf()的,但很少能将功能做到与printf()相近(实际上能完全熟悉printf()的人已经就不多,不信的话可以先看看《C陷阱与缺陷》了解printf()很多不太常用的参数,再去Microsoft Visual Studio\VC98\CRT\SRC中查看OUTPUT.C对printf()的实现)。

注2.如果输出单个字符 putchar(ch)会比printf(“%c”, ch)效率高的多。在字符串不长的情况下,多次调用putchar()也会比调用printf(“%s\n”, szStr);的效率高。在函数大量调用时非常明显。

--结束END--

本文标题: C/C++中可变参数的用法详细解析

本文链接: https://www.lsjlt.com/news/172666.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C/C++中可变参数的用法详细解析
    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()...
    99+
    2022-11-15
    C 可变参数
  • C/C++宏定义的可变参数详细解析
    编写代码的过程中,经常会输出一些调试信息到屏幕上,一般会调用printf这类的函数。但是当调试解决之后,我们需要手工将这些地方删除或者注释掉。最近在看《Linux C编程一站式学习》...
    99+
    2022-11-15
    宏定义 可变参数
  • 浅析C/C++中的可变参数与默认参数
    千万要注意,C不支持默认参数 C/C++支持可变参数个数的函数定义,这一点与C/C++语言函数参数调用时入栈顺序有关,首先引用其他网友的一段文字,来描述函数调用,及参数入栈: ---...
    99+
    2022-11-15
    可变参数 默认参数
  • C#可变参数params示例详解
    目录前言示例探究本质扩展知识总结前言 前几天在群里看到群友写了一个基础框架,其中设计到关于同一个词语可以添加多个近义词的一个场景。当时群友的设计是类似字典的设计,直接添加k-v的操作...
    99+
    2022-11-13
  • C语言可变参数函数详解
    目录C语言可变参数函数总结C语言可变参数函数 C 语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。这种函数需要固定数量的强制参数(manda...
    99+
    2022-11-12
  • C/C++可变参数的使用
    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()...
    99+
    2022-11-15
    可变参数 C C++
  • C语言可变参数与内存管理超详细讲解
    目录概述动态分配内存重新调整内存的大小和释放内存概述 有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许...
    99+
    2023-01-02
    C语言可变参数 C语言内存管理
  • C语言可变长的参数列表详解
    C语言可变长的参数列表 C语言可创建接收参数个数不确定的函数。如常用的标准库函数printf就是一个接收参数个数可变的函数。函数printf至少要接收一个字符串作为它的第一个实参。但...
    99+
    2022-11-12
  • C语言的可变参数函数实现详解
    目录1、简介2、简单的使用方式总结1、简介 今天看到一个有趣的东西C语言的可变参数函数 众所周知,C语言的函数不能重载,那么你printf和scanf是怎么可以输入多个参数的 例如查...
    99+
    2022-11-12
  • C++详细分析讲解函数参数的扩展
    目录一、函数参数的默认值二、函数占位参数三、小结一、函数参数的默认值 C++ 中可以在函数声明时为参数提供一个默认值当函数调用时没有提供参数的值,则使用默认值参数的默认值必须在函数声...
    99+
    2022-11-13
  • C语言如何实现可变参数详解
    目录可变参数实现代码分析关键语句为什么内存地址难点优化总结可变参数 可变参数是指函数的参数的数据类型和数量都是不固定的。 printf函数的参数就是可变的。这个函数的原型是:int...
    99+
    2022-11-12
  • C#中的out参数、ref参数和params可变参数用法介绍
    out参数: out关键字 通过引用来传递参数,在定义方法和调用方法的时候都必须使用out关键字 简单来讲out可以用来返回多个参数类型。 static void Ma...
    99+
    2022-11-12
  • c++可变参数模板使用示例源码解析
    目录前言认识可变模板参数使用可变模板参数递归法特例化包拓展完美转发总结前言 我们知道,C++模板能力很强大,比起Java泛型这种语法糖来说,简直就是降维打击。而其中,可变参数模板,...
    99+
    2023-01-13
    c++可变参数模板 c++可变参数
  • C语言可变参数与函数参数的内存对齐详解
    目录什么是可变参数?使用可变参数函数参数的内存对齐总结什么是可变参数? 有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。 C 语言为这种情况提供了...
    99+
    2022-11-13
  • c++中new的三种用法详细解析
    一. 简介new有三种使用方式:plain new,nothrow new和placement new。(1)plain new顾名思义就是普通的new,就是我们惯常使用的new。在...
    99+
    2022-11-15
    new c++
  • C语言中extern详细用法解析
    在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。  1. extern修饰变量的声明。  举例来...
    99+
    2022-11-12
  • C语言可变参数列表的用法与深度剖析
    目录前言一、可变参数列表是什么?二、怎么用可变参数列表三、对于宏的深度剖析隐式类型转换对两个函数的重新认知总结前言 可变参数列表,使用起来像是数组,学习过函数栈帧的话可以发现实际上他...
    99+
    2022-11-13
  • C++11可变参数模板的参数转发举例分析
    本篇内容主要讲解“C++11可变参数模板的参数转发举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++11可变参数模板的参数转发举例分析”吧!实例很多软件系统都存在日志(log)功能,通...
    99+
    2023-06-19
  • 浅析C++可变参数模板的展开方式
    目录前言可变参数模板的定义参数包的展开递归函数方式展开逗号表达式展开enable_if方式展开折叠表达式展开(c++17)总结前言 可变参数模板(variadic templates...
    99+
    2022-11-13
  • C语言中的可变参数怎么使用
    今天小编给大家分享一下C语言中的可变参数怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。一、什么是可变参数我们在C语言...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作