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

server/2024/11/24 14:52:30/

有什么用?

通常__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.ppmy.cn/server/144567.html

相关文章

“LLM是否是泡沫”

目录 “LLM是否是泡沫” 培养自己鉴别论文价值的能力、复现开源项目的能力、debug 代码的能力 llm 是生产力工具 多去找实习&#xff0c;读再多的论文&#xff0c;刷再多的技术文章&#xff0c;也不如一次 debug 多机通讯报错带来的认知深刻 一、LLM领域的发展与挑战 二、…

【LeetCode每日一题】——746.使用最小花费爬楼梯

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 数组 二【题目难度】 简单 三【题目编号】 746.使用最小花费爬楼梯 四【题目描述】 给你一…

node读取execl或写入execl数据保存

nodejs 使用 exceljs 库读取 execl 或写入 execl 数据后保存文件 安装库 exceljs npm i exceljs 读取execl const exceljs require(exceljs)const workbook new exceljs.Workbook() await workbook.xlsx.readFile(test.xlsx) // 读取第一个工作表 const worksheet workbo…

使用docker compose安装部署gitlab

安装gitlab docker pull gitlab/gitlab-ce:latest下载并安装 Docker Compose V2&#xff1a; sudo curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod…

基于Java Springboot高校教室资源管理系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&#xff1a;…

【RK3588 Linux 5.x 内核编程】-内核中断与Tasklet

内核中断与Tasklet 文章目录 内核中断与Tasklet1、Tasklet介绍2、创建Tasklet2.1 创建Tasklet2.2 动态方式创建Tasklet3、启用和禁用Tasklet4、Tasklet调度5、杀掉Tasklet6、Tasklet使用示例7、驱动验证在前面的文章中,对Linux的内核中断做了详细的介绍。我们知道,在Linux内核…

论文阅读——Performance Evaluation of Passive Tag to Tag Communications(一)

文章目录 摘要一、互耦对监听器标签输入阻抗的影响A. 无限细偶极子互阻抗的理论研究B. 电细偶极子的情况&#xff1a;理论与模拟C. 印刷偶极子的情况&#xff1a;电磁模拟与测量 二、T2T 通信系统的性能评估总结 论文来源&#xff1a;https://ieeexplore.ieee.org/document/970…

系统设计---RBAC模型与ABAC模型

RBAC 模型了解吗&#xff1f; 系统权限控制最常采用的访问控制模型就是 RBAC 模型 。 什么是 RBAC 呢&#xff1f; RBAC 即基于角色的权限访问控制&#xff08;Role-Based Access Control&#xff09;。这是一种通过角色关联权限&#xff0c;角色同时又关联用户的授权的方式。…