C/C++“宏“你该知道的事! --宏实现简单日志系统

7 篇文章 0 订阅

什么是“宏”,和常量有啥区别?

1. 宏定义

宏定义是C语言提供的三种预处理中的一种,又称为宏代换、宏替换,简称“宏”,用#define定义,如下:

#define Pi 3.1415926

宏常量没有类型,它是在编译前即预编译阶段进行字符替换,在预编译阶段,直接将PI替换成3.1415926,同时没有类型安全检查,系统也不会为它分配内存。在c语言中,头文件中的加入 #ifndef#define#endif 目的防止该头文件被重复引用,
其实“被重复引用”是指一个头文件在同一个c文件中被include了多次,这种错误是由于include嵌套造成的。比如在a.h文件#include “c.h” 而此时b.cpp文件包含#include “a.h” 和#include "c.h"此时就会造成c.h重复引用

头文件被重复引用引起的后果

有些头文件重复引用只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些,但是对于大工程而言编译效率低下那将是一件多么痛苦的事情。
有些头文件重复包含,会引起错误,比如在头文件中定义了全局变量(虽然这种方式不被推荐,但确实是C规范允许的)这种会引起重复定义。

推荐使用格式

#ifndef A_H  (if not  定义 a.h)
#define A_H   (引入 a.h)

#include <xxx.h>
#include "xxx.h"

void func(int a);

#endif A_H

2. 常量

常量则是一种标识符,它的值在运行期间恒定不变。常量使用关键字const定义,如下:

const float PI = 3.14159;

常量是在运行时进行替换,并且在编译时会进行严格的类型检验,同时系统也会为常量分配内存。

3. 区别

如上所述,C++语言可以用const 来定义常量,也可以用#define来定义宏常量。但是两者的区别在于:

const 常量有数据类型,而宏常量没有数据类型;
const 常量在运行时进行替换,宏常量则是在预编译截断进行替换,const 常量在编译阶段会进行类型安全检查,宏常量则不会;
有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

宏的高端用法

其实在一个大型的项目中,阅读代码最痛苦的就是看宏的代码,又会涩又难懂,前几篇文章也写过一些关于如何去更好地使用GDB或 g++ -E 展开宏。
总结了一波用法!

#if __GNUC__ && !__CHECKER__
#define OVS_UNUSED                   __attribute__((__unused__))   
#define OVS_LIKELY(CONDITION)        __builtin_expect(!!(CONDITION), 1)
#define OVS_UNLIKELY(CONDITION)      __builtin_expect(!!(CONDITION), 0)
#else
#define OVS_UNUSED
#define OVS_LIKELY(CONDITION)   (!!(CONDITION))
#define OVS_UNLIKELY(CONDITION) (!!(CONDITION))
#endif

没想到吧,函数、结构体有属性,宏也有属性。
可能大家有点看不懂了 !!(CONDITION)是干啥用的?
这个的用法的目的就是根据两次非的操作来转换为bool类型的结果值。
那这样的好处在哪里呢?

#define likely(x)  __builtin_expect(!!(x), 1)

也就是说明 x==1 是“经常发生的”或是“很可能发生的”。

所以使用likely ,执行if后面语句的可能性大些,编译器将if{}是的内容编译到前面

使用unlikely ,执行else后面语句的可能性大些,编译器将else{}里的内容编译到前面。

------以上操作是有利于cpu预取,提高预取指令的正确率,因而可提高效率。

下面是pragma的用法

#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态

简单的拿个宏来做个日志系统

首先要理解 ## 的意思。
##的作用就是连接在一起的意思看下面的demo

#include<stdio.h>
#define CAT(a, b) a##b

int main(){
    int ab = 10;
    printf("%d",CAT(a, b));   #注意这里是我故意分开 通过宏实现简单的参数名称拼接
}

#结果:10

我手撕的简单的日志系统入门demo 如下

#include<stdio.h>
#define LOG(frm, args...) do {                                                          \
    printf("[%s %s][%s : %s][%d]", __DATE__ ,__TIME__ ,__FILE__, __func__, __LINE__);   \
    printf(frm, ##args);                                                                \
}while(0);

#define LOG_ERR(frm, args...) do {\
    LOG(frm, ##args);           \
    printf("[ERR]\n");          \
}while(0); 


int main(){
    LOG_ERR("hello world[%s]", "owner:liwentao");
    int c = 2;
    int d = 4;
    int e = 6;
    LOG_ERR("%d %d %d", c, d, e);
}

对应结果
在这里插入图片描述
同时日志也会有很多级别。

enum log_level {
    LOG_LEVEL_EMERG = 0, 
    LOG_LEVEL_ALERT = 1,
    LOG_LEVEL_CRIT = 2, 
    LOG_LEVEL_ERR = 3,
    LOG_LEVEL_WARNING = 4,
    LOG_LEVEL_NOTICE = 5, 
    LOG_LEVEL_INFO = 6,
    LOG_LEVEL_DEBUG = 7,
    LOG_LEVEL_MAX = 8,
};

文章参考

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值