一乐电子

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

QQ登录

只需一步,快速开始

微信扫码登录

手机号码,快捷登录

手机号码,快捷登录

搜索
查看: 4927|回复: 3

C语言中可变参数的函数

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

/ J# }2 y  Z4 @. U
1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
  void foo(...);" q  ^- C4 K& E0 U: O  G
  void foo(parm_list,...);1 T# ]0 l) l* E+ a  a0 j/ o
  这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它。
/ D) u# d  Q0 N( T: M
. Y% B0 r5 b  s. S7 r
2.函数参数的传递原理
  函数参数是以数据结构:栈的形式存取,从右至左入栈。
  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:6 T- @' ]" }$ {4 z/ w6 |# `/ _" {
    void func(int x, float y, char z);; T/ R( ]! Y) `" Y& I
  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。) @! `5 a( @. D
  下面是 <stdarg.h> 里面重要的几个宏定义如下:
  N/ I6 C4 s& r* d" L$ d    typedef char* va_list;
& c. J8 Z9 C* f8 ~5 |9 G& o    void va_start ( va_list ap, prev_param ); /* ANSI version */
  w  A) _7 `0 Y" Q9 c2 R) k# U3 M    type va_arg ( va_list ap, type );
; S! I8 w9 p0 R% E7 U" ?    void va_end ( va_list ap );
/ d0 K" @: Q& t- F  va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。) v  M7 U* s7 B
  <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
/ r* `( u2 _  x5 C  <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
3 U6 V  z9 _; g6 l% x+ v1 @/ ?  <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;$ x, f8 X5 y1 [
  <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。$ o' I# y. }( c
  例如 int max(int n, ...); 其函数内部应该如此实现:
) ?! k4 g* r) d, ^% X! j2 N: y  #include <iostream.h> 6 y% C; S" W' o
  void fun(int a, ...) 9 f7 s. {4 v" A4 @
  {
- L. Z$ X. q6 _0 G& ?5 T& d    int *temp = &a;/ A, a- F( K9 Q
    temp++;  //评论中反馈有问题,待改中/ r4 m2 @) c2 I0 t; {) y: p
    for (int i = 0; i < a; ++i)
; J& W$ ?9 I' v6 y, P    {
' Z' l! |9 X3 ^5 Q9 C, @5 b3 M      cout << *temp << endl; * Y' w" T! p- s7 }$ a+ I
      temp++;
( G% B) o) \2 r! ~" `    } 0 R3 E3 s0 P( `% D9 f
  }
8 Y7 r' P6 }, Z. M  int main() + g2 a- J! c4 I5 v- V0 Y
  {
. c' d; h" S# c- J- o* f8 M" {    int a = 1; " y' k7 l3 M3 i3 b! b) f" U
    int b = 2; 5 ]7 Y2 V, x" l) J5 T
    int c = 3; 3 t8 U& t. J- |
    int d = 4; * \  x4 R, {  l8 |# Q% J: C: l
    fun(4, a, b, c, d); . l! R4 E) A/ D9 X
    system("pause");
( i; h. S% [9 q    return 0;
9 W6 n" b7 k; \6 ]  }
  Output:: % G% r1 |' k! t2 r$ [0 X
  1 1 J; W: a4 w9 X9 b, ^' E
  2
) l. ]  D( U4 k6 P. a  3
0 c( p6 w, i4 y7 \& p4 c/ ^  4
3:获取省略号指定的参数
9 t3 [$ l4 i! x8 A  \! N# l  在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。像这段代码:
: E, R/ y, t2 G) M5 {+ s- l; h  void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...) 2 ?  |) a0 A# ~( j0 h$ _0 h( [/ b
  { 6 v7 |! N& h* d. p$ g0 h% W0 h4 J; E
    va_list args; / P7 u6 c$ p5 h8 F6 U
    va_start(args, pszFormat); //一定要“...”之前的那个参数
# B/ Z9 ~2 L- b    _vsnprintf(pszDest, DestLen, pszFormat, args); & p* P3 ?" F# N
    va_end(args); ( P' S. Y/ W5 Y9 Y0 P
  }' @; B( o! R6 g) w8 e
4.演示如何使用参数个数可变的函数,采用ANSI标准形式 - g: `( U/ H5 S1 J
  #include 〈stdio.h〉
$ k3 p9 K! F+ d3 W/ `; s5 h# L  #include 〈string.h〉 6 N7 i" k4 x3 `, s$ a  @" a
  #include 〈stdarg.h〉 : i% z- n' P, L7 U
  /*函数原型声明,至少需要一个确定的参数,注意括号内的省略号*/ : o! v" X2 V8 g  J0 I. {
  int demo( char, ... );
* ]- s) g+ _4 t# P3 A- ^  void main( void )
' @  |+ q6 O! I' s7 F' ]  { ! |( O# E1 p: |! z
      demo("DEMO", "This", "is", "a", "demo!", ""); 0 P& D$ S& t7 x1 `4 k# a2 {
  }
* ~, U: u* @$ q- ~! u- B4 h  /*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/
; x5 l3 K; _0 p# X' F9 x6 d  int demo( char msg, ... )
3 U. y+ S7 }; q9 p5 X" R) C  {
& y; X" L- S. l, |' P% n         /*定义保存函数参数的结构*/% T9 G0 u7 }6 f7 H" n, R
      va_list argp;
- p! d* O7 B, s      int argno = 0;
4 Y( X4 X. X; w/ |      char para;
% ^4 W  X+ h8 q  j- p& F      /*argp指向传入的第一个可选参数,msg是最后一个确定的参数*/ 3 t( b# ?- C+ O4 r% O
      va_start( argp, msg ); ( S; @: n; u% v# t* \9 P1 s
      while (1) ; B# q5 {. J7 [: d' h* y& N5 }; Z
         { & `$ J7 k: K2 v3 T* w, Q7 Y: E8 `
            para = va_arg( argp, char); 9 S4 y4 n% C; i# \2 n1 t% _
              if ( strcmp( para, "") == 0 )
; o% B0 K5 q3 K5 p. [; S- ^                   break; 4 \! E' K3 p4 ]# h4 N2 `
              printf("Parameter #%d is: %s\n", argno, para); 8 S' Y8 r/ {. v+ Q" [) g
              argno++;
8 ~0 |. T8 ?2 Z3 }* N6 i    } % N# Y% a' v5 l
    va_end( argp ); 9 z/ s$ R4 g) j0 Z3 R
    /*将argp置为NULL*/$ \7 B; ~  G4 Q3 I' h- B( y  N
    return 0;
0 ^8 k$ Q2 L8 x! U$ l5 q  }
8 t) l& U5 \% R+ ?5 R# [& {4 c, L
, G/ B& @3 `8 V! T1 ?
如由不对的地方,非常欢迎给予指导!
6 |7 J9 C9 j- M( W2 F
1 i$ y2 S3 }7 c* |' Y4 i
2 Z1 z* N, \3 g
6 L! L' ^- s1 {8 y4 W
* ?* h  L, G) M7 w0 q
 楼主| 发表于 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:44 , Processed in 0.032626 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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