参考:

stdarg.h:https://zh.wikipedia.org/wiki/Stdarg.h

stdarg.h:http://baike.baidu.com/view/3373010.htm

linux环境下可以使用man手册:man stdarg


#################################################################


C语言也存在可变参数的概念

最常见的就是scanf和printf函数:

int scanf(const char * restrict format,...);
int printf(const char *fmt, ...);


你可以输入任意类型的任意个参数,但是必须在格式化字符串中确定输入参数的个数和类型。


那么我们如何自定义可变参数函数呢?

就需要使用stdarg.h头文件了。stdarg的全称就是standard arguments(标准参数),主要目的就是为了让函数能够接收可变参数。


它为用户定义了4个标准宏:

/* Define the standard macros for the user,
   if this invocation was from the user program.  */
#ifdef _STDARG_H

#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	__builtin_va_end(v)
#define va_arg(v,l)	__builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L || defined(__GXX_EXPERIMENTAL_CXX0X__)
#define va_copy(d,s)	__builtin_va_copy(d,s)
#endif

同时它定义了一个类型va_list

注意:如果想要使用stdarg.h中的宏定义和类型对象,必须显示定义头文件#include <stdarg.h>


接下来先介绍4个宏定义:


void va_start(va_list ap, last);

va_start函数初始化了va_list对象ap,为之后的va_arg和va_end函数作准备,所以必须首先调用。

参数last指的是变量参数列表之前的参数名,也就是调用函数中最后一个已知参数类型的参数。比如,printf函数中的fmt

因为last参数的地址会在va_start函数中使用,所以last不应该是一个寄存器变量,函数或者数组类型。


type va_arg(va_list ap, type);

va_arg函数返回ap当前指向的参数的值。

参数ap就是va_start初始化的va_list对象;

参数type是一个类型名,比如“char”,“int”等,表示当前ap指向的参数的类型

每次调用va_arg后,ap就会指向下一个参数。但如果已经遍历完参数列表,或者参数type并不是当前参数的实际类型名,此时调用va_arg函数将会发生随机错误。

ap被参数va_arg函数使用过后,将无法回到最开始的位置


void va_end(va_list ap);
va_end函数和va_start相对应。在同一个函数中,调用过va_start之后就必须调用va_end。

使用va_end以后,变量ap将重置为空,并释放内存。


void va_copy(va_list dest, va_list src);

C99标准。如果想要多次使用参数列表,那么可以使用va_copy函数。

每次调用过va_copy函数后,必须相应的在同一个函数中调用va_end函数,比如:

va_list aq;
va_copy(aq, ap);
...
va_end(aq)

有些情况下,va_copy函数已经在其他地方有所定义,所以使用相同功能的函数__va_copy。


注意:va_start/va_arg/va_end函数符合C89标准。而va_copy是C99定义的


#################################################333


学习完stdarg.h之后,我们使用几个函数来练习一下:


#include <stdio.h>
#include <stdarg.h>

void foo(char *fmt, ...)
{
    va_list ap;
    int d;
    char c, *s;

    va_start(ap, fmt);
    while (*fmt)
        switch (*fmt++) {
        case 's':              /* string */
            s = va_arg(ap, char *);
            printf("string %s\n", s);
            break;
        case 'd':              /* int */
            d = va_arg(ap, int);
            printf("int %d\n", d);
            break;
        case 'c':              /* char */
            /* need a cast here since va_arg only
               takes fully promoted types */
            c = (char) va_arg(ap, int);
            printf("char %c\n", c);
            break;
        }
    va_end(ap);
}


int main(int argc, char* argv)
{
    foo("%d %s %c", 23, "hello", 'z');
}

这是man stdarg里面的一个例子,可以实现对整数,字符串和字符的转换,但在这里也有一个缺陷,那就是无法在fmt中输入非格式化字符

由于可变参数函数的参数数量不定,C语言定义了省略号来表示之后的参数列表,但最少要有一个确定类型的参数(C++中可以忽略)


#include <stdio.h>
#include <stdarg.h>
void printargs(int arg1, ...) /* 输出所有int类型的参数,直到-1结束 */
{
    va_list ap;
    int i;
    va_start(ap, arg1);

    for (i = arg1; i != -1; i = va_arg(ap, int))
        printf("%d ", i);

    va_end(ap);
    putchar('\n');
}

int main(void)
{
    printargs(5, 2, 14, 84, 97, 15, 24, 48, -1);
    printargs(84, 51, -1);
    printargs(-1);
    printargs(1, -1);

    return 0;
}



这是百度百科里的一个例子。printargs假定输入的参数均为int类型的整数,将所有参数输出,直到遇到-1为止(好像,维基百科也是这样的)


接下来写一个比较完整的程序,类似于printf函数,能够在fmt中输入非格式化字符,同时能够输入格式化参数

#include <stdio.h>
#include <stdarg.h>

void printff(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);

    int i=0;
    while (fmt[i] != '\0')
    {
        if (fmt[i] != '%')
        {
            printf("%c", fmt[i]);
            i ++;

            continue;
        }

        fmt ++; // 跳过%
        switch(fmt[i])
        {
        case 'c': // 得到一个字符
            char cc;
            cc = (char)va_arg(ap, int);
            printf("%c", cc);
            break;
        case 'd': // 得到一个整数
            int dd;
            dd = (int)va_arg(ap, int);
            printf("%d", dd);
            break;
        case 's': // 得到一个字符串
            char *ss;
            ss = va_arg(ap, char*);
            printf("%s", ss);
            break;
        }

        va_end(ap);
    }
}

int main(int argc, char* argv)
{
    printff("Hello World\n");

    printf("%d Hi %s 2233 %c\n", 2, "adfa", 'a');
}



这样子就能简单实现自定义参数函数的效果

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐