当前位置: 首页 > article >正文

C语言练级->##__VA_ARGS__(可变参数)的用法

有什么用?

通常__VA_ARGS__用于宏定义,其中关于日志宏需要用的,printf 等支持可变参数的函数的宏封装。

首先我们先知道这个__VA_ARGS__的英文全称是“Variadic Arguments” 叫可变参数。说到可变参数学过C语言的朋友们应该都会想到printf();通常我们使用printf 用到占位符,然后传入变量将其改为变量的值。如下代码

    char str[] = "龙弟";
	int age = 21;
	printf("我是谁:%s,年龄是:%d", str, age);
	//或者直接打印字符串
	printf("我是龙弟,年龄21岁");

而大家有想过如果我们第一个参数是字符串“我是谁:%s,年龄是:%d” ,那占位符越多,后面给printf的参数不是越来越多吗?所以就有了可变参数这个说法

int printf(const char *format, ...);

如上代码format 就是  “我是谁:%s,年龄是:%d”,后面的三个点 在C语言中用作可变参数(可以看做一个省略号)。那和__VA_ARGS__有什么关系?二者在定义宏时需要互相打配合!!!

什么时候用? 

首先...和__VA_ARGS__, 在函数定义时或者宏定义时为什么用的...而不是__VA_ARGS__。

int printf(const char *format, ...);

就像printf用的是...不是 __VA_ARGS__

  1. ...的作用
    在宏定义中,...位于固定参数列表之后,表示宏可以接受额外的可变参数。这些可变参数的数量和类型在宏定义时是不确定的,它们是在宏被实际调用时提供的。

  2. __VA_ARGS__的作用
    __VA_ARGS__是一个特殊的预处理标识符,它用于在宏体内引用所有传递给宏的可变参数。当宏被调用时,__VA_ARGS__会被替换为实际传递的可变参数列表(包括参数之间的逗号分隔符)。

由上可知:__VA_ARGS__和...的分工是明确的,一个是在定义使用,一个是在宏体内使用。通常的用法如下:

#define LOG(fmt,...) fprintf(stdout,fmt,__VA_ARGS__)

int main()
{
    printf("hello world")
    return 0;
}

上面的代码中我们定义了一个LOG宏,其中里面封装的是 fprintf。stdout是输出到屏幕的流。LOG中没有指定,所以我们用LOG默认就是打印到屏幕上。

##__VA_ARGS__和__VA_ARGS__的区别

(VS2022中好像没了)

首先##和__VA_ARGS__其实是没什么关联的,##有单独的语法说明,__VA_ARGS__也只是一个宏参数

单独的##使用方法

用于拼接前后的两个代码(token)。但是##不能是第一个或者最后一个子串
 

#include <iostream>
#define LOG(x) log##x()
 
void logA(){
    printf("log func A \n");
}
 
void logB(){
    printf("log func B\n");
}
 
int main()
{
    LOG(A);
 
    getchar();
    return 0;
}

结果:

 ##__VA_ARGS__

有了上面的铺垫我们就可以知道,##用于连接两串代码,但是和##__VA_ARGS__一起用时,用于在外界没有传入可变参数时,将“,”抵消。

#define LOG(fmt,...) fprintf(stdout,fmt,__VA_ARGS__)
 
int main()
{
    char str[] = "龙弟";
	int age = 21;
	LOG("我是谁:%s,年龄是:%d", str, age);
	//或者直接打印字符串
	LOG("我是龙弟,年龄21岁");
    return 0;
}

 当我们fmt是"我是谁:%s,年龄是:%d",我们的...是str, age。此时的fprintf变成了

fprintf(stdout, "我是谁:%s,年龄是:%d", str, age);

此时str前面这个逗号就是原本__VA_ARGS__前的逗号。所以这样使用时不会出现问题。

但是对于下面的fprintf生成的是:

fprintf(stdout, "我是龙弟,年龄21岁", );

此时我们发现由于没有可变参数的传入,所以__VA_ARGS__为空,多余了一个逗号,会导致出错。这时候##的用处就来了。 把宏替换成如下形式就是在__VA_ARGS__前+##

#define LOG(fmt,...) fprintf(stdout,fmt,##__VA_ARGS__)

 这个##将逗号和__VA_ARGS__拼接起来,结果__VA_ARGS__此时为空,和逗号拼接后相当于逗号被拿走了,如果平时有可变参数传入时,这时就算拼接了,那也是正常显示逗号,所以没问题

日志宏

上面说这个语法通常用于日志宏,所以下面实现了一个简易的日志宏给大家

#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include<stdio.h>
#include<time.h>
//定义一个宏函数用来打印日志用fprintf
//fmt 是传入的占位符字符串  ...是可变参数(一般用于后面参数不确定的情况类似printf)
//日志一般要用到时间 这里使用 time函数 struct tm*变量 strftime将time转为字符串存到一个f中 localtime当前时间
//接下来要增加日志等级
#define INF 1
#define DBG 2
#define ERR 3
#define LOG_DEFAULT_LEVEL 2
#define LOG(level,fmt,...)    do{\
    if(level < LOG_DEFAULT_LEVEL)break;\
    time_t t = time(NULL);\
    struct tm* lt = localtime(&t);\
    char st[32] = {0};\
    strftime(st,31,"%H:%M:%S",lt);\
    fprintf(stdout,"[level:%d %s %s:%d]" fmt"\n",level,st,__FILE__,__LINE__,##__VA_ARGS__);}while(0)
#define ILOG(fmt,...) LOG(INF,fmt,##__VA_ARGS__)
#define DLOG(fmt,...) LOG(DBG,fmt,##__VA_ARGS__)
#define ELOG(fmt,...) LOG(ERR,fmt,##__VA_ARGS__)
#endif

讲解:

1.日志的等级:INF DBG ERR。默认等级 LOG_DEFAULT_LEVEL 2

2.下面的宏函数主体中 LOG是我们要封装的宏,我们实际封装的宏函数如下:

do{\
    if(level < LOG_DEFAULT_LEVEL)break;\
    time_t t = time(NULL);\
    struct tm* lt = localtime(&t);\
    char st[32] = {0};\
    strftime(st,31,"%H:%M:%S",lt);\
    fprintf(stdout,"[level:%d %s %s:%d]" fmt"\n",level,st,__FILE__,__LINE__,##__VA_ARGS__);}while(0)

讲解:如果低于默认等级则不打印,t 变量 和 lt 变量都是求时间。strftime 是把时间转换为字符串

最后在通过fprintf进行打印,其中__FILE__ __LINE__ 是宏,里面封装了文件名和哪一行

3.最后根据每个等级的日志宏 用LOG进行了封装。实际用法如下:

int main()
{
    ILOG("我是龙弟");
    BLOG("我是阿龙");
    ELOG("今年%d岁",21);
    return 0;
}


http://www.kler.cn/a/406863.html

相关文章:

  • ssm面向品牌会员的在线商城小程序
  • Web3.0安全开发实践:Clarity最佳实践总结
  • 小红书运营教程10(小红书笔记制作01)
  • GESP2023年9月认证C++四级( 第三部分编程题(1-2))
  • 哈希表(极速学习版)
  • Day24 回溯算法part03
  • uniapp中使用uni.$emit和uni.$on进行页面通讯传值
  • 3-测试go-redis+redsync实现分布式锁 --开源项目obtain_data测试
  • VRRP实现出口网关设备冗余备份
  • win10中使用ffmpeg和MediaMTX 推流rtsp视频
  • JAVA下载EXCEL,PDF文件之后无法打开,提示文件损坏
  • electron主进程和渲染进程之间的通信
  • 大数据学习18之Spark-SQL
  • STL关联式容器之multiset及multimap
  • Flutter:AnimatedSwitcher当子元素改变时,触发动画
  • Ansible使用简介和基础使用
  • 嵌入式 UI 开发的开源项目推荐
  • C#学习笔记——窗口停靠控件WeifenLuo.WinFormsUI.Docking使用-腾讯云开发者社区-腾讯云
  • vue3中父div设置display flex,2个子div重叠
  • 华为无线AC+AP组网实际应用小结
  • FreeIPCC:Ai智能呼叫中心是什么?
  • 【数据结构】归并排序 —— 递归及非递归解决归并排序
  • 基于自混合干涉测量系统的线展宽因子估计算法matlab仿真
  • Python Matplotlib 安装指南:使用 Miniconda 实现跨 Linux、macOS 和 Windows 平台安装
  • MAC C语言 Helloword
  • spring学习(四)