C语言可变参数设计

       有时 , 可能会碰到这样的情况 . 您希望函数带有可变数量的参数 , 而不是预定义数量
的参数,例如 printf() scanf() 还有 open() 等函数就是可变参数函数,他们的参数
是不确定的。 C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根
据具体的需求接受可变数量的参数

函数参数地址问题

        在了解可变函数参数之前,我们先来了解一下函数形参地址,我们来看下面一段
程序:
void fun(int a,int b,int c,int d,char e)
{
    printf("a=0x%p\n",&a);
    printf("b=0x%p\n",&b);
    printf("c=0x%p\n",&c);
    printf("d=0x%p\n",&d);
    printf("e=0x%p\n",&e);
}
int main(int argc, char const *argv[])
{
    char a;
    fun(1,2,3,4,'a');
    return 0;
}
它是用来查看函数形参的地址的,运行:
得到结论:可以发现函数形参的地址是连续的 , 而且从左到右是高地址到低地址存放
的。

但是,系统也不一定完全按这样的规律去执行的,例如参数为:

void fun(int a,double b,int c,int d,int e)
        此时运行程序查看参数地址会发现 b 的地址比 c 要低,这种现象的出现可能是由于系
统的某些内存优化处理导致的。
        但是. 可变参数的地址是绝对连续的,且地址是从左到右依次变高的,这是和已知参数
的不同之处

可变参数设计(va_list , va_start ,va_arg, va_end )

需要添加的头文件:

#include <stdarg.h>
va_list 这是一个适用于 va_start() va_arg() va_end() 这三个宏存储信息的类型。
typedef char* va_list;
其实在程序中 va_list 就是一个地址指针,用来指向形参的地址
void va_start(va_list ap, last_arg) 这个宏初始化 va_list 变量 ,
va_arg va_end 宏是一起使用的。 last_arg 是最后一个传递给函数的已知的固定
参数 , 即省略号之前的参数。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))
        _INTSIZEOF 的作用 是因为形参在内存中的地址是 4 的倍数,所以用来字节对齐,可以看出
va_start 的作用 就是让指针指向传递过来的下一个参数,也就能明白为什么 va_start
的第二个参数要是最后一个已知参数。
type va_arg(va_list ap, type) 这个宏检索函数参数列表中类型为 type 的下一个参
数。参数第一个为 va_list 变量,第二个为参数类型
#define va_arg(ap,type) (*(type*)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)))
        通过上面的代码我们就不难明白 va_arg 的作用了,就是输出 ap 指向地址参数的值,
同时 ap 指向下一个参数
void va_end(va_list ap) 这个宏允许使用了 va_start 宏的带有可变参数的函数返回。
如果在从函数返回之前没有调用 va_end ,则结果为未定义。
#define va_end(ap) (ap = (va_list)0)
va_end 实际就是将 ap 指针指向 null
该stdarg.h头文件提供了实现可变参数功能的函数和宏。具体步骤 如下:
        1.定义一个函数 , 最后一个参数为省略号 (…), 省略号前面可以设置自定义参数。
        2..在函数中创建一个 va_list 类型变量 , 该类型是在 stdarg.h 头文件中定义的。
        3.使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在
stdarg.h 头文件中定义的。
        4.使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
        5.用宏 va_end 来清理赋予 va_list 变量的内存。
举例简单的一个求和函数:
#include <stdio.h>
#include<string.h>
#include<errno.h>
#include<stdarg.h>
int sum(int num,...)
{
    va_list valist; //在函数中创建一个 va_list 类型变量
    int add1 = 0;
    int i;
    va_start(valist, num);
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
        add1 += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
    return add1;
}
int main()
{
    printf("sum = %d\n", sum(4, 2, 3, 4, 5));
    printf("sum = %d\n", sum(6, 5, 10, 15,4,7,9));
}
        到这里我们就差不多知道怎么去设计可变参数了,同时我们还可以大致知道类似于
printf,scanf 它们是如何实现的,例如我们来分析一下 printf() 的大致实现:
int printf(const char *ptr,...)
{
    va_list pr_va;
    va_start(pr_va,ptr);
    char *p = ptr; //定义指针指向传递进来的字符串
    /*遍历 ptr,寻找%,然后根据%后面跟的符号打印替换成相应类型的值*/
    while (*p != 0)
    {
        if(*p == % && *(++p) != 0)
        {
            switch(*p)
            {
                case d: /*va_arg(pr_va,int);*/
                case s: /*va_arg(pr_va,char*);*/
                /*...*/
            }
        }
        p++;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九月丫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值