文章目录

  • C变参API
    • C变参API函数原型
    • C变参API实现源码
    • C变参API应用实例
      • C 变参函数缺点
  • C++变参实现
    • 方法
      • initializer_list 形参
      • 可变参数模板

C变参API

C变参API函数原型

va_list ap   /* 就等价于char *ap,定义一个指针 */va_start(ap, format)    /* 就是将指针ap移动到第一个可变参数的地方,即ap = ap + sizeif(char *) */va_arg(ap, 变量类型)     /* 返回当前ap指针指向的值,然后指向下一个可变参数 */va_end                   /* 将指针请为NULL */

C变参API实现源码

typedef char *  va_list;#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ap += _INTSIZEOF(t), *(*t)(ap - _INTSIZEOF(t))
#define va_end(ap)      ( ap = (va_list)0 )

C变参API应用实例

C 语言中,有时需要变参函数来完成特殊的功能,比如 C 标准库函数 printf() 和 scanf()。C 中提供了省略符…能够帮住 coder 完成变参函数的书写。变参函数原型申明如下:

type functionname(type param1,...);

变参函数至少要有一个固定参数,省略号“…”不可省略,比如 printf() 原型如下:

int printf(const char *format,...);

在头文件 stdarg.h 中定义了三个宏函数用于获取指定类型的实参:

void va_start(va_list arg,prev_param);
type    va_arg(va_list arg,type);
void    va_end(va_list arg);

va 在这里是 可变参数(variable argument)的意思,借助上面三个宏函数,变参函数的实现就变得相对简单很多。一般的变参函数处理过程:

(1)定义一个 va_list 变量设为 va;
(2)调用 va_start() 使得va存放变参函数的变参前的一个固定参数的地址;
(3)不断调用 va_arg() 使得va指向下一个实参;
(4)最后调用 va_end() 表示变参处理完成,将 va 置空。

原理就是:函数的参数在内存中从低地址向高地址依次存放。

看一个例子:模仿 pritnf() 的实现:

#include<iostream>
#include<stdarg.h>
#include<string.h>
using namespace std;  void func(char *c,...) {        int i=0;  double result=0;  va_list arg;        //va_list变量  va_start(arg,c);    //arg指向固定参数c  while(c[i]!='\0') {      if(c[i]=='%'&&c[i+1]=='d'){  printf("%d",va_arg(arg,int));  i++;  } else if(c[i]=='%'&&c[i+1]=='f') {  printf("%f",va_arg(arg,double));  i++;  } else {putchar(c[i]);} i++;  }  va_end(arg);
}  int main() {  int i=100;  double j=100.0;  printf("%d be equal %f\n",i,j);  func("%d be equal %f\n",i,j);system("pause");
}

C 变参函数缺点

  • 缺乏类型检查,容易出现不合理的强制类型转换。在获取实参时,是通过给定的类型进行获取,如果给定的类型与实际参数类型不符,则会出现类型安全性问题,容易导致获取实参失败。
  • 不支持自定义类型。自定义类型在程序中经常用到,比如我们要使用printf()来打印一个Student类型的对象的内容,该用什么格式字符串去指定实参类型,通过C提供的va_list,我们无法提取实参内容。
    鉴于以上两点,李健老师在其著作《编写高质量代码改善C++程序的150个建议》建议尽量不要使用 C 风格的变参函数。

C++变参实现

方法

为了编写能够处理不同数量实参的函数,C++11提供了两种主要方法:

  • 如果所有实参类型相同,可以传递标准库类型initializer_list;
  • 如果实参类型不同,可以编写一种特殊的函数,也就是所谓的可变参数模板。

initializer_list 形参

initializer_list 是 C++11 引入的一种标准库类模板,用于表示某种特定类型值的数组。initializer_list类型定义在同名的头文件中,它提供的操作有:

initializer_list<T> lst;           //默认初始化T类型的空列表。
initializer_list<T> lst{a,b,c,...};   //lst的元素是对应初始值的副本,且列表中的元素是const。
lst2(lst);      //拷贝构造一个initializer_list对象,不拷贝列表中的元素,与原始列表共享元素
lst2=lst;      //赋值,与原始列表共享元素。lst.size();       //列表中的元素数量。
lst.begin();    //返回指向lst中首元素的指针。
lst.end();      //返回lst中尾元素下一位置的指针。

和vector与list一样,initializer_list也是一种模板类型,定义initializer_list对象时必须指明列表中所含元素的类型。与vector和list不同之处在于initializer_list中的元素不可修改,并且拷贝构造和赋值时元素不会被拷贝。如此设计,让initializer_list更加符合参数通过指针传递,而非值传递,提高性能。所以C++11采用了initializer_list作为变参函数的形参,下面给出一个打印错误的变参函数:

void error_msg(initializer\_list<string> il) {for(auto beg=il.begin();beg!=il.end()) {cout<<*beg<<" ";}cout<<endl;
}

可变参数模板

简介:
目前大部分主流编译器的最新版本均支持了C++11标准(官方名为ISO/IEC14882:2011)大部分的语法特性,其中比较难理解的新语法特性可能要属可变参数模板(variadic template)了,GCC 4.6和Visual studio 2013都已经支持变参模板。可变参数模板就是一个接受可变数目参数的函数模板或类模板。可变数目的参数被称为参数包(parameter packet),这个也是新引入C++的概念,可以细分为两种参数包:
(1)模板参数包(template parameter packet),表示零个或多个模板参数。
(2)函数参数包(function parameter packet),表示零个或多个函数参数。

可变参数模板示例:
使用省略号来指明一个模板的参数包,在模板参数列表中,class…或typename…指出接下来的参数表示零个或多个类型参数;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数。声明一个带有可变参数个数的模板的语法如下所示:

// 1.申明可变参数的类模板
template<typename... Types> class tuple;
tuple<int, string> a;  // use it like this// 2.申明可变参数的函数模板
template<typename T,typename... Types> void foo(const T& t,const Types&... rest);
foo<int,float,double,string>(1,2.0,3.0,"lvlv");//use like this// 3.申明可变非类型参数的函数模板(可变非类型参数也可用于类模板)
template<typename T,unsigned... args> void foo(const T& t);
foo<string,1,2>("lvlv");//use like this

其中第一条示例中Types就是模板参数包,第二条示例中rest就是函数参数包,第三条示例中args就是非类型模板参数包。

参数包扩展:
现在我们知道parameter packet了,怎么在程序中真正具体地去处理打包进来的“任意个数”的参数呢?也就是说可变参数模板,我们如何进行参数包的扩展,获取传入的参数包中的每一个实参呢?对于一个参数包,可以通过运算符sizeof…来获取参数包中的参数个数,比如:

template<typename... Types> void g(Types... args) {cout<<sizeof...(Types)<<endl;  //类型参数数目cout<<sizeof...(args)<<endl;   //函数参数数目
}

我们能够对参数包唯一能做的事情就是对其进行扩展,扩展一个包就是将它分解为构成的元素,通过在参数包的右边放置一个省略号来触发扩展操作,例如:

template<typename T,typename... Types> ostream& print(ostream& os,const T& t,const Types&... rest) {os<<t<<",";return print(os,rest...);
}

上面的示例代码中,存在两种包扩展操作:
(1)const Types&… rest表示模板参数包的扩展,为print函数生成形参列表;
(2)对print的调用中rest…表示函数参数包的扩展,为print调用生成实参列表。

可变参数函数实例:
可变参数函数通常以递归的方式来获取参数包的每一个参数。第一步调用处理包中的第一个实参,然后用剩余实参调用自身。最后,定义一个非可变参数的同名函数模板来终止递归。我们以自定义的print函数为例,实现如下:

#include <iostream>
using namespace std;template<typename T> ostream& print(ostream& os,const T& t){os<<t<<endl;  //包中最后一个元素之后打印换行符
}template<typename T,typename... Types> ostream& print(ostream& os,const T& t,const Types&... rest){os<<t<<",";  //打印第一个实参print(os,rest...);  //递归调用,打印其他实参
}int main(){print(cout,10,123.0,"lvlv",1); //例1print(cout,1,"lvlv0","lvlv1");
}

程序输出:

10,123,lvlv,1
1,lvlv0,lvlv1

上面递归调用print,以例1为例,执行的过程如下:

前三个调用只能与可变参数版本的print匹配,非变参版本是不可行的,因为这三个调用要传递两个以上实参,非可变参数的print只接受两个实参。对于最后一次递归调用print(cout,1),两个版本的print都可以,因为这个调用传递两个实参,第一个实参的类型为ostream&,另一个是const T&参数。但是由于非可变参数模板比可变参数模板更加特例化,因此编译器选择非可变参数版本。

可变参C API va_list,va_start,va_arg_va_end以及c++可变参模板相关推荐

  1. 对va_list; va_start ; va_end ;vsprintf理解(转)

    以下为转载内容: 1 int printf(const char* fmt, ...) 2 { 3 va_list args; 4 int i; 5 //1.将变参转化为字符串 6 va_start( ...

  2. 变长参数va_list va_start va_arg va_end

    对于int printf(const char *format, ...);这种变长参数,需要使用va_list va_start va_end va_arg来访问参数. 下面是一个tutorials ...

  3. va_list/va_start/va_arg/va_end深入分析

    va_list/va_start/va_arg/va_end这几个宏,都是用于函数的可变参数的. 我们来看看在vs2008中,它们是怎么定义的: 1: ///stdarg.h 2: #define v ...

  4. va_list/va_start/va_end的使用

    va_list 键入以保存有关变量参数的信息 va_start 初始化变量参数列表 初始化ap以检索参数paramN后面的附加参数. 调用va_start的函数在返回之前也应调用va_end. 参数不 ...

  5. vsnprintf va_list va_start va_end

    1.函数原型: int vsnprintf(char *str, size_t size, const char *format, va_list ap); 某度百科: _vsnprintf是C语言库 ...

  6. va_list va_start va_end的使用

    <pre name="code" class="cpp" style="color: rgb(51, 51, 51); white-space: ...

  7. C语言使用函数参数传递中的省略号:va_list, va_start, va_arg, va_end

    首先要处理这种省略号的参数的话,需要包含头文件#include <stdarg.h>,然后利用下面的函数对"..."省略号变量进行处理. va_list arg; ty ...

  8. 如何获取函数的变长参数(va_list, va_start, va_arg, va_end)

    最近在花时间研读C++. 函数这章讲到了函数的变长参数(ellipsis...),但是primer中讲得比较浅,提到了怎么声明怎么调用,但是没有写明在函数内部是如何获取变长的参数的. 1)省略号(el ...

  9. va_list(),va_start()

    (一)写一个简单的可变参数的C函数 下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的 C函数要在程序中用到以下这些宏: void va_start( va_list arg_ptr, pr ...

最新文章

  1. 一行命令装下所有「炼丹」工具及依赖项,就靠这个免费软件源了|教程
  2. linux上安装spark_hadoop_java_scala
  3. 15 MM配置-BP业务伙伴-定义供应商主记录的编号范围
  4. 分布式、微服务、云架构
  5. 2.1.3 JavaScript代码书写规则
  6. idea将项目打包(jar包/war包)
  7. java 通过TCP\UDP 协议实现多人聊天,点对点,文件传送-----分服务器端和客户端...
  8. cannot import name 'StrictRedis' from 'redis'
  9. DM8 Out of space,错误码 code = -523问题解决
  10. 给宝宝做一个cocos免费游戏-故事和开始界面
  11. 《晚明》小说各战役配图
  12. Win8 无法连接打网络打印机(HP LaserJ1010)
  13. 互联网/移动互联网小团队创业
  14. [PHP] 新浪企业邮箱登录功能难点梳理
  15. codeforce Zebras(思维 + 模拟)
  16. 软著申请个人实名认证流程
  17. 对SendMessage与PostMessage的理解
  18. Eclipse Plungins--Pydev
  19. 第四届高教杯计算机绘图教程,第十一届高教杯全国大学生先进成图技术与产品信息建模.PDF...
  20. 关于Android Framework渲染机制,你需要学习哪些?

热门文章

  1. 怎样关闭qq位置定位服务器,手机qq怎么关闭定位
  2. SitePoint播客#71:不断发展的互联网
  3. Linux中的setuid简介
  4. 近期尝试UR5和PhantomOmni的联动仿真出现的问题
  5. Flink standalone配置(血汗注意事项,哭唧唧)
  6. 小薛读论文04:预测、解决方案与滚动时域 (UTD24期刊MSOM重要综述)
  7. matlab中int函数的用法
  8. 自学php怎么找工作,php学多久可以找工作,PHP要自学多久才能找到工作
  9. 【Unity】Sprite Atlas功能讲解
  10. SEM竞价|这5个方面重要性你理解透没有?