让函数能伸能缩

你既需要功能强大如find()那样可以用函数指针进行搜索的函数,也需要易于使用的函数。printf()函数有一个很酷的功能:接收参数的数量可变。

让函数能伸能缩 - 图1

你的函数如何做到这点?

这里刚好有个问题需要用到可变数量参数。Head First酒吧里的人正在为计算账单而烦恼,一名员工为了提高工作效率,根据现存鸡尾酒清单创建了一个枚举类型和一个返回每种酒价格的函数:

  1. enum drink {
  2. MUDSLIDE, FUZZY_NAVEL, MONKEY_GLAND, ZOMBIE
  3. };
  4. double price(enum drink d)
  5. {
  6. switch(d) {
  7. case MUDSLIDE:
  8. return 6.79;
  9. case FUZZY_NAVEL:
  10. return 5.31;
  11. case MONKEY_GLAND:
  12. return 4.82;
  13. case ZOMBIE:
  14. return 5.89;
  15. }
  16. return 0;
  17. }

很酷,如果Head First酒吧的员工想知道某种酒的单价,只要调用这个函数就行了。但如果他们想要计算一单酒的总价:

让函数能伸能缩 - 图2

他们希望有一个叫total()的函数,它接收酒的杯数和这些酒的名字。

让函数能伸能缩 - 图3可变参数函数

参数数量可变的函数被称为可变参数函数(variadic function)。C标准库中有一组(macro)可以帮助你建立自己的可变参数函数。为了弄清它是如何工作的,你将创建一个函数打印一连串int的函数:让函数能伸能缩 - 图4

让函数能伸能缩 - 图5

下面是代码:

我们逐行分析。

让函数能伸能缩 - 图6

 

让函数能伸能缩 - 图7百宝箱

函数与宏

宏用来在编译前重写代码,这里的几个宏va_startva_argva_end看起来很像函数,但实际上隐藏在它们背后的是一些神秘的指令。在编译前,预处理器会根据这些指令在程序中插入巧妙的代码。

 

这里没有蠢问题

问:等等,为什么va_endva_start叫宏?它们不就是一般的函数吗?

:不是,它们只是设计成了普通函数的样子,预处理会把它们替换成其他代码。

问:什么是预处理器?

:预处理器在编译阶段之前运行,它会做很多事情,包括把头文件包含进代码。

问:可以只使用可变参数,而不用普通参数吗?

:不行,至少需要一个普通参数,只有这样才能把它的名字传给va_start

问:如果我从va_arg中读取比传给函数更多的参数会怎样?

:会发生不确定的错误。

问:听起来真糟糕。

:是的,非常糟糕。

问:如果我以double或其他类型读取int参数呢?

:也会发生不确定的错误。

 

让函数能伸能缩 - 图8练习

下面轮到你上场了,Head First酒吧的人想要创建一个函数,能够返回一巡酒的总价,函数如下:

让函数能伸能缩 - 图9

使用前几页上的price()函数完成total()函数的代码:

让函数能伸能缩 - 图10

 

让函数能伸能缩 - 图11练习解答

下面轮到你上场了,Head First酒吧的人想要创建一个函数,能够返回一巡酒的总价,函数如下:

让函数能伸能缩 - 图12

使用前几页上的price()函数完成total()函数的代码:

让函数能伸能缩 - 图13

让函数能伸能缩 - 图14试驾

你创建了一些调用函数的测试代码,编译代码并查看结果:

让函数能伸能缩 - 图15

代码正确运行了!

现在你学会了使用可变参数,代码用起来更简单,也更直观了。

让函数能伸能缩 - 图16要点

  • 接收数量可变参数的函数叫可变参数函数。

  • 为了创建可变参数函数,需要包含stdarg.h头文件。

  • 可变参数将保存在va_list中。

  • 可以用va_start()va_arg()va_end()控制va_list

  • 至少需要一个普通参数。

  • 读取参数时不能超过给出的参数个数。

  • 需要知道要读取参数的类型。