结合print讲解va_start和va_end使用

1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表

void foo(...);
void foo(parm_list,...);
这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它。


2.函数参数的传递原理

  函数参数是以数据结构:栈的形式存取,从右至左入栈。

  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);
  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
  下面是 <stdarg.h> 里面重要的几个宏定义如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type ); 
void va_end ( va_list ap ); 
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。

 例子:

  1. #include <stdio.h>  
  2. #include <stdarg.h>  
  3.   
  4.   
  5. //va_start(arg,format),初始化参数指针arg,将函数参数format右边第一个参数地址赋值给arg  
  6. //format必须是一个参数的指针,所以,此种类型函数至少要有一个普通的参数,   
  7. //从而提供给va_start ,这样va_start才能找到可变参数在栈上的位置。   
  8. //va_arg(arg,char),获得arg指向参数的值,同时使arg指向下一个参数,char用来指名当前参数型  
  9. //va_end 在有些实现中可能会把arg改成无效值,这里,是把arg指针指向了 NULL,避免出现野指针   
  10.   
  11.   
  12. void print(const char *format, ...)  
  13. {  
  14.     va_list arg;  
  15.     va_start(arg, format);  
  16.   
  17.     while (*format)  
  18.     {  
  19.         char ret = *format;  
  20.         if (ret == '%')  
  21.         {  
  22.             switch (*++format)  
  23.             {  
  24.             case 'c':  
  25.             {  
  26.                         char ch = va_arg(arg, char);  
  27.                         putchar(ch);  
  28.                         break;  
  29.             }  
  30.             case 's':  
  31.             {  
  32.                         char *pc = va_arg(arg, char *);  
  33.                         while (*pc)  
  34.                         {  
  35.                             putchar(*pc);  
  36.                             pc++;  
  37.                         }  
  38.                         break;  
  39.             }  
  40.             default:  
  41.                 break;  
  42.             }  
  43.         }  
  44.         else  
  45.         {  
  46.             putchar(*format);  
  47.         }  
  48.         format++;  
  49.     }  
  50.     va_end(arg);  
  51. }  
  52. int main()  
  53. {  
  54.     print("%s %s %c%c%c%c%c!\n""welcome""to"'C''h''i''n''a');  
  55.     system("pause");  
  56.     return 0;  
  57. }
输出结果:

wKioL1ZQfY2hQOOGAAAZkTUFq_w029.png


printf输出类型简单介绍:

printf(“格式控制字符串”, 输出表列)
其中格式控制字符串用于指定输出格式。格式控制串可由格式字符串和非格式字符串两种组成。格式字符串是以%开头的字符串,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。如:

  • “%d”表示按十进制整型输出;
  • “%ld”表示按十进制长整型输出;
  • “%c”表示按字符型输出等。


类型字符用以表示输出数据的类型,其格式符和意义如下表所示:
格式字符 意义
d以十进制形式输出带符号整数(正数不输出符号)
o以八进制形式输出无符号整数(不输出前缀0)
x,X以十六进制形式输出无符号整数(不输出前缀Ox)
u以十进制形式输出无符号整数
f以小数形式输出单、双精度实数
e,E以指数形式输出单、双精度实数
g,G以%f或%e中较短的输出宽度输出单、双精度实数
c输出单个字符
s输出字符串




参考资料:

http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html

http://blog.csdn.net/iynu17/article/details/51588199

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值