一乐电子

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

QQ登录

只需一步,快速开始

微信扫码登录

手机号码,快捷登录

手机号码,快捷登录

搜索
查看: 4928|回复: 3

C语言中可变参数的函数

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

& O9 v2 G" M4 [% f: l
1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
  void foo(...);/ P/ w3 u) v5 O! d3 C' d, i
  void foo(parm_list,...);
8 z$ }- X6 V8 `+ B) ^! t" K  这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它。/ I1 E% b5 a0 d- o* J, K3 d+ T5 |0 c

% f/ C3 m$ t" c% Q. m$ l8 u
2.函数参数的传递原理
  函数参数是以数据结构:栈的形式存取,从右至左入栈。
  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:" {$ ^% Y- k% t: G# R; F
    void func(int x, float y, char z);
$ D5 N) J- X  N; D5 t+ Y: B  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。) C/ f1 j: W6 m' I
  下面是 <stdarg.h> 里面重要的几个宏定义如下:
% q% m- l/ N, M( u% ]* Z$ z0 `9 L    typedef char* va_list;! _. \2 D* j/ I8 [3 [4 X0 E
    void va_start ( va_list ap, prev_param ); /* ANSI version */8 ^5 g3 O' R0 u8 m# G# l
    type va_arg ( va_list ap, type ); / D( M# ~$ c0 r, D$ Z- \
    void va_end ( va_list ap );
( P: u( r: d8 V, r1 s  va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
' s" e) [& k2 z  <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
3 B9 w4 I2 x$ B  <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;/ H. y8 B% R$ @0 F
  <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;$ ~2 P6 x- ^  I
  <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
/ ~* L+ n& _, c% }  例如 int max(int n, ...); 其函数内部应该如此实现:
$ a2 _% ^4 u9 |  w5 C$ R  #include <iostream.h>
; `5 o/ E* p3 r5 ]) k( ?; I: F  void fun(int a, ...) 2 E1 ~! x- S* K  ^  w: m  l
  { 5 g) g/ ?( t, p0 U9 ^+ Y0 j2 _
    int *temp = &a;4 S2 M2 z' p6 W; N4 t( s
    temp++;  //评论中反馈有问题,待改中
5 I: g2 v* }- x0 K
    for (int i = 0; i < a; ++i) . R( q$ F$ d9 c0 M2 v) [: D8 b. {8 F
    {
0 q( x; J3 q5 _      cout << *temp << endl;
$ G) c; d" J/ s      temp++; 9 \5 _* J# s% ^
    } 0 K& x% {! t  L( W2 X4 h4 M
  }
' B: g$ N( v- S2 `" s  int main()
4 |2 `, p4 k! y$ k2 h  {
& s- \0 P! k9 m    int a = 1; 8 ~+ s' ~0 e) s
    int b = 2; 6 \  L9 [; l5 z) m4 H
    int c = 3; & b3 l1 Q, v8 U2 E6 Z, R" M
    int d = 4;
  r6 F; |0 s& J( G    fun(4, a, b, c, d);
/ t4 N4 U, ^5 M    system("pause");
- d- G! [! G8 b* p    return 0; ) Q) a' \; F+ o( u
  }
  Output::
( R7 z4 H( m3 w* X4 d  1 + c! O1 N6 Y: v3 @3 e. O' J
  2 & y: [( e% Q! Q0 w
  3
$ X& [' S" c) X$ g' J9 W  4
3:获取省略号指定的参数
1 l4 s' K3 J6 v4 ^$ _  在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。像这段代码:
1 z# B: r. l- x* y, e6 y2 S0 x  void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...) 8 F& h2 m6 l5 K( h& D- N; d
  {
, R" u0 t0 x  ~1 J9 j3 V    va_list args; 7 D* w6 ^5 f. _2 h( E' n; q
    va_start(args, pszFormat); //一定要“...”之前的那个参数5 ^0 ^. t: M+ c+ N7 i6 Q. v# \
    _vsnprintf(pszDest, DestLen, pszFormat, args);
) C; _- D% {% J+ X! p6 H0 M    va_end(args); ) I5 r8 _% t' t+ B0 c$ n' t
  }1 S8 G" v1 X( h2 f( X: d6 m7 }( t5 }
4.演示如何使用参数个数可变的函数,采用ANSI标准形式 * a  G8 v( V( }, J7 y
  #include 〈stdio.h〉
4 L* S( [6 w# v+ f1 T2 z  #include 〈string.h〉 # _# I: D1 d; v/ e$ U( F; q
  #include 〈stdarg.h〉 ! e8 K" b, L0 s' [
  /*函数原型声明,至少需要一个确定的参数,注意括号内的省略号*/ ) J, h/ ^$ P( E/ X; A
  int demo( char, ... );
3 |$ F# p7 A6 P& J* `; g  void main( void ) : J. A2 ?, s) l* T/ _0 Q; w
  {
5 p8 w) B7 K5 W* F6 v2 z      demo("DEMO", "This", "is", "a", "demo!", ""); - @! p+ s4 z' J+ @/ _% [; L
  } ( a" x- D5 a0 R6 A8 j: a! {) k& D
  /*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/ ) s6 R2 u+ I8 E2 n
  int demo( char msg, ... ) 2 K+ ]; ]: v( d- N
  {
2 G+ e( C7 X0 P6 o         /*定义保存函数参数的结构*/
, I2 v# z" j& c- m      va_list argp;
# c; O/ K; K3 m      int argno = 0; , X) N* r" l) L! v" y" i
      char para; ; e: t+ h* }1 h6 F8 t! T# q
      /*argp指向传入的第一个可选参数,msg是最后一个确定的参数*/ 8 a" {4 [2 u& o0 m- u- ]
      va_start( argp, msg ); 3 R6 J' D0 c% ~5 w% z
      while (1) , n( y: S/ U5 u9 P; Y- z2 x
         { % @4 t! w. C% x+ _& B* f% u2 B% b8 p
            para = va_arg( argp, char); / m# A$ \. Y1 k: K+ P/ P* |
              if ( strcmp( para, "") == 0 )
$ x- |) `7 v$ d& n4 ~" _                   break;
5 J/ v  N7 Q& s8 C2 w& C              printf("Parameter #%d is: %s\n", argno, para); 4 H# B8 A0 l; H/ h: Y' z
              argno++; ) C* Z* p$ \: D. W' D
    }
9 M* a9 b) e2 j0 j; j2 V    va_end( argp );   X' I; F' o$ A
    /*将argp置为NULL*/' @/ J+ ?5 w0 d0 {# H! L3 z! E, D( H5 H
    return 0;
1 c& G5 D, q* J4 G, |4 l  }
1 C( r* R5 F3 Q. _) X: \8 z" r: x! ^) R8 p+ p+ O' ]
如由不对的地方,非常欢迎给予指导!
+ ?( j* h0 @# w
8 ?( h& T9 M" }& X2 Z7 U5 a

! G0 f, ~+ S4 M9 O- r, Y

8 r' c; L  }4 j& L$ F/ _8 [0 D& ^' W( ~  F
 楼主| 发表于 2019-3-14 18:17 | 显示全部楼层
回复

使用道具 举报

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

使用道具 举报

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

使用道具 举报

本版积分规则

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

GMT+8, 2025-10-27 06:49 , Processed in 0.030679 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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