一乐电子

 找回密码
 请使用微信账号登录和注册会员

QQ登录

只需一步,快速开始

微信扫码登录

手机号码,快捷登录

手机号码,快捷登录

搜索
查看: 5648|回复: 3

C语言中可变参数的函数

[复制链接]
发表于 2019-3-14 18:17 | 显示全部楼层 |阅读模式
【转】C语言中可变参数的函数(三个点,“...”) C语言中可变参数的函数(三个点,“...”)
  本文主要介绍va_start和va_end的使用及原理。
  在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解。
  介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理:

9 z" G; C/ [1 F. L
1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
  void foo(...);, ^# ?4 o: ?3 j
  void foo(parm_list,...);1 ~% `3 U$ r! z1 B/ W, A( e" k- U
  这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它。
9 q$ `: X8 L  L0 x' w  N3 R$ |% V+ c1 x3 M4 ?5 \- R+ h
2.函数参数的传递原理
  函数参数是以数据结构:栈的形式存取,从右至左入栈。
  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:) [# N9 X* g1 C; e/ m2 A
    void func(int x, float y, char z);
' C4 R; Z$ `6 o5 P1 K& `$ q; N  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
6 G4 P3 K4 ?! ]  下面是 <stdarg.h> 里面重要的几个宏定义如下:2 \( K" n6 ?" W9 a+ X1 B# w5 @
    typedef char* va_list;
* a, ?) p& ^( y- E    void va_start ( va_list ap, prev_param ); /* ANSI version */- W7 C8 ^5 D# h$ L
    type va_arg ( va_list ap, type ); 7 f& T  {- R# g
    void va_end ( va_list ap ); ' a* s6 b% _) [6 K7 O. O, E
  va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
! d. t1 ?( @, E  <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);$ e# y  w- f% z1 e; l. T
  <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
# i: w' ]2 X- E+ b  <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;5 R; J5 v0 |5 d1 P3 T6 q
  <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
7 L8 ?2 d% }7 C3 O5 X' `  例如 int max(int n, ...); 其函数内部应该如此实现:4 i1 {( X! w& v" K! P
  #include <iostream.h> $ Y, \: m2 e3 q
  void fun(int a, ...)
5 a+ E6 \4 C* |) R  K0 o  { 0 o" I( V0 ]" o$ B
    int *temp = &a;9 q2 i; M# T% k: a) U5 V' y/ J
    temp++;  //评论中反馈有问题,待改中- u* j$ H  ?  G! X* w
    for (int i = 0; i < a; ++i)
; `: J- i3 s& d( F/ `5 q5 K$ U    { / f+ a! A5 V( E! b. q5 ?8 X4 }
      cout << *temp << endl; ; T8 p& t6 x: `# J- ^
      temp++;
2 A3 \8 i: A) h1 O    }
" u/ Z! y. q7 ~  }
# [& @7 I, I2 q% H  int main() ; H2 O! a9 I" h# \  j4 N
  {
# y& c. R7 F3 ]* l  P* I    int a = 1;
# _+ ?8 G3 g" z    int b = 2; 9 k: y+ m# J6 [- R4 w
    int c = 3; 4 t0 S- f* R: E% X! n) R) ~
    int d = 4;
  M  o8 \+ Q! \. [! o( B    fun(4, a, b, c, d);
, k* V- X6 H5 G; O2 m5 z: B. ]    system("pause"); " \: l* c8 M$ R1 w( U8 G7 d
    return 0;
( F! ~) j- e, f: z- d  }
  Output:: 6 L, d- ?( [) m5 P" ]
  1
& z) s; @: A1 F- |( s  C  2 ) [4 n% ]% Y& a0 f3 G6 m) L
  3 . a; U7 W" Q3 `! x0 I  j6 n% p
  4
3:获取省略号指定的参数0 u! M' ]7 b2 i5 Q' b( M" b
  在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。像这段代码: 9 q- h% o- P4 u" O
  void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)
/ g. i7 L& x3 V. L9 u  {
5 i% d$ u8 S) s8 ?- G  ~% R2 H; i4 Z    va_list args;
1 H- e8 q+ `5 Q8 u8 E    va_start(args, pszFormat); //一定要“...”之前的那个参数/ Q. v: Z) N# M! P- n/ F0 Y
    _vsnprintf(pszDest, DestLen, pszFormat, args);
/ O2 V& R* z% d5 T1 H  _: P7 v% @9 n    va_end(args);
1 V( j, ?% }9 ^) |  }
  F+ q9 d; q" g# ?6 L 4.演示如何使用参数个数可变的函数,采用ANSI标准形式   h! z: }* Y# N- x: Q" Y5 L; W; Q- [
  #include 〈stdio.h〉 ; P1 C- I0 t8 {! G. a3 _$ ~
  #include 〈string.h〉 ' w" p: e" W5 `& [% o$ R
  #include 〈stdarg.h〉 ' m! O/ O, w: b8 I+ N4 ?9 w
  /*函数原型声明,至少需要一个确定的参数,注意括号内的省略号*/
- r2 o! O% B4 F, c4 `' N  int demo( char, ... );
$ B( \+ c9 {, r. C1 e  void main( void ) 5 o3 D& ~) |4 M+ u8 m
  { ! E. O7 ^5 G$ @5 f7 M" C
      demo("DEMO", "This", "is", "a", "demo!", "");
" a3 d- M" @9 B. G  }
" ^& X- H" x/ w% V  /*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/
3 |8 ^2 [9 r  q+ ~* O  int demo( char msg, ... )
9 e9 {% y$ a" U  {
" k; \# E1 x% l$ a/ |$ H. q, N         /*定义保存函数参数的结构*/
/ m. @6 X5 Z3 \0 S      va_list argp; 0 r1 Q6 T; ^' O/ h+ r
      int argno = 0; ' x( m8 C& h3 Z: L. t0 Z1 a- a
      char para; 7 X' [, l# z$ o, f3 G! f' @- q# f
      /*argp指向传入的第一个可选参数,msg是最后一个确定的参数*/ # W; L5 D7 d8 r7 L( B8 U1 d
      va_start( argp, msg ); ' L, Y0 h  c  y9 h! f% ]" Z% P
      while (1) 5 H0 X- `7 i7 e: x9 C
         {
+ X6 D: T3 t" x) b% u/ e            para = va_arg( argp, char);
- h* S( w7 M; {- h7 n* [- T: Y              if ( strcmp( para, "") == 0 )
% ^0 A) C$ c/ ~$ {9 a/ L$ v9 y                   break;
% Q1 @4 Y2 J) q              printf("Parameter #%d is: %s\n", argno, para); ; T& Q- k! t3 E! {3 k7 K
              argno++; 8 N. m7 U) o& ?# L* P8 C1 F
    }
3 Q, O8 C# u) u6 F9 d- d: n! a! k: ?    va_end( argp );
& ~* w* |8 ]# T1 O4 o$ }/ t/ B    /*将argp置为NULL*/' I" Y8 z' _( i" P
    return 0;
7 b5 ]% _/ t& W4 w' t/ W7 Y! z  }
8 F; j& w# v( I' H% G. X9 _  u
4 M% E$ @, `2 P" t% p
如由不对的地方,非常欢迎给予指导!
" w8 x  [/ O6 j! s( [
  ?! `4 @1 I6 m% w1 ]$ ^

' C  a1 w! Q! D
+ w4 Y  x) S% X: ]+ D
7 B9 [- j( |- w1 T+ |. o
 楼主| 发表于 2019-3-14 18:17 | 显示全部楼层
回复

使用道具 举报

发表于 2019-3-15 14:45 | 显示全部楼层
已经看不懂了耶
回复

使用道具 举报

发表于 2019-6-6 14:33 来自手机 | 显示全部楼层
看不懂,学习学习
回复

使用道具 举报

本版积分规则

QQ|一淘宝店|手机版|商店|一乐电子 ( 粤ICP备09076165号 ) 公安备案粤公网安备 44522102000183号

GMT+8, 2026-7-13 11:42 , Processed in 0.031714 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表