C/C++ 可变参数函数、程序运行Zlog日志框架、Google测试框架

1、可变参数函数

  • 可以在运行时取任意的实参个数并根据实参的个数自动处理不同实参的情形,或者至少可以在运行时指定任意的实参个数。

实现方法:

1. 继承C的stdarg.h可变参数宏实现

  • C语言中prinft()即为典型的可变参数函数,它除了有一个参数 format 固定以外,后面跟的参数的个数和类型是可变的。
  • printf() 函数通过分析第一个字符串参数中的占位符个数来确定形参的个数;通过占位符的不同来确定参数类型(%d表示int类型、%s表示char *)
  • gcc 中 printf 源代码:
#include <stdarg.h>

int printf(const char *fmt, ...)
{
	char printf_buf[1024];
	va_list args;      		/* args为定义的一个指向可变参数的变量,va_list以及下边要用到的va_start,va_end都是是在定义
	                     可变参数函数中必须要用到宏, 在stdarg.h头文件中定义 */
	int printed;
	va_start(args, fmt);  	//初始化args的函数,使其指向可变参数的第一个参数,fmt是可变参数的前一个参数
	
	printed = vsprintf(printf_buf, fmt, args);
	va_end(args);          //结束可变参数的获取
	puts(printf_buf);
	return printed;
}
  • 宏说明:
    • va_list----用于定义一个va_list类型的变量,为后面的扩展可变参列表做准备
      eg: va_list args;

    • va_start—用于初始化va_list类型的变量
      eg: va_start(args,fmt); 表示进行扩展参数,其中 args 为va_list类型的变量,fmt为离可变参(…)最近的一个确定参数

    • va_arg—从变参列表中获取一个参数
      eg: type va_arg(args,data_type) — data_type表示数据类型
      —> int b = va_arg(pvar,int);

    • va_end—关闭变参扩展,进行后续的内存回收工作 eg : va_end(pvar)

/* --sum.cpp-- 可变参数宏实现求任意个整形值得和 */
#include <stdarg.h>
#include <iostream>
using namespace std;

int sum(int count, ...){     	//count 表示可变参数个数
    va_list ap;         	//声明一个va_list变量
    va_start(ap, count);     //初始化,第二个参数为最后一个确定的形参

    int sum = 0;  
    for(int i = 0; i < count; i++)          
        sum += va_arg(ap, int); //读取可变参数,第二个参数为可变参数的类型

    va_end(ap);              //清理工作 
    return sum;
}

int main(){
	cout << sum(3,1,2,3); 	// 第一个3为参数个数
	return 0;
}

使用事项:

  1. 函数原型中,省略号必须在参数列表的末尾:也就是说,在函数原型中参数列表省略号的右边不能再出现确定参数;
  2. 在使用结束之后,一定要使用va_end进行清理工作;因为可变参的机制类似于动态开辟空间,而va_end就相当于内存回收;
  3. 在使用时,需要知道传递参数的类型及个数;这就规定,在定义函数的时候至少有一个固定的形参,用于传递函数目前的变参的个数;
  4. 可变参数宏只能顺序访问可变参数,无法回溯,但是可以在清理操作完成后重新使用 va_start 初始化 va_list 变量,重新遍历形参表;
  5. 该方法不安全,容易出现内存溢出,或是泄露的问题。

2. 使用C++ 11标准中的 initializer_list 头文件

initializer_list 是C++11新标准中引入的一个标准库类型,与 vector 等容器一样initializer_list也支持begin()和end()操作,返回指向首元素的迭代器和尾后迭代器。initializer_list在同名头文件中声明,其实现由编译器支持。

说明:

  1. 函数原型中使用实例化initializer_list模板代表可变参数列表;
  2. 使用迭代器访问 initializer_list 中的参数;
  3. 传入实参以列表形式写在{ }之内。
  4. 函数原型 initializer_list 与普通形参无异。这表明形参列表中可以包含其他类型参数且位置不限,以下函数原型是正确的:
    void func(char c, initializer_list <int> il, double d);
  5. 同一个 initializer_list 中的参数具有相同的类型。本质上来说initializer_list是一个编译器支持的容器类模板,同其他容器一样,容器中的元素具有相同的类型。
/* --sum.cpp-- 利用initializer_list模板实现求和 */
#include <iostream>
using namespace std;
#include <initializer_list>

int sum(initializer_list<int> il){
    int sum = 0;
    for(auto p = il.begin(); p != il.end(); p++)   //使用迭代器访问参数
        sum += *p;
    return sum;
}

int main(){
    cout << sum({1,2,3});       // 参数以列表形式传递
    return 0;
}

3. 可变参数模板

参考:
https://blog.csdn.net/Clark_Sev/article/details/89500808
https://blog.csdn.net/qq_35280514/article/details/51637920

2、程序运行日志打印模版(Zlog日志框架)

1. 单独c文件

#include <stdio.h>
#include <stdarg.h>		// 可变参数宏实现

#define OPEN_LOG 1                  // 声明是否打开日志输出
#define LOG_LEVEL LOGLEVEL_INFO     // 声明当前程序的日志等级状态,只输出等级等于或高于该值的内容

typedef enum{               // 日志等级,越往下等级越高
    LOGLEVEL_DEBUG = 0,
    LOGLEVEL_INFO,
    LOGLEVEL_WARN,
    LOGLEVEL_ERROR,
}E_LOGLEVEL;

char *EM_logLevelGet(const int level){  // 得到当前输入等级level的字符串
    if(level == LOGLEVEL_DEBUG){
        return (char*)"DEBUG";
    }else if (level == LOGLEVEL_INFO ){
        return (char*)"INFO";
    }else if (level == LOGLEVEL_WARN ){
        return (char*)"WARN";
    }else if (level == LOGLEVEL_ERROR ){
        return (char*)"ERROR";
    }else{
        return (char*)"UNKNOWN";
    }   
}

void EM_log(const int level, const char* fun, const int line, const char *fmt, ...){ // 日志输出函数(可变参数)
    #ifdef OPEN_LOG     // 判断开关
    va_list arg;
    va_start(arg, fmt);	// fmt为最后一个确定的形参
    char buf[1 + vsnprintf(NULL, 0, fmt, arg)];     // 创建缓存字符数组,长度需+1(结束符)
    												// vsnprintf(NULL, 0, fmt, arg)返回arg 中的字符个数(不包含终止符)
    vsnprintf(buf, sizeof(buf), fmt, arg);          // 赋值 fmt 格式的 arg 到 buf
    va_end(arg);   
    if(level >= LOG_LEVEL){                         // 判断当前日志等级,与程序日志等级状态对比
        printf("[%s]\t[%s %d]: %s \n", EM_logLevelGet(level), fun, line, buf);
    }  
    #endif
}

#define EMlog(level, fmt...) EM_log(level, __FUNCTION__, __LINE__, fmt) // 宏定义,隐藏形参

int main(){
    int a = 1, b = 2;
    printf("Start log test: \n");
    EMlog(LOGLEVEL_DEBUG, "debug ing");   // 当前语句输出等级为 LOGLEVEL_DEBUG
    EMlog(LOGLEVEL_INFO, "info ing");
    EMlog(LOGLEVEL_WARN, "warn ing");
    EMlog(LOGLEVEL_ERROR, "error ing");

    printf("当前代码所在函数为:\t%s\n",__FUNCTION__);   // 调用宏__FUNCTION__得到当前代码所在函数
    printf("当前代码所在行为\t:%d\n",__LINE__);         // 调用宏__LINE__得到当前代码所在行
    return 0;
}

2. 函数模块

  • log.h
#ifndef _EM_LOG_H_      // 多个文件引用时,不能重复定义
#define _EM_LOG_H_

#include <stdarg.h>

#define OPEN_LOG 1                  // 声明是否打开日志输出
#define LOG_LEVEL LOGLEVEL_INFO     // 声明当前程序的日志等级状态,只输出等级等于或高于该值的内容
#define LOG_SAVE 0                  // 可补充日志保存功能

typedef enum{                       // 日志等级,越往下等级越高
    LOGLEVEL_DEBUG = 0,
    LOGLEVEL_INFO,
    LOGLEVEL_WARN,
    LOGLEVEL_ERROR,
}E_LOGLEVEL;

void EM_log(const int level, const char* fun, const int line, const char *fmt, ...);

#define EMlog(level, fmt...) EM_log(level, __FUNCTION__, __LINE__, fmt) // 宏定义,隐藏形参

#endif
  • log.c
#include <stdio.h>
#include "log.h"

char *EM_logLevelGet(const int level){  // 得到当前输入等级level的字符串
    if(level == LOGLEVEL_DEBUG){
        return (char*)"DEBUG";
    }else if (level == LOGLEVEL_INFO ){
        return (char*)"INFO";
    }else if (level == LOGLEVEL_WARN ){
        return (char*)"WARN";
    }else if (level == LOGLEVEL_ERROR ){
        return (char*)"ERROR";
    }else{
        return (char*)"UNKNOWN";
    }
    
}

void EM_log(const int level, const char* fun, const int line, const char *fmt, ...){ // 日志输出函数
    #ifdef OPEN_LOG     // 判断开关
    va_list arg;
    va_start(arg, fmt);
    char buf[1 + vsnprintf(NULL, 0, fmt, arg)];     // 创建缓存字符数组,长度需+1(结束符)
    vsnprintf(buf, sizeof(buf), fmt, arg);          // 赋值 ftm 格式的 arg 到 buf
    va_end(arg);   
    if(level >= LOG_LEVEL){                         // 判断当前日志等级,与程序日志等级状态对比
        printf("[%s]\t[%s %d]: %s \n", EM_logLevelGet(level), fun, line, buf);
    }  
    #endif
}
  • app.c
#include <stdio.h>
#include "log.h"

int main(){
    int a = 1, b = 2;
    printf("Start log test: \n");
    EMlog(LOGLEVEL_DEBUG, "debug ing");   // 当前语句输出等级为 LOGLEVEL_DEBUG
    EMlog(LOGLEVEL_INFO, "info ing");
    EMlog(LOGLEVEL_WARN, "warn ing");
    EMlog(LOGLEVEL_ERROR, "error ing");

    printf("当前代码所在函数为:\t%s\n",__FUNCTION__);   // 调用宏__FUNCTION__得到当前代码所在函数
    printf("当前代码所在行为\t:%d\n",__LINE__);         // 调用宏__LINE__得到当前代码所在行
    return 0;
}

gcc app.c log.c -o app 编译

3、Google测试框架

#include <stdio.h>
#include <stdlib.h>

typedef struct 
{
    int output;
    int a;
    int b;
    int (*TeseFunc)(int, int);  // 两个整数形参的函数指针,测试目标函数
    int line;                   // 函数所在行
}T_Test;

// 结构体作为函数返回值(新建结构体)
T_Test *addFunc(int (*TeseFunc)(int, int), int a, int b, int output, int line){
    T_Test *m_Test = (T_Test *)malloc(sizeof(T_Test));  // 动态申请
    m_Test->a = a;
    m_Test->b = b;
    m_Test->TeseFunc = TeseFunc;
    m_Test->output = output;
    m_Test->line = line;
    return m_Test;
}

// 宏定义函数,缺省line
#define addFunc(TeseFunc, a, b, output) addFunc(TeseFunc, a, b, output, __LINE__)

void runTest(T_Test *p_Test){
    if(p_Test != NULL){
        int count = p_Test->TeseFunc(p_Test->a, p_Test->b);
        if(count == p_Test->output){
            printf("success \n");
        }
        else{
            printf("[LINE: %d] fail %d != %d\n", p_Test->line, count, p_Test->output);
        }
        free(p_Test);   // 释放内存
    }
}

int add(int a, int b){
    return a+b;
}

int main(){
    printf("Test app start.\n");
    T_Test *m_Test = addFunc(add, 1, 2, 4); 
    runTest(m_Test);
    return 0;
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值