[TOC]

文章参考:https://www.cnblogs.com/pengdonglin137/p/3345911.html

概述

函数这章讲到了函数的变长参数(ellipsis…),但是primer中讲得比较浅,提到了怎么声明怎么调用,但是没有写明在函数内部是如何获取变长的参数的。

va_list是在C语言中解决变参问题的定义的一个类型,常在 va_start(), va_arg(), and va_end() 宏中使用。变参问题是指参数的个数不定,可以是传入一个参数也可以是多个。可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。

1)省略号(ellipsis)

在无法给出所有传递给函数的参数的类型和数目时,可以使用省略号(…)指定函数参数表。有如下几种形式:

1
2
3
1 void fun1(int a, double b, ...); //给出确定的几个参数,其他用省略号
2 void fun2(int a ...); //省略号前有或者没有逗号都是可以的
3 void fun3(...); //也可以不确定任何参数,但和没有参数是不一样的

最典型的应用就是printf函数,printf的声明和调用方法如下:

1
2
int printf( const char *format [,argument]... );    //官方声明
printf("My name is %s, age %d.", "AnnieKim", 24); //调用

2)通用的工作原理

大多数带有变长参数的函数都利用显式声明的参数中的一些信息,来获取调用中提供的其他可选实参的类型和数目。

比如printf函数,就是根据第一个参数推导可选实参:如果第一个’%’后有一个’s’,说明后面要有第二个参数,类型是字符串;如果还有第二个’%’,后面跟一个’d’,说明还需要第三个参数,是一个整型等等。

所以说,通常情况下,第一个参数是必不可少的。

3)如何获取变长参数

现在,我们要关注的是函数内部的实现细节。当我看到primer这部分的时候还真是好奇实现细节呢,只怪我孤陋寡闻,以前没见过⊙﹏⊙b。

为了解决变长参数问题,需要用到以下几个宏(以下定义来自MSDN),并且使用这几个宏时必须至少提供一个显式的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdarg.h>

type va_arg(
va_list arg_ptr,
type
);
void va_end(
va_list arg_ptr
);
void va_start(
va_list arg_ptr,
prev_param
);

其中,type是指要获取的参数的类型,比如int,char *等,arg_ptr是指向参数列表的指针(va_list类型),prev_param是指最后一个显式声明的参数,以用来获取第一个变长参数的位置。

使用步骤:

a)定义一个va_list类型的变量,变量是指向参数的指针。

b)va_start初始化刚定义的变量,第二个参数是最后一个显式声明的参数。

c)va_arg返回变长参数的值,第二个参数是该变长参数的类型。

d)va_end将a)定义的变量重置为NULL。

注意事项:

a)变长参数的类型和数目不能通过宏来获取,只能通过自己写程序控制。

b)编译器对变长参数函数的原型检查不够严格,会影响代码质量。

4)举个例子

最后举个例子,是自己写的printf函数,只能用于处理’%s’和’%d’。为简单起见,没有做任何异常处理,理解这些宏的使用方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1 #include <iostream>
2 #include <stdarg.h>
3 using namespace std;
4
5 void myprintf(const char *format...)
6 {
7 va_list argptr;
8 va_start(argptr, format); //va_start
9
10 char ch;
11 while (ch = *(format++)) //逐个遍历format字符串
12 {
13 if (ch == '%')
14 {
15 ch = *(format++);
16 if (ch == 's')
17 {
18 char *name = va_arg(argptr, char *); //va_arg
19 cout<<name;
20 }
21 else if (ch == 'd')
22 {
23 int age = va_arg(argptr, int); //va_arg
24 cout<<age;
25 }
26 }
27 else
28 {
29 cout<<ch;
30 }
31 }
32 cout<<endl;
33 va_end(argptr); //va_end
34 }
35
36 int main()
37 {
38 myprintf("My name is %s, age %d.", "AnnieKim", 24);
39 return 0;
40 }

在以上函数中, 用到了以下几个宏:

  • va_list

  • va_start

  • va_arg

  • va_end

va_list 用来定义一个变量列表的指针类型.
va_start(listPointer, n) 的意思是将 listPointer 这个指针绑定到有 n 个变量的传入参数列表上.
va_arg(listPointer, type) 从参数列表中逐个取出数据, 取出数据的类型由 type 决定, 它返回这个 type 类型的值, 你可以马上把它赋值给另一个变量.
当函数调用结束的时候, 要记得使用 va_end 来清除 listPointer 指向的空间, 否则会发生内存泄漏问题.