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

写在前面

在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_BEGIN
		this->_Getal().construct(
			_STD addressof(this->_Myval(_Pnode)),
				_STD forward<_Valty>(_Val)...);
		_CATCH_ALL
		this->_Getal().deallocate(_Pnode, 1);
		_RERAISE;
		_CATCH_END

		return (_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++的类成员函数的调用规则。详细情况见下表:

image

                    表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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,构造函数的参数列表用于指定在创建类对象时需要提供的参数。构造函数参数位于构造函数的声明和定义中的括号内,用逗号分隔每个参数。 构造函数参数列表的使用可以有以下几种情况: 1. 默认构造函数:如果没有在类中定义任何构造函数,则编译器会自动生成一个默认构造函数。默认构造函数不接受任何参数,因此参数列表为空。 ```cpp class MyClass { public: MyClass() { // 默认构造函数的定义 } }; ``` 2. 带参数的构造函数:可以在构造函数参数列表中指定一个或多个参数,以接受在创建对象时传递给构造函数的值。 ```cpp class MyClass { public: MyClass(int value) { // 带一个整数类型参数的构造函数的定义 } MyClass(int value1, int value2) { // 带两个整数类型参数的构造函数的定义 } }; ``` 3. 初始化成员量:在构造函数参数列表中,可以使用成员初始化列表来初始化类的成员量。 ```cpp class MyClass { private: int myValue; public: MyClass(int value) : myValue(value) { // 使用成员初始化列表初始化 myValue 成员量 } }; ``` 在上述示例中,构造函数 `MyClass(int value)` 使用成员初始化列表将传入的 `value` 参数赋值给成员量 `myValue`。 通过构造函数参数列表,可以在创建对象时传递初始值,并用于对类的成员量进行初始化。这样可以确保对象在创建时具有正确的初始状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值