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

所属分类: 软件编程 / C 语言 阅读数: 96
收藏 0 赞 0 分享

由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载。对这种情况,提出了指针参数来解决问题。

如printf()函数,其原型为:
int   printf(   const   char*   format,   ...);

它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法:  
printf( "%d ",i);  
printf( "%s ",s);  
printf( "the   number   is   %d   ,string   is:%s ",   i,   s);  

如何实现其功能?

我们需要以下几个宏定义:

(1)va_list
定义了一个指针arg_ptr, 用于指示可选的参数.

(2)va_start(arg_ptr, argN)
使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,argN是位于第一个可选参数之前的固定参数, 或者说最后一个固定参数.如有一va函数的声明是void va_test(char a, char b, char c, ...), 则它的固定参数依次是a,b,c, 最后一个固定参数argN为c, 因此就是va_start(arg_ptr, c).

(3)va_arg(arg_ptr, type)
返回参数列表中指针arg_ptr所指的参数, 返回类型为type. 并使指针arg_ptr指向参数列表中下一个参数.返回的是可选参数, 不包括固定参数.

(4)va_end(arg_ptr)
清空参数列表, 并置参数指针arg_ptr无效.

(注:va在这里是variable-argument(可变参数)的意思.   这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件)

也需你现在还是不能理解,别着急,现在从一个实例着手.定义这么一个函数,函数的第一个参数是固定的,其余参数是可变的。定义为:
void   simple_va_fun(int i,...); 其代码为:

复制代码 代码如下:

#include <iostream>
#include <stdarg.h>
using namespace std;
void simple_va_fun(int i,...);

int main(int argc,char *argv[])
{
    simple_va_fun(100);  
    simple_va_fun(100,200);
    simple_va_fun(100,200,'a');
    return 0;
}

void simple_va_fun(int i,...)  
{  
    va_list   arg_ptr;   //定义可变参数指针
    va_start(arg_ptr,i);   // i为最后一个固定参数
    int j=va_arg(arg_ptr,int);   //返回第一个可变参数,类型为int
    char c=va_arg(arg_ptr,char);   //返回第二个可变参数,类型为char
    va_end(arg_ptr);        //  清空参数指针
    printf( "%d %d %c\n",i,j,c);  
    return;  
}


代码运行解释:  
(1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.

(2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.

(3)然后用va_arg返回第一个可变的参数,并赋值给整数j。va_arg的第二个参数是你要返回的参数的类型,这里是int型.  返回第一个可变参数后arg_ptr指向第二个可变参数,用同样的方法返回并赋值给c,类型为char类型。

(4)最后用va_end宏结束可变参数的获取。

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

附加:

参数在堆栈中分布:
在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的. 总之,函数在堆栈中的分布情况是:地址从高到低,依次是:函数参数列表,函数返回地址,函数执行代码段. 堆栈中,各个函数的分布情况是倒序的.即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分.参数在堆栈中的分布情况如下:

最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数代码段

更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多