C语言的可变参数函数理解与应用

一、可变参数函数的理解

  1. 官方定义:C语言允许定义参数数量为可变的函数,这称为可变参数函数。它与传统意义上的强制定义固定参数的函数不同。
  2. 可变参数函数应用最广的是格式化输入输出函数。如printf,scanf。可支持任意参数输入输出。
  3. 可变参数函数可有助于我们封装printf函数。例如,可以指定打印到控制台或文件中。

二、可变参数函数的应用

        1、可变参数的常规打印

        直接参考代码,注释写的很清楚了。

        代码的核心也是通过vprintf打印到控制台。

#include 
#include 
// 可变参数的头文件
#include 

// 默认为打印到控制台
void script_log_error(char *fmt, ...)
{
    // 定义可变参数列表
    va_list ap;
    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    // 打印可变参数
    printf("数据为:");
    vprintf(fmt, ap);
    // 结束
    va_end(ap);

    printf("\n");
}

int main()
{
    // 使用可变参数的函数打印
	script_log_error("type is_3:%f,%s,%d hunhe5", 362.01, "123654", 50);
    script_log_error("type is_5: no data");
}

        2、可变参数的解析并打印

        解析主要分四步处理:

                1.通过循环遍历的方式,解析每个输入字符

                2.把%d、%f、%s作为关键字解析,并获取对应的数据。

                3.把修饰性字符与实际数据都转换成字符串并存储于二维数组中。

                4.把二维数组中的内容转换到一维数组中,然后打印一维数组。

#include 
#include 
// 可变参数的头文件
#include 

void script_dzlog_error(char *fmt, ...)
{
    va_list ap;
    char *p, *sval;
    int ival;
    double dval;
    // 存储修饰性字符
    char data_val[256] = {0};
    // 存储每一段数据
    char data[10][256] = {0};

    int idx_d1 = 0;
    int cnt_v = 0;

    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    for (p = fmt; *p; p++) {
        if(*p != '%') {
            // 获取修饰性字符
            data_val[cnt_v++] = *p;
            continue;
        }
        switch(*++p) {
        case 'd':
        {
            ival = va_arg(ap, int);
            char tmp[128] = {0};

            // 把修饰性字符复制到data中
            memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
            // 清空data_val
            memset(data_val, 0x00, sizeof(data_val));
            cnt_v = 0;

            // 把数据型字符复制到data中
            my_itoa(ival, tmp);
            memcpy(&data[idx_d1++][0], tmp, sizeof(tmp));

            break;
        }
        case 'f':
        {
            dval = va_arg(ap, double);
            char tmp[128] = {0};

            // 把修饰性字符复制到data中
            memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
            // 清空data_val
            memset(data_val, 0x00, sizeof(data_val));
            cnt_v = 0;

            // 把数据型字符复制到data中
            my_ftoa(dval, tmp);
            memcpy(&data[idx_d1++][0], tmp, sizeof(tmp));

            break;
        }
        case 's':
        {
            char tmp[128] = {0};
            char tmp_cnt = 0;
            for (sval = va_arg(ap, char *); *sval; sval++)
            {
                // printf("%c", *sval);
				tmp[tmp_cnt++] = *sval;
			}

            // 把修饰性字符复制到data中
            memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
            // 清空data_val
            memset(data_val, 0x00, sizeof(data_val));
            cnt_v = 0;

            // 把数据型字符复制到data中
            // printf("tmp:%s\n", tmp);
            memcpy(&data[idx_d1++][0], tmp, sizeof(tmp));
            break;
        }
        default:
            data_val[cnt_v] = *p;
            // putchar(*p);
            break;
        }
    }

    // 把修饰性字符复制到data中
    memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
    // printf("data_tail:%s\n", data_val);
    // 清空data_val
    memset(data_val, 0x00, sizeof(data_val));

	va_end(ap);

    // 打印描述性文字
    char data_print[4096] = {0};
    int cnt_p = 0;
    int idx_p = 0;
    for (cnt_p = 0; cnt_p < idx_d1; cnt_p++)
    {
        int len = strlen(data[cnt_p]);
        memcpy(&data_print[idx_p], &data[cnt_p], len);
        idx_p += len;        
    }


    // 打印到控制台
    printf("\033[31m[script error]\033[0m %s\n", data_print);
    // 打印到文件中
    // dzlog_error("\033[31m[script error]\033[0m %s\n", data_print);
}

int main()
{
    // 打印数据
    script_dzlog_error("type is_0:%s,%s %s string", "sssssss5", "ttttttttt6", "rrrrrr7");
    script_dzlog_error("type is_5: no data");
}

三、可变参数函数的封装

       实现目的:

                1、理论上支持N种打印模式(目前已实现两种,可自行添加)

                2、支持三种打印类型。分别是error、warning、debug类型

                3、通过应用层代码控制打印位置。如打印到控制台或者文件中。

        实现思路:

                1、底层封装成库。剥离实现方式与实现逻辑。

                2、应用层书写逻辑代码,用于指定打印实现与打印位置。

                3、提供注册函数,用于把应用层逻辑传达给底层。

        实现代码(忽略文件命名方式):

                1、底层代码

        script_debug.h文件

#ifndef __SCRIPT_DEBUG_H__
#define __SCRIPT_DEBUG_H__

#include 
// 可变参数头文件
#include 

#define SCRIPT_DEBUG 1

#ifdef SCRIPT_DEBUG
    #define SCRIPT_DEBUG_ERR(fmt, args...)  g_script_log.error(fmt, ##args)

#if (SCRIPT_DEBUG >= 1)
	#define SCRIPT_DEBUG_WARN(fmt, args...)  g_script_log.warni(fmt, ##args)
#else
	#define SCRIPT_DEBUG_WARN(fmt,args...) 
#endif

#if (SCRIPT_DEBUG >= 2)
	#define SCRIPT_DEBUG_DEBUG(fmt, args...)  g_script_log.debug(fmt, ##args)
#else
	#define SCRIPT_DEBUG_DEBUG(fmt,args...) 
#endif // #if (SCRIPT_DEBUG >= 2)

#endif	// #ifdef SCRIPT_DEBUG

typedef void (*script_log_print)(char *fmt, ...);

// 支持打印类型
typedef struct script_log{
	script_log_print error;
	script_log_print warni;
	script_log_print debug;
} script_log_t;

extern script_log_t g_script_log;

// 注册函数
int script_log_interface_register(script_log_print error, script_log_print warni, script_log_print debug);


#endif

        script_debug.c文件

#include "script_debug.h"
#include 

// 默认为打印到控制台
void script_log_error(char *fmt, ...)
{
    // 定义可变参数列表
    va_list ap;
    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    // 打印可变参数
    printf("\033[31m[script error]\033[0m ");
    vprintf(fmt, ap);
    // 结束
    va_end(ap);

    printf("\n");
}

void script_log_warni(char *fmt, ...)
{
    // 定义可变参数列表
    va_list ap;
    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    // 打印可变参数
    printf("\033[33m[script warni]\033[0m ");
    vprintf(fmt, ap);
    // 结束
    va_end(ap);

    printf("\n");
}

void script_log_debug(char *fmt, ...)
{
    // 定义可变参数列表
    va_list ap;
    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    // 打印可变参数
    printf("\033[34m[script debug]\033[0m ");
    vprintf(fmt, ap);
    // 结束
    va_end(ap);

    printf("\n");
}

// 初始化打印的位置为控制台
script_log_t g_script_log = {
    .error = script_log_error,
    .warni = script_log_warni,
    .debug = script_log_debug,
};

// 实现功能:提供给vicenter注册
int script_log_interface_register(script_log_print error, script_log_print warni, script_log_print debug)
{
    g_script_log.error = error;
    g_script_log.warni = warni;
    g_script_log.debug = debug;
}

                2、应用层代码

        script_print.h

#include "stdio.h"
#include 
#include "script_debug.h"
#include "zlog.h"

char *my_itoa(int num, char *str);
char *my_ftoa(float num, char *str);

script_log_print script_dzlog_error(char *fmt, ...);
script_log_print script_dzlog_warni(char *fmt, ...);
script_log_print script_dzlog_debug(char *fmt, ...);

void parse_para(va_list ap, char *fmt, char *out_data);

        script_print.c

#include "script_print.h"

// 打印位置:日志文件中
// 打印类型:error类型
script_log_print script_dzlog_error(char *fmt, ...)
{
    va_list ap;
    char out_data[4096] = {0};

    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    parse_para(ap, fmt, out_data);
	va_end(ap);

    // 打印到控制台
    printf("\033[31m[script error]\033[0m %s\n", out_data);
    // 打印到文件中
    dzlog_error("\033[31m[script error]\033[0m %s\n", out_data);
}

// 打印位置:日志文件中
// 打印类型:warni类型
script_log_print script_dzlog_warni(char *fmt, ...)
{
    va_list ap;
    char out_data[4096] = {0};

    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    parse_para(ap, fmt, out_data);
	va_end(ap);

    printf("\033[33m[script warni]\033[0m %s\n", out_data);
    dzlog_warn("\033[33m[script warni]\033[0m %s\n", out_data);
}

// 打印位置:日志文件中
// 打印类型:debug类型
script_log_print script_dzlog_debug(char *fmt, ...)
{
    va_list ap;
    char out_data[4096] = {0};

    // 获取不定参数的首地址,首地址是从fmt开始
    va_start(ap, fmt);
    parse_para(ap, fmt, out_data);
	va_end(ap);

    printf("\033[34m[script debug]\033[0m %s\n", out_data);
    dzlog_debug("\033[34m[script debug]\033[0m %s\n", out_data);
}

// 解析可变参数的数据,并把数据转换成字符串存储在out_data中
void parse_para(va_list ap, char *fmt, char *out_data)
{
    char *p, *sval;
    int ival;
    double dval;
    int idx_d1 = 0;
    int cnt_v = 0;

    // 存储每一段数据
    char data[10][256] = {0};
    // 存储修饰性字符
    char data_val[256] = {0};

    // 循环遍历每个字符
    for (p = fmt; *p; p++)
    {
        if(*p != '%') {
            // 获取修饰性字符
            data_val[cnt_v++] = *p;
            continue;
        }
        switch(*++p) {
        case 'd':
        {
            ival = va_arg(ap, int);
            char tmp[128] = {0};

            // 把修饰性字符复制到data中
            memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
            // 清空data_val
            memset(data_val, 0x00, sizeof(data_val));
            cnt_v = 0;

            // 把数据型字符复制到data中
            my_itoa(ival, tmp);
            memcpy(&data[idx_d1++][0], tmp, sizeof(tmp));

            break;
        }
        case 'f':
        {
            dval = va_arg(ap, double);
            char tmp[128] = {0};

            // 把修饰性字符复制到data中
            memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
            // 清空data_val
            memset(data_val, 0x00, sizeof(data_val));
            cnt_v = 0;

            // 把数据型字符复制到data中
            my_ftoa(dval, tmp);
            memcpy(&data[idx_d1++][0], tmp, sizeof(tmp));

            break;
        }
        case 's':
        {
            char tmp[128] = {0};
            char tmp_cnt = 0;
            for (sval = va_arg(ap, char *); *sval; sval++)
            {
                // printf("%c", *sval);
				tmp[tmp_cnt++] = *sval;
			}

            // 把修饰性字符复制到data中
            memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
            // 清空data_val
            memset(data_val, 0x00, sizeof(data_val));
            cnt_v = 0;

            // 把数据型字符复制到data中
            // printf("tmp:%s\n", tmp);
            memcpy(&data[idx_d1++][0], tmp, sizeof(tmp));
            break;
        }
        default:
            data_val[cnt_v] = *p;
            // putchar(*p);
            break;
        }
    }

    // 把修饰性字符复制到data中
    memcpy(&data[idx_d1++][0], data_val, sizeof(data_val));
    // printf("data_tail:%s\n", data_val);
    // 清空data_val
    memset(data_val, 0x00, sizeof(data_val));

    // 打印描述性文字
    // char data_print[4096] = {0};
    int cnt_p = 0;
    int idx_p = 0;
    for (cnt_p = 0; cnt_p < idx_d1; cnt_p++)
    {
        int len = strlen(data[cnt_p]);
        memcpy(&out_data[idx_p], &data[cnt_p], len);
        idx_p += len;        
    }
}

char *my_itoa(int num, char *str)
{
    if(str == NULL)
    {
            return NULL;
    }
    sprintf(str, "%d", num);
    return str;
}

char *my_ftoa(float num, char *str)
{
    if(str == NULL)
    {
            return NULL;
    }
    sprintf(str, "%.2lf", num);//将num 保留2位小数赋值给str
    return str;
}

                3、main.c文件

#include 
#include "script_print.h"
extern int vicenter_cli_init(void);

int main()
{
	// 注册打印方式-假如没有注册,则采用默认打印,底层已经实现。
	script_log_interface_register(script_dzlog_error, script_dzlog_warni,     script_dzlog_debug);

    // 测试代码
    SCRIPT_DEBUG_ERR("type is_0:%s,%s %s string11", "sssssss5", "ttttttttt6", "rrrrrr7");
	SCRIPT_DEBUG_WARN("type is_1:%d %d,%d %d ddd3", 360, 508, 1000000, 1);
	SCRIPT_DEBUG_DEBUG("type is_2:%f %f %f,ffff3", 362.01, 108.024, 50);
}

大功告成,奖励自己一下

你可能感兴趣的:(C语言,c语言)