C/C++"宏"你该知道的事
什么是“宏”,和常量有啥区别?
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,
};