c语言中可变参数函数设计方案.docx

上传人:b****2 文档编号:17228147 上传时间:2023-07-23 格式:DOCX 页数:15 大小:22.24KB
下载 相关 举报
c语言中可变参数函数设计方案.docx_第1页
第1页 / 共15页
c语言中可变参数函数设计方案.docx_第2页
第2页 / 共15页
c语言中可变参数函数设计方案.docx_第3页
第3页 / 共15页
c语言中可变参数函数设计方案.docx_第4页
第4页 / 共15页
c语言中可变参数函数设计方案.docx_第5页
第5页 / 共15页
c语言中可变参数函数设计方案.docx_第6页
第6页 / 共15页
c语言中可变参数函数设计方案.docx_第7页
第7页 / 共15页
c语言中可变参数函数设计方案.docx_第8页
第8页 / 共15页
c语言中可变参数函数设计方案.docx_第9页
第9页 / 共15页
c语言中可变参数函数设计方案.docx_第10页
第10页 / 共15页
c语言中可变参数函数设计方案.docx_第11页
第11页 / 共15页
c语言中可变参数函数设计方案.docx_第12页
第12页 / 共15页
c语言中可变参数函数设计方案.docx_第13页
第13页 / 共15页
c语言中可变参数函数设计方案.docx_第14页
第14页 / 共15页
c语言中可变参数函数设计方案.docx_第15页
第15页 / 共15页
亲,该文档总共15页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

c语言中可变参数函数设计方案.docx

《c语言中可变参数函数设计方案.docx》由会员分享,可在线阅读,更多相关《c语言中可变参数函数设计方案.docx(15页珍藏版)》请在冰点文库上搜索。

c语言中可变参数函数设计方案.docx

c语言中可变参数函数设计方案

c语言中可变参数函数的设计

c语言中可变参数函数的设计

c语言中可变参数函数的设计

-----最近想好好学学这个,先把网上搜集得资料贴上.

 

======================================================================

参数可变函数的实现(上)CSDNBlog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

此文献给如我一般还在探索C语言之路的朋友们。

注:

本文中测试程序的编译环境为win2000和VC6.0

缘起:

作为一个程序员,我没有写过参数可变的函数,我相信大部分朋友也没有涉及过,或者我的境界层次太低了。

那么缘何我要去揭这一层面纱呢?

因为好奇!

我是个思维具有极大惰性的人,曾经识得参数可变函数,也懒得去深究,但是它的三点(函数声明时参数列表中的“…”)却深刻的映入了我的记忆里,而且是带着若干个闪耀的问号。

可是就在昨天,在拜读某君的高论时,它再一次出现了。

我的资质真的是不太够,因为某君在谈到它时只是给出了中关于它的宏定义,我想大概在高手眼里,点这一下就神会了吧。

可是他这么轻轻一点却使留在记忆里曾经的那几个问号无限的膨胀,以至于我这个又菜又懒的所谓程序员也萌生了莫大的好奇。

破题:

但凡所谓“实现”都是从没有到有的过程,但是我只是想去解惑它的实现,因为它原本就是好端端的正为成千上万的程序员们服务。

还是从我们熟悉的printf说起:

如果你是个C语言的程序员,无论你是初学者还是高高手,对于printf都不会陌生,甚至你已经用了无数次了。

我已经说过我是个有极大惰性的人,所以每次用printf都是照本宣科,规规矩矩的按教科书上说的做,从来没有问过一个为什么,这就是所谓的“熟视无睹”吧。

其实,printf函数是一个典型的参数可变的函数。

在保证它的第一个参数是字符串的条件下,你可以输任意数量任意合法类型的参数。

只要你在第一个字符串参数中使用了对应的格式化字符串,你就可以输出正确的值。

这难道不是件很有趣的事吗?

那它是怎么做到的?

1,首先,怎么得到参数的值。

对于一般的函数,我们可以通过参数对应在参数列表里的标识符来得到。

但是参数可变函数那些可变的参数是没有参数标识符的,它只有“…”,所以通过标识符来得到是不可能的,我们只有另辟途径。

我们知道函数调用时都会分配栈空间,而函数调用机制中的栈结构如下图所示:

|......|

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

|参数2|

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

|参数1|

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

|返回地址|

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

|调用函数运行状态|

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

可见,参数是连续存储在栈里面的,那么也就是说,我们只要得到可变参数的前一个参数的地址,就可以通过指针访问到那些可变参数。

但是怎么样得到可变参数的前一个参数的地址呢?

不知道你注意到没有,参数可变函数在可变参数之前必有一个参数是固定的,并使用标识符,而且通常被声明为char*类型,printf函数也不例外。

这样的话,我们就可以通过这个参数对应的标识符来得到地址,从而访问其他参数变得可能。

我们可以写一个测试程序来试一下:

#include

voidva_test(char*fmt,...);//参数可变的函数声明

voidmain()

{

inta=1,c=55;

charb='b';

va_test("",a,b,c);//用四个参数做测试

}

voidva_test(char*fmt,...)//参数可变的函数定义,注意第一个参数为char*fmt

{

char*p=NULL;

p=(char*)&fmt;//注意不是指向fmt,而是指向&fmt,并且强制转化为char*,以便一个一个字节访问

for(inti=0;i<16;i++)//16是通过计算的值(参数个数*4个字节),只是为了测试,暂且将就一下

{

printf("%.4d",*p);//输出p指针指向地址的值

p++;

}

}

编译运行的结果为

0056000000660000|0001000000000000|0098000000000000|0055000000000000

由运行结果可见,通过这样方式可以逐一获得可变参数的值。

至于为什么通常被声明为char*类型,我们慢慢看来。

2,怎样确定参数类型和数量

通过上述的方式,我们首先解决了取得可变参数值的问题,但是对于一个参数,值很重要,其类型同样举足轻重,而对于一个函数来讲参数个数也非常重要,否则就会产生了一系列的麻烦来。

通过访问存储参数的栈空间,我们并不能得到关于类型的任何信息和参数个数的任何信息。

我想你应该想到了——使用char*参数。

Printf函数就是这样实现的,它把后面的可变参数类型都放到了char*指向的字符数组里,并通过%来标识以便与其它的字符相区别,从而确定了参数类型也确定了参数个数。

其实,用何种方式来到达这样的效果取决于函数的实现。

比如说,定义一个函数,预知它的可变参数类型都是int,那么固定参数完全可以用int类型来替换char*类型,因为只要得到参数个数就可以了。

3,言归正传

我想到了这里,大概的轮廓已经呈现出来了。

本来想就此作罢的(我的惰性使然),但是一想到如果不具实用性便可能是一堆废物,枉费我打了这么些字,决定还是继续下去。

我是比较抵制用那些不明所以的宏定义的,所以在上面的阐述里一点都没有涉及定义在的va(variable-argument)宏。

事实上,当时让我产生极大疑惑和好奇的正是这几个宏定义。

但是现在我们不得不要去和这些宏定义打打交道,毕竟我们在讨生计的时候还得用上他们,这也是我曰之为“言归正传”的理由。

好了,我们来看一下那些宏定义。

打开文件,找一下va_*的宏定义,发现不单单只有一组,但是在各组定义前都会有宏编译。

宏编译指示的是不同硬件平台和编译器下用怎样的va宏定义。

比较一下,不同之处主要在偏移量的计算上。

我们还是拿个典型又熟悉的——X86的相关宏定义:

1)typedefchar*va_list;

2)#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

3)#defineva_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))

4)#defineva_arg(ap,t)(*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))

5)#defineva_end(ap)(ap=(va_list)0)

我们逐一看来:

第一个我想不必说了,类型定义罢了。

第二个是颇有些来头的,我们也不得不搞懂它,因为后面的两个关键的宏定义都用到了。

不知道你够不够细心,有没有发现在上面的测试程序中,第二个可变参数明明是char类型,可是在输出结果中占了4个byte。

难道所有的参数都会占4个byte的空间?

那如果是double类型的参数,且不是会丢失数据!

如果你不嫌麻烦的话,再去做个测试吧,在上面的测试程序中用一个double类型(长度为8byte)和一个longdouble类型(长度为10byte)做可变参数。

发现什么?

double类型占了8byte,而longdouble占了12byte。

好像都是4的整数倍哦。

不得不引出另一个概念了“对齐(alignment)”,所谓对齐,对Intel80x86机器来说就是要求每个变量的地址都是sizeof(int)的倍数。

原来我们搞错了,char类型的参数只占了1byte,但是它后面的参数因为对齐的关系只能跳过3byte存储,而那3byte也就浪费掉了。

那为什么要对齐?

因为在对齐方式下,CPU的运行效率要快得多(举个例子吧,要说明的是下面的例子是我从网上摘录下来的,不记得出处了。

示例:

如下图,当一个long型数(如图中long1)在内存中的位置正好与内存的字边界对齐时,CPU存取这个数只需访问一次内存,而当一个long型数(如图中的long2)在内存中的位置跨越了字边界时,CPU存取这个数就需要多次访问内存,如i960cx访问这样的数需读内存三次(一个BYTE、一个SHORT、一个BYTE,由CPU的微代码执行,对软件透明),所以对齐方式下CPU的运行效率明显快多了。

18162432

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

|long1|long1|long1|long1|

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

||||long2|

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

|long2|long2|long2||

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

|....)。

好像扯得有点远来,但是有助于对_INTSIZEOF(n)的理解。

位操作对于我来说是玄的东东。

单个位运算还应付得来,而这样一个表达式摆在面前就晕了。

怎么办?

菜鸟自有菜的办法。

(待续)

 

Trackback:

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

C语言中的可变参数函数CSDNBlog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

第一篇

C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:

intprintf(constchar*format,...);

它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式:

printf("%d",i);

printf("%s",s);

printf("thenumberis%d,stringis:

%s",i,s);

一个简单的可变参数的C函数

先看例子程序。

该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定。

在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值。

函数代码如下:

//示例代码1:

可变参数函数的使用

#include"stdio.h"

#include"stdarg.h"

voidsimple_va_fun(intstart,...)

{

va_listarg_ptr;

intnArgValue=start;

intnArgCout="0";//可变参数的数目

va_start(arg_ptr,start);//以固定参数的地址为起点确定变参的内存起始地址。

do

{

++nArgCout;

printf("the%dtharg:

%d",nArgCout,nArgValue);//输出各参数的值

nArgValue=va_arg(arg_ptr,int);//得到下一个可变参数的值

}while(nArgValue!

=-1);

return;

}

intmain(intargc,char*argv[])

{

simple_va_fun(100,-1);

simple_va_fun(100,200,-1);

return0;

}

下面解释一下这些代码。

从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:

⑴由于在程序中将用到以下这些宏:

voidva_start(va_listarg_ptr,prev_param);

typeva_arg(va_listarg_ptr,type);

voidva_end(va_listarg_ptr);

va在这里是variable-argument(可变参数)的意思。

这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件。

⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。

⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数。

⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。

⑸设定结束条件,这里的条件就是判断参数值是否为-1。

注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。

至于为什么它不会知道参数的数目,在看完这几个宏的内部实现机制后,自然就会明白。

第二篇

C语言之可变参数问题

C语言中有一种长度不确定的参数,形如:

"…",它主要用在参数个数不确定的函数中,我们最容易想到的例子是printf函数。

  原型:

  intprintf(constchar*format[,argument]...);

  使用例:

  printf("Enjoyyourselfeveryday!

\n");

  printf("Thevalueis%d!

\n",value);

  这种可变参数可以说是C语言一个比较难理解的部分,这里会由几个问题引发一些对它的分析。

  注意:

在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任意数量的函数参数。

  问题:

printf的实现

  请问,如何自己实现printf函数,如何处理其中的可变参数问题?

答案与分析:

  在标准C语言中定义了一个头文件专门用来对付可变参数列表,它包含了一组宏,和一个va_list的typedef声明。

一个典型实现如下:

  typedefchar*va_list;

  #defineva_start(list)list=(char*)&va_alist

  #defineva_end(list)

  #defineva_arg(list,mode)\

  ((mode*)(list+=sizeof(mode)))[-1]

  自己实现printf:

  #include

  intprintf(char*format,…)

  {

  va_listap;

  va_start(ap,format);

  intn=vprintf(format,ap);

  va_end(ap);

  returnn;

  }

  问题:

运行时才确定的参数

  有没有办法写一个函数,这个函数参数的具体形式可以在运行时才确定?

  答案与分析:

  目前没有"正规"的解决办法,不过独门偏方倒是有一个,因为有一个函数已经给我们做出了这方面的榜样,那就是main(),它的原型是:

  intmain(intargc,char*argv[]);

函数的参数是argc和argv。

  深入想一下,"只能在运行时确定参数形式",也就是说你没办法从声明中看到所接受的参数,也即是参数根本就没有固定的形式。

常用的办法是你可以通过定义一个void*类型的参数,用它来指向实际的参数区,然后在函数中根据根据需要任意解释它们的含义。

这就是main函数中argv的含义,而argc,则用来表明实际的参数个数,这为我们使用提供了进一步的方便,当然,这个参数不是必需的。

  虽然参数没有固定形式,但我们必然要在函数中解析参数的意义,因此,理所当然会有一个要求,就是调用者和被调者之间要对参数区内容的格式,大小,有效性等所有方面达成一致,否则南辕北辙各说各话就惨了。

  问题:

可变长参数的传递

  有时候,需要编写一个函数,将它的可变长参数直接传递给另外的函数,请问,这个要求能否实现?

  答案与分析:

  目前,你尚无办法直接做到这一点,但是我们可以迂回前进,首先,我们定义被调用函数的参数为va_list类型,同时在调用函数中将可变长参数列表转换为va_list,这样就可以进行变长参数的传递了。

看如下所示:

  voidsubfunc(char*fmt,va_listargp)

  {

  ...

  arg=va_arg(fmt,argp);/*从argp中逐一取出所要的参数*/

  ...

  }

  voidmainfunc(char*fmt,...)

  {

  va_listargp;

  va_start(argp,fmt);/*将可变长参数转换为va_list*/

  subfunc(fmt,argp);/*将va_list传递给子函数*/

  va_end(argp);

  ...

  }

  问题:

可变长参数中类型为函数指针

  我想使用va_arg来提取出可变长参数中类型为函数指针的参数,结果却总是不正确,为什么?

  答案与分析:

  这个与va_arg的实现有关。

一个简单的、演示版的va_arg实现如下:

  #defineva_arg(argp,type)\

  (*(type*)(((argp)+=sizeof(type))-sizeof(type)))

  其中,argp的类型是char*。

  如果你想用va_arg从可变参数列表中提取出函数指针类型的参数,例如

  int(*)(),则va_arg(argp,int(*)())被扩展为:

  (*(int(*)()*)(((argp)+=sizeof(int(*)()))-sizeof(int(*)())))

  显然,(int(*)()*)是无意义的。

  解决这个问题的办法是将函数指针用typedef定义成一个独立的数据类型,例如:

  typedefint(*funcptr)();

  这时候再调用va_arg(argp,funcptr)将被扩展为:

  (*(funcptr*)(((argp)+=sizeof(funcptr))-sizeof(funcptr)))

  这样就可以通过编译检查了。

  问题:

可变长参数的获取

  有这样一个具有可变长参数的函数,其中有下列代码用来获取类型为float的实参:

  va_arg(argp,float);

  这样做可以吗?

  答案与分析:

  不可以。

在可变长参数中,应用的是"加宽"原则。

也就是float类型被扩展成double;char,short被扩展成int。

因此,如果你要去可变长参数列表中原来为float类型的参数,需要用va_arg(argp,double)。

对char和short类型的则用va_arg(argp,int)。

  问题:

定义可变长参数的一个限制

  为什么我的编译器不允许我定义如下的函数,也就是可变长参数,但是没有任何的固定参数?

  intf(...)

  {

  ...

  }

  答案与分析:

  不可以。

这是ANSIC所要求的,你至少得定义一个固定参数。

  这个参数将被传递给va_start(),然后用va_arg()和va_end()来确定所有实际调用时可变长参数的类型和值。

第一篇

C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:

intprintf(constchar*format,...);

它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式:

printf("%d",i);

printf("%s",s);

printf("thenumberis%d,stringis:

%s",i,s);

一个简单的可变参数的C函数

先看例子程序。

该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定。

在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值。

函数代码如下:

//示例代码1:

可变参数函数的使用

#include"stdio.h"

#include"stdarg.h"

voidsimple_va_fun(intstart,...)

{

va_listarg_ptr;

intnArgValue=start;

intnArgCout="0";//可变参数的数目

va_start(arg_ptr,start);//以固定参数的地址为起点确定变参的内存起始地址。

do

{

++nArgCout;

printf("the%dtharg:

%d",nArgCout,nArgValue);//输出各参数的值

nArgValue=va_arg(arg_ptr,int);//得到下一个可变参数的值

}while(nArgValue!

=-1);

return;

}

intmain(intargc,char*argv[])

{

simple_va_fun(100,-1);

simple_va_fun(100,200,-1);

return0;

}

下面解释一下这些代码。

从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:

⑴由于在程序中将用到以下这些宏:

voidva_start(va_listarg_ptr,prev_param);

typeva_arg(va_listarg_ptr,type);

voidva_end(va_listarg_ptr);

va

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2