变长参数------>大学不教你我来教

这个知识点在大学里一般都不讲,翻阅所有的C语言入门书籍也没有相关的内容,但你不要认为它就那么遥不可以及,其实还是比较简单,知识有一点技术而已,废话少说,

我们用的最多的C函数是哪个?毫无疑问,是printf。但是你看过printf的声明式吗,那是相当诡异。随便拿一本带C库函数参考的书,可查到如下结果:
             int printf(const char *format, ...);
那三个连续的点就代表大于或等于0个参数,再加上前面的format参数,所以printf函数至少要接受一个字符串,后面就随便了。但是这是如何实现的呢,不急,首先必须要了解C语言函数参数传递的机制。

C语言函数参数传递机制

我们知道,每个程序都有个用户栈,所有的函数调用都要使用它,比如被调用函数的实参及局部变量都要存在用户栈中,所以每个函数都有个栈帧。下面我用两个函数来说明这种调用机制:


int main(void)                       int add(int param1, int param2)
{                                     {
    int a, b, c;                            int d;
    a = 2;                                  d = param1 + param2;
    b = 3;                                   return param1+param2;
    c = add(a,b);                      }
    return 0;
}
main函数在调用add函数时,main负责把b,a及eip(即返回地址)按顺序压栈,注意栈是向下增长的。然后执行call指令调用add函数。(ps: 其实在返回地址和局部变量d之间函数add还会压入一些寄存器,但是这对我们理解变长参数没有影响,总之要记住参数是自右向左压栈的)
                               

如果上面说的你看不懂,那么jordanli22叫你记住:add(a,b);在前面的参数a的地址最小,&b比&a大(&b)= (char *)&a +sizeof(a);

处理变长参数的宏定义
处理变长参数说白了就是怎样获取那些在声明中没明说的参数,比如在printf("%d equals to %d.", a, b)中,第一个字符串参数可直接通过format取得,但a和b呢?这就要需要对参数传递的理解了。主函数调用printf时肯定是先压入b,再压入a,最后压入"%d equals to %d"的地址。
现在我们来获取a的地址,很简单,当然是 (char*)&format+sizeof(const char*)。其中&format是参数format在栈中的地址,而a正好format上面。

OK, 现在我们可以系统地讲解处理变长参数的宏了。

实际上,C语言的大侠已经设计好了一个处理可变参数的头文件供我们使用了。#include <stdarg.h>

请打开这个头文件,我将帮大家解读一下这个头文件的使用方法。
在头文件stdarg.h中先定义个类型 va_list,它是专门用来取可变参数的,如果要对付可变参数首先要定义一个va_list类型的变量ap:
            typedef char* va_list;
因为在获取参数地址时必须知道它前一个参数的数据类型大小,所以要定义_INTSIZEOF(n),它的作用和sizeof类似,不过__va_sizeof(char),__va_sizeof(short)的值都是4:

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

举例如下:

char       sizeof(char) = 1      _INTSIZEOF(char) = 4 

short int  sizeof(short int) = 2   _INTSIZEOF(short int) = 4 

int    sizeof(int) = 4    _INTSIZEOF(int) = 4 

long int   sizeof(long int) = 4   _INTSIZEOF(long int) = 4 

float      sizeof(float) = 4  _INTSIZEOF(float) = 4 

double    sizeof(float) = 8     _INTSIZEOF(double) = 8


然后定义va_start获取第一个可变参数的地址:


 #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )


va_arg用于获取ap当前指向的参数的值,并顺带把va_list移向下一个参数
         

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


当用完ap时,用va_end将其赋值为空,也就是0
      
#define va_end(ap)      ( ap = (va_list)0 )


处理变长参数的一般框架
如果我们自己实现printf函数,代码的形式大概如下
int chparamf(const char *format, ...)
{
    va_list ap;
    va_start(ap,format);
    循环使用 va_arg(ap,你要取的参数的数据类型)
    va_end(ap);
}

举个简单的例子:无穷个数(double类型)相加

#include <stdio.h>   

#include <stdarg.h>

#define  PN printf("\n")

double  add(double cont,...)//cont表示加数的个数

{

int i = 0;

double sum = 0;

va_list ap;//char * ap;

va_start(ap,cont);//ap指向了cont后的变量的首地址

for(i = 0; i < cont; i++)

{

sum = sum + va_arg(ap,double);//取ap指向的地址的值,以double的格式来取,同 //时ap指向了下一个变量的首地址

}

va_end(ap);//赋值为空

return sum ;

}

int main()  

double sum1 = add(3, 4.0, 5.0, 6.2);//如果这里写4系统默认以int来存储,int是4个字节, //但上面函数已经用double来取这个变量了,所以必须 //写4.0,使系统默认以double来存储,也可以以下面

//这种方式存储

printf("%lf",sum1);

PN;

double a,b,c,d;

a = 3, b = 4, c = 5, d = 6.1;//这时候4会被系统转化为4.0

sum1 = add(a, b, c, d);

    printf("%lf",sum1);

PN;

return 0;

}  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值