C 语言编程新手精髓之变参函数

在这里插入图片描述
我们以 printf 这个 very 熟悉的函数为例,来分析一下变参函数。先看下 printf 函数的定义:

int printf(const char fmt, …){ int i; int len; / va_list 即 char * / va_list args; va_start(args, fmt); / 内部使用了 va_arg() */ len = vsprintf(g_PCOutBuf,fmt,args); va_end(args); for (i = 0; i < strlen(g_PCOutBuf); i++) { putc(g_PCOutBuf[i]); } return len;}

什么是变参函数?

可变参数函数的原型声明为 type VAFunction(type arg1, type arg2, … );

参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用 ‘’…’’ 表示。固定参数和可选参数共同构成一个函数的参数列表。

printf 函数涉及了以下几个重要的宏:

typedef char * va_list;/* * Storage alignment properties /#define _AUPBND (sizeof (acpi_native_int) - 1) //acpi_native_int 为 4 字节(根据机器字长而定)#define _ADNBND (sizeof (acpi_native_int) - 1) / * Variable argument list macro definitions /#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))#define va_arg(ap, T) ((T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))#define va_end(ap) (void) 0#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
在这里插入图片描述
C语言1232_副本.jpg

分析以下三个宏的作用

  1. va_start 宏

作用: 根据 A 取得可变参数表的首指针并赋值给 ap。

原理: 根据最后一个固定参数 A 的地址 + 第一个变参对 A 的偏移地址,然后赋值给 ap,这样 ap 就是可变参数表的首地址(函数传递的参数会从右向左依次入栈,并且 ARM 的栈为降栈,所以参数 A 的地址最低)。

  1. va_arg 宏

作用: 指取出当前 ap 所指的可变参数并将 ap 指针指向下一可变参数。

  1. va_end 宏

作用: 结束可变参数的获取,与 va_start 对应使用。

堆栈中,各个函数的分布情况是倒序的,即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分。参数在堆栈中的分布情况如下: ******************* 最后一个参数 倒数第二个参数 … 第一个参数 函数返回地址 函数代码段 *******************得到可变参数个数的三种办法:1) 函数的第一个参数,指定后续的参数个数,如 func(int num,…);2) 根据隐含参数,判断参数个数,如 printf 系列的,通过字符串中 % 的个数判断;3) 特殊情况下(如参数都是不大于 0xFFFF 的 int),可以一直向低处访问堆栈,直到返回地址。

而 _bnd(X, bnd) 宏就是以 4 字节对齐的变量 X 的大小。

在这里插入图片描述

自己实现一个简单的 printf 函数

#include <stdio.h>#include <string.h>#include <stdarg.h>#include <stdlib.h>void print(char fmt, …){ va_list ptr; va_list temp_ptr = NULL; / 用于存储最终转换的结果 / char buf[100] = {0}; / 用于存储临时转换的结果 / char temp_buf[50] = {0}; unsigned char index = 0; unsigned char len, arg_len; len = strlen(fmt); / 得到可变参数的首地址 */ va_start(ptr, fmt); for(; fmt; fmt++){ if(fmt == ‘%’){ switch(++fmt){ case ‘d’: case ‘D’: sprintf(temp_buf, ‘’%d’’, va_arg(ptr, int)); temp_ptr = temp_buf; break; case ‘s’: case ‘S’: / 取出当前变量,并将指针指向下一个可变参数 / temp_ptr = va_arg(ptr, char); break; } arg_len = strlen(temp_ptr); strcat(buf+index, temp_ptr); index += arg_len; }else{ buf[index] = fmt; index++; } } / 结束取参 / va_end(ptr); / 输出最终转换结果 */ puts(buf);}int main(){ print(’‘My name is %s and my height is %d cm.’’, ‘‘Lance#’’, 178); return 0;}

程序运行结果:

My name is Lance# and my height is 178 cm.

你的支持就是我的动力!

文章最后

怎么快速学C/C++,有什么方法,打算深入了解这个行业的朋友,可以加C/C++学习群:648778840,不管你是小白还是大牛,小编我都欢迎,不定期分享干货,包括小编自己整理的一份2019最新的C/C++资料和0基础入门教程,欢迎初学和进阶中的小伙伴。

每天晚上20:00我都会开直播给大家分享C/C++编程学习知识和路线方法,群里会不定期更新最新的教程和学习方法,大家都是学习C/C++的,或是转行,或是大学生,还有工作中想提升自己能力的前端党,如果你是正在学习C/C++的小伙伴可以加入学习。最后祝所有程序员都能够走上人生巅峰,让代码将梦想照进现实,非常适合新手学习,有不懂的问题可以随时问我,工作不忙的时候希望可以给大家解惑。

猜你喜欢

转载自blog.csdn.net/chengxuyuanbawei/article/details/89762267