C语言-可变参数列表

﹏ヽ暗。殇╰゛Y 2024-04-17 20:34 167阅读 0赞

C语言-可变参数列表

文章目录

  • C语言-可变参数列表
    • 起步
    • C语言的可变参数列表
    • VA_ARGS 与 记号粘贴符
    • 标准库 stdarg

起步

假使你有使用 Python 编程的经验,你应该会觉得设计接口能用 *arg**kwarg 这件事是多么的酸爽。毕竟定义一个拥有长长参数列表的函数是多么的累赘,形参不能总是被函数使用到则是累赘中的累赘。多说无益,还是用个 Python Demo 举例。

假使我想设计一个打印函数,就叫 YouPrint 吧。YouPrint 会将我传递的参数按每行打印,且排头以 “You” 开头。使用如下:

  1. Usage:
  2. YouPrint("zhong", "ying", "ding")
  3. Output:
  4. You: zhong
  5. You: ying
  6. You: ding

如果不用“可变参数列表”的方式设计,那 YouPrint 应该如下这般:

  1. def YouPrint(a, b, c):
  2. for alpha in (a, b, c):
  3. print(f"You: {alpha}")

这样显然在违背我们对 打印函数 的习惯。难道打印函数不应该允许任意传参吗?万一我只传一个参数呢,又或者传十个呢?在 Python 中,其解决办法就是 *arg。因此合理的设计是:

  1. def YouPrint(*arg):
  2. for alpha in arg:
  3. print(f"You: {alpha}")

在 Python 中,*变量名 出现在形参中时,其意义为:变量 arg (上面例子中用到 arg)会接收所有的位置传参(这些参数没有被其他形参接收),自身将以元组的身份出现,存放这些传递过来的参数。听不懂没关系,我就是想引出,C 语言也可以做到。

C语言的可变参数列表

C 语言的可变参数主要有三种方式实现,一种基于标准库 <stdarg.h>,使用 va_start, va_arg, va_end。另一种用宏 __VA_ARGS__, 再一种用记号粘贴符号 ##

后两种的使用场景较小,常在 debug 之类的时候用;第一种使用场景则更为广泛些,但事实上不建议滥用。

VA_ARGS 与 记号粘贴符

现在假设一个场景,我们写程序的时候需要 printf 之类的 DEBUG 操作,同时希望程序完成之后,这些打印语句会自动失效,而非我们手动去删除。并且,这些 DEBUG 语句应该要有明显的标识,足以让我们一眼辨出哪些是 DEBUG 语句,哪些是正常输出。

而作为一个输出函数,自然有必要同 printf 一样,在合法操作下传递任意个参数。

根据以上要求,显然宏函数最为合适不过。用 __VA_ARGS__ 实现如下:

  1. #include <stdio.h>
  2. #define DEBUG
  3. #ifdef DEBUG
  4. #define f_debug(fmt, ...) printf("DEBUG: " fmt "\n", __VA_ARGS__)
  5. #else
  6. #define f_debug(fmt, ...)
  7. #endif
  8. int main()
  9. {
  10. int a, b;
  11. f_debug("完成变量 %s 与 %s 的定义", "a", "b");
  12. a = 1024;
  13. b = 512;
  14. f_debug("完成变量 a = %d 与 b = %d 的赋值", a, b);
  15. int rlt = a + b;
  16. f_debug("完成变量之间的加法运算i,结果为:%d,即将准备输出结果", rlt);
  17. printf("The result is %d\n", rlt);
  18. return 0;
  19. }

(如果你对 #ifdef 等条件编译了解有限,不妨看看我对该内容的一些笔记 C语言-预处理(1), C语言-预处理(2)。这里就不再做过多的讲解。)

编译代码后运行,可以看到输出如下:

  1. DEBUG: 完成变量 a b 的定义
  2. DEBUG: 完成变量 a = 1024 b = 512 的赋值
  3. DEBUG: 完成变量之间的加法运算i,结果为:1536,即将准备输出结果
  4. The result is 1536

如果我们不需要 DEBUG 信息了,注释掉代码中的一行宏定义即可:

  1. /* #define DEBUG */
  2. 输出:
  3. The result is 1536

尽管以上的设计有些不好,因为我们无法对 f_debug 传递一个参数,好在基本实现了我们对功能的诉求。也可以看到,不论是传递三个参数,还是两个参数,只要在第一个参数 fmt 的正常约束之下,程序都是可以正常运行的。


记号粘贴运算符的使用与 VA_ARGS 极类似。我们只需要对不定长参数(也就是省略号)取个名字,然后再 ##名字 即可。使用同上的示例代码,但需要做些小小改动:

  1. #define f_debug(fmt, ...) printf("DEBUG: " fmt "\n", __VA_ARGS__)
  2. ||
  3. || 修改
  4. \/
  5. #define f_debug(fmt, arg...) printf("DEBUG: " fmt "\n", ##arg)

代码中的 arg 就是 ... 的名字。

标准库 stdarg

标准库 <stdarg.h> 带领之下,不定长参数的功能更强大些了,不过用起来就稍稍麻烦了。需要记住三个宏函数,一个数据类型:

  • va_start, va_arg, va_end
  • va_list

用一个简单的示例说明一下如何使用:

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. /* 形参 num 用来指明不定长参数的个数 */
  4. void print_a_word(int num, ...)
  5. {
  6. va_list arg; /* 声明一个变参 arg,其类型为 va_list */
  7. va_start(arg, num); /* 初始化 */
  8. int i;
  9. for(i=0; i<num; ++i) {
  10. /* va_arg 用来取出变参中的变量,char* 用来指明变量的类型 */
  11. printf("%s\n", va_arg(arg, char*));
  12. }
  13. va_end(arg); /* 将变参 arg 置为 NULL */
  14. }
  15. int main()
  16. {
  17. print_a_word(3, "I love you.", "I miss you.", "Where are you?");
  18. return 0;
  19. }
  20. // 输出:
  21. I love you.
  22. I miss you.
  23. Where are you?

需要说明的有几点:

  • 必须向函数传递不定长参数的个数,因为 va_start 绑定变参的时候需要用到该值。
  • 每调用一次 va_arg,指针就会指向下一个参数变量。
  • va_arg 的第二个参数是参数类型,这就意味着有些类型是不建议使用的。因为函数调用时会发生类型转换,如 short,char 会转换成 int,float 会转换成 double。

将上述代码做一些小小改动,传递 char 类型的实参:

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. void print_a_word(int num, ...)
  4. {
  5. ...
  6. for(i=0; i<num; ++i) {
  7. printf("%c\n", va_arg(arg, char)); /* char 类型 */
  8. }
  9. ...
  10. }
  11. int main()
  12. {
  13. print_a_word(3, 'a', 'b', 'c'); /* char 类型 */
  14. return 0;
  15. }

编译代码时会有警告如下:

  1. ╭─root@localhost /home/Cpp/ChangeArgs
  2. ╰─✗ gcc a-chang.c
  3. In file included from a-chang.c:2:0:
  4. a-chang.c: 在函数‘print_a_word’中:
  5. a-chang.c:11:36: 警告:通过‘...’传递时‘char’被提升为‘int [默认启用]
  6. printf("%c\n", va_arg(arg, char));
  7. ^
  8. a-chang.c:11:36: 附注:(因此您应该向‘va_arg’传递‘int’而不是‘char’)
  9. a-chang.c:11:36: 附注:如果执行到这段代码,程序将中止

此时强行运行程序,会发生段错误。如果根据编译器提示,将 va_arg(arg, char) 修改为 va_arg(arg, int),编译代码就不会有告警信息,还可以得到正确的运行结果。

然而,不定长传参本身就不建议使用,何况这种 “奇巧淫技” 的手段呢!

发表评论

表情:
评论列表 (有 0 条评论,167人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C语言可变参数

    C语言可变参数可以使用宏函数取出,宏函数在头文件stdarg.h中。 贴出如下简单的代码,博客转载自: [https://www.cnblogs.com/edver/p/84