2019独角兽企业重金招聘Python工程师标准>>>

写在前面

在C++语言中,有两个三个(???)地方用到了“..."这个符号,分别是:

  1. 变长参数列表。下面用讲到。
  2. 异常处理。用于表示捕获所有异常。
  3. 今天发现在stl中有下面的写法存在。有关这个问题可以参考

省略号和可变参数模板 - zray4u http://my.oschina.net/ray1421/blog/714159

    template<class... _Valty>_Nodeptr _Buynode(_Nodeptr _Next, _Nodeptr _Prev,_Valty&&... _Val){  // allocate a node and set links and value_Nodeptr _Pnode = this->_Buynode0(_Next, _Prev);_TRY_BEGINthis->_Getal().construct(_STD addressof(this->_Myval(_Pnode)),_STD forward<_Valty>(_Val)...);_CATCH_ALLthis->_Getal().deallocate(_Pnode, 1);_RERAISE;_CATCH_ENDreturn (_Pnode);}

参考资料

C语言函数可变参数详解 - ranpanf的专栏 - 博客频道 - CSDN.NET
http://blog.csdn.net/ranpanf/article/details/4693130

stdarg.h解析

stdarg.h是ANSI C 的标准头文件。它对可变参数的函数(vararg function)提供了支持。什么是可变参数的函数呢?举个例子:printf 与scanf就是。

stdarg.h对vararg function支持的关键是定义了几个非常有用的宏。

1.typedef char *va_list;

注解:本质是一个指向程序运行栈中某一个地址的指针(以后提到的栈都为程序运行栈)

2.#define __va_size(type) /

(((sizeof(type) + sizeof(long) - 1) / sizeof(long)) */ sizeof(long))

注解:计算某种数据类型的参数在栈中占有的空间。/是连接宏定义中的两行:如

#define sum(a,b)((a)+(b))

等效于:

#define sum(a,b)((/

a)+(b))

在IA32(32位机器程序或汇编程序)的程序中,PUSH和POP指令的操作数是4Bytes(DWORD)。如:char变量为一个字节,但入栈时需要一个DWORD。一个

sizeof为5Bytes的结构变量需要2个DWORD。

3#define va_start(ap, last) /

((ap) = (va_list)&(last) + __va_size(last))

注解:让直指指向第一个可变参数的首地址。

4. #define va_arg(ap, type) /

(*(type *)((ap) += __va_size(type), (ap) -/ __va_size(type)))

注解:这是我见过用逗号操作数最巧妙地例子。

5. #define va_end(ap) ((void)0)

注解:出现一个空值 标准库里将指向可变参数的指针重置为NULL。

为了详细解析,我先说一下几个要点。

1. 函数参数的传递机制:这里不是指值传递和引用传递。而是以从汇编(机器)语言程序员看参数传递。

我们知道有三种传递方式:寄存器传值,存储器传值,堆栈传值。C支持那种参数传值方式呢?C程序员怎么选择函数参数传递方式呢?在C语言的标准引入了一个概念:调用规则(calling convention),调用规则有:_cdecl,_stdcall,_fastcall,_thiscall(仅C++支持)等,请大家参阅MSDN获得详情。_cdecl 默认C/C++的调用规则,_stdcall win32API的调用规则,_fastcall一般不用,_thiscall C++的类成员函数的调用规则。详细情况见下表:

                    表1 函数调用规则详解

函数的调用过程:用户函数的调用是由程序运行栈来管理的,

标准库函数和系统调用一般也会用到栈,还有共享代码段(dll),陷入(软中断)等概念,我就不详细说了。现在只要知道用户函数是由程序运行栈管理的即可,栈的功能体现在三点:1.用栈保存函数的返回地址,2.用栈传递函数参数,3.在栈中创建局部变量。这里有个概念叫栈帧,它是指函数在调用时占用的栈中一部分连续的空间。函数的嵌套反映在栈中就是调用函数的栈帧的地址高于被调用者的栈帧的地址并顺序存放(假设栈是从高向低增长的)。函数的返回会伴随着他的栈帧的销毁,这也是为什么局部变量会在作用域之外没法应用。上面的表中有一列为“清栈”,想必你读到此处,它的含义你明白了:调用者清栈,是指被调用的函数参数保存在它调用者的栈帧中;而被调用者清栈,是指被调用的函数参数保存在它自己的栈帧中,通常用RET n指令返回。

现在我解析一段小程序:

#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>   //用于变参函数,变长参数列表
using namespace std;

//在可变长参数列表中,可变长参数的类型不需要与其左侧第一个非可变长参数一样。想想printf就了解了。

int  average(int first, ...)  
{
    int count = 0, sum = 0, i = first;
    va_list marker;                 //typedef char *va_list 可以仅将va_list看作一个指向一片内存空间的指针。
    va_start(marker, first);     // Initialize variable arguments 将maker指向变长参数列表中第一个参数的起始地址
    while (i != -1)
    {
        count++;
        sum += i;
        i = va_arg(marker, int);  
    }
    va_end(marker);              // Reset variable arguments 相当于将指针置为NULL
    return(sum ? (sum / count) : 0);
}

int main(){
    int a = 20;
    printf("Average(%d,%d)=%d\n", a, 400, average(a, 400, -1));
    return printf("Average(%d,%d,%d)=%d\n", 100, 20, 400, average(100, 20, 400, -1));
}

//这里是标准库中与变长参数相关的定义.

在标准库中所有带参数的宏最展开的语句中最外面都带一层括号。
下面这条语句的编写真的是很见功力。自己经过测试发现,short,int在此语句下,如果假定short-2,int-4,此语句求得的short占用4字节。int占用4字节。如果是long long类型,假定本身为8byte,求得的长度为8. 通过这一条宏考虑了所有字节对其的需要。虽然毫无可读性,应该是我能力不够,但真让人佩服。一条语句融合了位运算、sizeof运算符,并且内在地包含了对内存对齐等机制的理解,即一个类型或变量的sizeof长度与其在内存栈中“占据”的空间并不完全相同。这条语句不会是通过枚举各种可能的情况归纳出来的吧!!!???感觉比24点游戏难多了。哈哈
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

----------------------------------

因为变参函数要求最左边的参数必须给定,所以参数的起始地址由最左边的参数确定。另外貌似变参参数的类型要与最左边的参数类型相同,这里的+号后面的数,刚好指向变参列表的第一个参数。

#define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

-----------------------------------

下面这条宏对应上面的va_arg(marker,int)。不要认为是编写者抽风,先加了一个数,再减去一个相同的数,在语义上这两个量是不能抵销的,因为前面的一个量是具有累加效果的,使用的+=,属于赋值运算符的范畴,会修改ap的值,而后面的减法运算,只是将ap减去这个量的值作为整个表达式的值,并不改变ap的值。虽然这个宏第一次运行相当ap未变化,但之后的每次都向后偏移。之所以这样编写是将所有的情况统一起来。实际上如果ap在va_start中指向的是第一个显式参数的地址时,就不再需要后面的减法。  注意区别赋值运算与非赋值运算。

#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

补充-另外一种实现方式(更好理解)

“i = va_arg( marker, int);”:取函数当前可变参数,并且让指针指向下一个可变参数,该宏的定义如下:

#define va_arg(ap, type) /

(*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))

上面用到了续行符貌似续行符后面不能多加空格,会报错的,最好也不要在后面写注释,会不会报错没测试过。

这里用到了逗号运算符“,”。逗号运算符的定义是:expr1,expr2

先计算expr1,在计算expr2,整个表达式的结果为expr2的值。

显然逗号运算符的左侧操作数:(ap) += __va_size(type)是将修改指针移向下一个可变参数,但不会作为整个表达式的结果;而右侧操作数(ap) - __va_size(type)的不会修该指针的值,但会将恢复到指针被修改之前结果并将其作为整个表达式的结果(但要记住ap的值还是被向后改变了)。该结果为char型的变量的地址值,经过强制类型转化和取指针指向的值,终于得到了type类型的可变参数。

另外值得指出的是,对于变参函数,函数调用方式使用的是__cdecl,参数是从右到左入栈,即本例中first在栈顶,而栈位于内存的高端,栈顶的地址小于栈底的地址。所以每次访问要将相应的地址加上一个数,而不是减去一个数。
---------------------------

#define _crt_va_end(ap)      ( ap = (va_list)0 )

几点需要注意

1. 有可变参数的函数必须拥有非可变参数,并且非可变参数必须位于可变参数的左面(原文是前面,感觉用左边更好一些)。

2. 程序员必须自己控制可变参数的类型以及可变参数的数目。

转载于:https://my.oschina.net/ray1421/blog/699538

关于c++变长参数列表总结相关推荐

  1. c语言参数buf,C语言---变长参数列表---变长参数的传递

    5.4.2 变长参数的传递 上一节讲述了如何创建具有变长参数的函数和如何读取变长参数,其操作都在函数内完成,本节将讲述把变长参数列表整体作为参数传递给其他函数的方法. 变长参数传递的函数族如下: #i ...

  2. Spark UDF变长参数的二三事儿

    在复杂业务逻辑中,我们经常会用到Spark的UDF,当一个UDF需要传入多列的内容并进行处理时,UDF的传参该怎么做呢? 下面通过变长参数引出,逐一介绍三种可行方法以及一些不可行的尝试... 引子 变 ...

  3. 变长参数va_list va_start va_arg va_end

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

  4. c语言 宏 变长参数,科学网—C/C++中处理变长参数函数(Variadic Function)的几个宏 - 彭彬的博文...

    近日在模式中进行非线性方程组求解时遇到变长参数函数的问题,以前从来没有自己写过变长参数的函数,于是补了一下课,将近日对该小问题的学习和理解整理如下. 一.变长参数函数(variadic functio ...

  5. java 变长参数 知乎_变长参数探究

    前言 变长参数,指的是函数参数数量可变,或者说函数接受参数的数量可以不固定.实际上,我们最开始学C语言的时候,就用到了这样的函数:printf,它接受任意数量的参数,向终端格式化输出字符串.本文就来探 ...

  6. matlab 变长参数,变长参数函数的概念

    分享一个2015年华为笔试知识点:变长参数函数 变长参数的函数即参数个数可变.参数类型不定 的函数. 设计一个参数个数可变.参数类型不定的函数是可能的,最常见的例子是printf函数.scanf函数和 ...

  7. 变长参数模板 和 外部模板

    变长参数模板 解释 C++03只有固定模板参数.C++11 加入新的表示法,允许任意个数.任意类别的模板参数,不必在定义时将参数的个数固定. 变长模板.变长参数是依靠C++11新引入的参数包的机制实现 ...

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

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

  9. 使用基于Boost的预处理器元编程实现变长类型列表的参数化

    最近的工作中有这样一个需求: 使用宏自动生成类成员函数的声明和实现代码,成员函数的返回值类型不定,参数表可能为空,也可能有任意个任意类型的参数,例如: //函数名:foo0.返回值:int.参数类型表 ...

最新文章

  1. 微博爬虫“免登录”技巧详解及Java实现
  2. 使用带有用户名和密码的cURL?
  3. VMware Workstation 与 Device/Credential Guard 不兼容。在禁用 Device/Credenti
  4. sql如何获取全部的索引名称_这句简单的sql,如何加索引?颠覆了我多年的认知...
  5. C++实现链式存储二叉树
  6. C/C++ OpenCV图像的线性混合
  7. Android--Activity的跳转及Activity之间的数据传递
  8. COM 组件实现事件、通知
  9. 学术论文SCI、期刊、毕业设计中的图表专用软件
  10. 0x0000006B蓝屏解决方法
  11. hDC转PostScript转PDF
  12. 一个twitter puddles的算法实现
  13. qq邮箱mx服务器,QQ域名邮箱管理系统MX记录是什么?怎么添加设置?
  14. 数据库入门之字符匹配
  15. 问菩萨为何倒坐,叹众生不肯回头
  16. 顺时针打印矩阵(编程题讲解)
  17. 稀疏问题的解决——数据平滑 - yiyi_xuechen
  18. Java每天10道面试题,跟我走,offer有!(十)
  19. quasar ssr网站 利用github(giscus) 实现评论功能
  20. MapReduce 读取ORC格式文件

热门文章

  1. mysql实现综合排名_利用sql 进行综合排名
  2. 检测DTMF信号中的时间间隔
  3. 利用SeekFree的核心板调试MM32F3277的ISP功能
  4. 基于TPS28225功率MOS半桥电路测试
  5. TD8620手持数字特斯拉计一些基本的定标
  6. mysql自然连接和等值连接_mysql sql99语法 内连接等值连接
  7. 查询出来时间不对_2020年一级、二级建造师执业资格考试成绩可查询!
  8. android屏幕分享软件,ScreenStream(屏幕分享)
  9. java hello work_Java入门教程系列 – 第一个程序 “hello, world”
  10. linux 开启LACP 单端口,linux – 使用FTOS在Force10 S50N上PXE启动LACP主机