一乐电子

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

QQ登录

只需一步,快速开始

微信扫码登录

手机号码,快捷登录

手机号码,快捷登录

搜索
查看: 5171|回复: 3

C语言中可变参数的函数

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

; l) O, t, a! c8 c# y
1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
  void foo(...);8 M# r5 r. W9 ]/ y
  void foo(parm_list,...);
& d4 j- n! Y9 w& v  这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它。/ g8 W  Q! K( {" {# z

8 s7 d# k4 j6 ?
2.函数参数的传递原理
  函数参数是以数据结构:栈的形式存取,从右至左入栈。
  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:9 E7 t, @  i/ V% |8 O4 Z
    void func(int x, float y, char z);
+ d% `7 g2 E! C4 r  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
. `2 z1 P  b5 Q& K/ K# s4 x  下面是 <stdarg.h> 里面重要的几个宏定义如下:
6 ]5 K- Z8 t8 q8 v1 [8 M" Z+ F# p    typedef char* va_list;
% g  P  Y$ K& V    void va_start ( va_list ap, prev_param ); /* ANSI version */4 F, D+ j' m& G- e* _! E
    type va_arg ( va_list ap, type );
7 X/ D- T1 K4 o* U, P    void va_end ( va_list ap ); 6 v( }/ m5 U; N
  va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。  A0 P- u9 ~) A9 s/ ~# V7 x4 W
  <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
; \, ^1 g# f& J# z* d) j2 t- z  <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
( N) K- Q9 D7 r* S) u, x% R  <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
' K$ h8 B3 {! q$ a6 d" Q' r  s  <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
: L3 ?( E4 W) S6 c2 G: ?) s  例如 int max(int n, ...); 其函数内部应该如此实现:2 ~, s% [2 O' I+ Q) w2 |3 T
  #include <iostream.h> 2 U+ w$ ^9 j% B6 j& `3 @( b/ L+ B
  void fun(int a, ...)
) K# [7 r! |# [' R: c  {
6 Y5 G) b7 c3 |$ h1 z0 M    int *temp = &a;
# D7 @* g% S; X. L) S    temp++;  //评论中反馈有问题,待改中6 A. N; o& h3 t1 W! W4 ^, u) L
    for (int i = 0; i < a; ++i) 4 D+ ^1 v( G1 L
    { + L" x+ R7 Q7 w" D
      cout << *temp << endl; 2 V, H" w$ C2 q8 \: f( j1 L2 s
      temp++;
( T# C. }7 j" O' \    } 9 G5 C9 n/ [) T1 o
  }
( p$ x' F, t' e1 M  int main() ) v0 o5 t+ s3 i5 Y- l
  { 4 Q) y1 W& p3 }4 G$ G
    int a = 1;
+ i$ t1 }% R0 b6 W$ k+ l    int b = 2; % ~0 [, Y9 B- Q* m; b
    int c = 3;
2 }# b7 P3 v$ j" }7 Q- _    int d = 4;
; Q3 h. n" E  i7 t    fun(4, a, b, c, d);
5 a- K  b; @$ ]8 ]# |+ A    system("pause"); ! a: e! m! n9 P1 r( b
    return 0;
* h9 }5 b8 T% y2 h- a  }
  Output::
) l0 l- Z5 x% E3 R: r, B* M0 C  1
" q- k+ `# V3 C5 d; t9 i  2 6 z2 M+ O* o' h
  3
5 ^0 p6 K% d* ^% c: m2 ^$ U  4
3:获取省略号指定的参数
3 Q, i. M" l  ?( ?  在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。像这段代码:
: f4 |8 K5 S9 L2 P& Z* b  @  void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...) + t6 }! e5 I5 D6 M% z# `
  {
" B6 _! i: {& v* ]    va_list args;
0 p! V" w" o, k5 O7 K    va_start(args, pszFormat); //一定要“...”之前的那个参数
1 n. y4 v. {1 G3 G+ m# |    _vsnprintf(pszDest, DestLen, pszFormat, args); 9 {4 j/ H) I# f8 G0 A* `
    va_end(args); ; |% t9 w6 r& C) b' R8 T( M
  }
0 \0 O( ?0 ?9 Y& W( ~, ?" P 4.演示如何使用参数个数可变的函数,采用ANSI标准形式   ?8 T. K" j1 e' Z3 y
  #include 〈stdio.h〉 # J; ~! m, @1 t0 r, V
  #include 〈string.h〉
& G- [  F5 M7 k' e$ i7 ^# h( S7 W  #include 〈stdarg.h〉
( ^7 [3 _. N  ]) M0 h+ O& E  /*函数原型声明,至少需要一个确定的参数,注意括号内的省略号*/
1 W9 z) M  M! o/ H8 R  int demo( char, ... );
% @6 s! U9 \  C# b' Q1 Z  void main( void )
- I+ U: |) s8 L" ^0 S' e! I# X  {
! w1 P# G/ u2 _; b& e! |      demo("DEMO", "This", "is", "a", "demo!", ""); 6 }1 ?8 b( ~% i
  }
3 ]1 ?1 l, Q# y1 Q  /*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/
: Y, X9 R9 S' H0 [) w  int demo( char msg, ... )   x9 J. I& }1 e7 @8 L3 `$ w
  {   e+ P; B% ]0 A. o- Z& f2 |+ T5 }
         /*定义保存函数参数的结构*/
0 _8 v7 I3 p6 T. k; l, a      va_list argp; ) B/ C0 A5 M) }! J2 J: O: [" F
      int argno = 0; 6 X  [4 e8 N' L7 l
      char para; 1 Z9 s3 w  V2 l0 G
      /*argp指向传入的第一个可选参数,msg是最后一个确定的参数*/
. J* ~  |' L0 q9 K; z& H( e. D      va_start( argp, msg ); 6 n- L. c( ~$ r4 [
      while (1) 4 t( S4 Y3 q$ K( V0 _
         { / A8 G% m5 q4 N; U4 }
            para = va_arg( argp, char);   f% O- G9 f1 f& ]6 K
              if ( strcmp( para, "") == 0 ) 7 w- x' c+ ~2 i+ r& z: ?* L- g/ u
                   break; 3 }; b+ D. m2 x( a9 B; O* U
              printf("Parameter #%d is: %s\n", argno, para);
  K- l4 T4 z0 w5 B+ x              argno++;
% y$ U; b& n, X* o    } 9 l# }( }3 |" b, |6 k6 v& h" H5 {
    va_end( argp );
1 O: s7 u% G8 Y    /*将argp置为NULL*/
1 `- f  Z9 N2 R, Z! O% g( f    return 0;
9 r. y- V+ \1 L  }: _: I3 |, d7 `# n' a
, T( j% ]! [% h) W' f. p# k
如由不对的地方,非常欢迎给予指导!

0 v/ }! m) S* Z3 C% \* u0 h6 ^! M8 E& j! m- V& r/ t' S
2 j6 A4 m% m! T0 Z4 d2 z
/ J0 i6 ^' W+ g# r

2 Y/ o$ b% [2 ?
 楼主| 发表于 2019-3-14 18:17 | 显示全部楼层
回复

使用道具 举报

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

使用道具 举报

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

使用道具 举报

本版积分规则

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

GMT+8, 2026-1-11 17:26 , Processed in 0.033455 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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