1、
#define CLR_RED "\033[31m" /* 红色字 */
#define CLR_GREEN "\033[32m" /* 绿色字 */
#define CLR_YELLOW "\033[33m" /* 黄色字 */
#define CLR_BLUE "\033[34m" /* 蓝色字 */
#define CLR_PURPLE "\033[35m" /* 紫色字 */
#define CLR_SKYBLUE "\033[36m" /* 天蓝字 */
#define CLR_WHITE "\033[37m" /* 白色字 */
#define CLR_BLK_WHT "\033[40;37m" /* 黑底白字 */
#define CLR_RED_WHT "\033[41;37m" /* 红底白字 */
#define LOG_LEVEL_MAP(XXX) \
XXX(LOG_LEVEL_DEBUG, "DEBUG", CLR_WHITE) \
XXX(LOG_LEVEL_INFO, "INFO ", CLR_GREEN) \
XXX(LOG_LEVEL_WARN, "WARN ", CLR_YELLOW) \
XXX(LOG_LEVEL_ERROR, "ERROR", CLR_RED) \
XXX(LOG_LEVEL_FATAL, "FATAL", CLR_RED_WHT)
typedef enum {
LOG_LEVEL_VERBOSE = 0,
#define XXX(id, str, clr) id,
LOG_LEVEL_MAP(XXX)
#undef XXX
LOG_LEVEL_SILENT
} log_level_e;
这个宏的作用是在日志中,对不同级别的日志,选择不同的颜色打印。上面的是表示颜色的宏,很简单。 关键是下面的LOG_LEVEL_MAP,这个宏可以说是一个二级宏,不知道有没有这个概念,因为它后面括号里的内容,也是一个宏。看下面的用法,在LOG_LEVEL_MAP宏的下面定义了一个枚举,这个枚举表示告警的级别。在这个枚举的中,定义了另一个宏。先把LOG_LEVEL_MAP(XXX)的第一层宏展开,是这样的:
typedef enum {
LOG_LEVEL_VERBOSE = 0,
XXX(LOG_LEVEL_DEBUG, "DEBUG", CLR_WHITE) \
XXX(LOG_LEVEL_INFO, "INFO ", CLR_GREEN) \
XXX(LOG_LEVEL_WARN, "WARN ", CLR_YELLOW) \
XXX(LOG_LEVEL_ERROR, "ERROR", CLR_RED) \
XXX(LOG_LEVEL_FATAL, "FATAL", CLR_RED_WHT)
LOG_LEVEL_SILENT
} log_level_e;
这里为了看起来方便,把里面的宏定义取掉了。展开第一层后,XXX还是一个宏,在enum中定义的该宏,所以需要继续展开,根据第一段代码中的宏定义,展开后是这样的:
typedef enum {
LOG_LEVEL_VERBOSE = 0,
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARN,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL,
LOG_LEVEL_SILENT
} log_level_e;
这样看起来就简单多了,但这还不是全部。。。
第二个使用该宏的地方:
const char* pcolor = "";
const char* plevel = "";
#define XXX(id, str, clr) \
case id: plevel = str; pcolor = clr; break;
switch (level) {
LOG_LEVEL_MAP(XXX)
}
#undef XXX
第一层展开后跟上面的是一样的,主要就是第二层,这里直接展开第二层:
switch (level) {
case LOG_LEVEL_DEBUG: plevel = "DEBUG", pcolor = CLR_WHITE; break;
case LOG_LEVEL_INFO: plevel = "INFO", pcolor = CLR_GREEN; break;
case LOG_LEVEL_WARN: plevel = "WARN", pcolor = CLR_YELLOW; break;
case LOG_LEVEL_ERROR: plevel = "ERROR", pcolor = CLR_RED; break;
case LOG_LEVEL_FATAL: plevel = "FATAL", pcolor = CLR_RED_WHT; break;
}
是不是很简单了,就是根据日志级别,设置相应的信息。当然,上面所有的宏展开实际上都在一行,只不过我为了看起来简单,分了行。
2、使用宏实现类似于C++中容器的功能
在array.h和queue.h中,使用宏定义实现了类似于vector和deque的功能。因为这两个功能的实现方式差不多,而且理解起来很简单,就不贴接口了,只看看结构体。
#define ARRAY_DECL(type, atype) \
struct atype { \
type* ptr; \
size_t size; \
size_t maxsize;\
}; \
typedef struct atype atype;\
type就是容器中元素的类型,atype就是这个容器的类型。举个程序中使用例子
ARRAY_DECL(struct epoll_event, events)
这样就定义了一个容器,容器类型为events,成员类型为struct epoll_event,展开就是
struct events {
struct epoll_event* ptr;
size_t size;
size_t maxsize;
};
功能挺强大,但理解起来挺简单。当然了,比起C++标准库的容器,用起来肯定还是麻烦一些。
3、使用宏实现类似C++继承的功能
#define HTIMER_FIELDS \
HEVENT_FIELDS \
uint32_t repeat; \
uint64_t next_timeout; \
struct heap_node node;
struct htimer_s {
HTIMER_FIELDS
};
struct htimeout_s {
HTIMER_FIELDS
uint32_t timeout; \
};
struct hperiod_s {
HTIMER_FIELDS
int8_t minute;
int8_t hour;
int8_t day;
int8_t week;
int8_t month;
};
htimer_s、htimeout_s和hperiod_s都包含HTIMER_FIELDS,就相当于这三个结构体“继承”了同一个“基类”,拥有共同的一些成员。但是我个人不喜欢这种方式,我宁愿将该“基类”实现为一个结构体,然后作为其他三个结构体的内部成员。我觉得使用”基类结构体“的方式比使用宏更具意义,也更可读。
先写这么多吧。。。