【说在前面的话】 【'##'的“表”用法】 #define def_u32_array(__name, __size) uint32_t array_##__name[__size]; 实际中,我们可以这样使用:
宏展开的效果是: uint32_t array_sample_buffer[64]; 可以看到,'array__'与形参“__name”是没有天然分割的,因此要想将'array_'与'__name'所代表的内容(而不是__name本身)粘连在一起,就需要“##”运算的帮助。另一方面,'__name'与'['是具有天然分隔的——编译器不会认为'__name'与'['是连接在一起的,因此这里并不需要画蛇添足的使用'##'运算——如果你这么做了,预编译器会毫不犹豫的告诉你语法错误。——这是'##'运算的普通用法。 【'##'的官方“里”用法】
这里定义了一个宏'safe_atom_code()',在括号内,无论你填写任何内容,都会被无条件的放置到“__VA_ARGS__”所在的位置,你可以认为括号里的“...”实际上就是对应'__VA_ARGS__'。比如,我们可以写下这样的代码: /** \fn void wr_dat (uint16_t dat) \brief Write data to the LCD controller \param[in] dat Data to write */ static __inline void wr_dat (uint_fast16_t dat) { safe_atom_code( LCD_CS(0); GLCD_PORT->DAT = (dat >> 8); /* Write D8..D15 */ GLCD_PORT->DAT = (dat & 0xFF); /* Write D0..D7 */ LCD_CS(1); ) } 这个代码确保在向寄存器GCLD_PORT->DAT写入数据时不会被其它中断打断。
你不仅提出了问题,甚至还实际测试了下,似乎完全等效,“根本没差别嘛!”——你惊呼道。然而,事实上并没有那么简单:
可变参数宏的引入就解决了这个问题:
回头再来看前面的问题, #define safe_atom_code(...) 与
的差别在于,前者括号里可以放包括','在内的几乎任意内容;而后者则完全不能容忍逗号的存在——比如你调用了一个函数,函数的参数要用到都好隔开吧?再比如,你用到了逗号表达式……——想想都很酸爽。 #define log_info(__STRING, ...) printf(__STRING, __VA_ARGS__) 因此,使用的时候,我们可以这样写:
宏展开后实际上对应于: printf('------------------------------------\r\n',); printf(' Cycle Count : %d', total_cycle_cnt); 看似没有问题,注意到一个细节没有?在第一个printf()的最后多了一个','。虽然有些编译器,例如GCC并不会计较(也许就是一个warning),但对于广大洁癖严重的处女座程序员来说,这怎么能忍,于是在ANSI-C99标准引入可变参数宏的时候,又贴心了加了一个不那么起眼的语法:当下面的组合出现时 ',##__VA_ARGS__',如果__VA_ARGS__是一个空字符串,则前面的','会一并被删除掉。因此,上面的宏可以改写为:
此时,前面的代码会被展开为: printf('------------------------------------\r\n'); printf(' Cycle Count : %d', total_cycle_cnt); 处女座表示,这次可以安心睡觉了。 【正文:'##'的骚操作】
EXAMPLE(); 被展开为:
EXAMPLE(我们提供的值); 被展开为:
这个技巧其实对API的封装特别有效:它允许我们简化函数API的使用,比如在用户忽略的情况下,自动给函数填充某些默认值,而在用户主动提供参数的情况下,替代那些默认值。这里我举两个现实中的例子:
假设我们有一个初始化函数,初始化函数允许用户通过结构体来配置一些参数: typedef struct xxxx_cfg_t { ... } xxxx_cfg_t;
int xxxx_init(xxxx_cfg_t *cfg_ptr); 为了简化用户的配置过程,初始化函数会检查指针cfg_ptr是否为NULL,如果为NULL则自动使用默认配置,反之将使用用户定义的配置。此时,我们可以通过宏来提供默认值NULL:
有些消息处理函数可以批量的处理某一类消息,而具体选中了哪些消息类别,则通常由二进制掩码来表示,例如: typedef struct msg_t msg_t; struct { uint16_t msg; uint16_t mask; int (*handler)(msg_t *msg_ptr); } msg_t;
/*! \note 高字节表示操作的类别: 比如0x00表示控制类,0x01表示WRITE,0x02表示READ */ enum { SIGN_UP = 0x0001, WRITE_MEM = 0x0100, WRITE_SRAM = 0x0101, WRITE_FLASH = 0x0102, WRITE_EEPROM = 0x0103, READ_MEM = 0x0200, READ_SRAM = 0x0201, READ_FLASH = 0x0202, READ_EEPROM = 0x0203, };
extern int iap_sign_up_handler(msg_t *msg_ptr); extern int iap_write_mem(msg_t *msg_ptr); extern int iap_read_mem(msg_t *msg_ptr);
def_msg_map( iap_message_map /* 严格的将 SIGN_UP 映射到 对应的处理函数中 */ add_msg( SIGN_UP, iap_sign_up_handler ), /* 批量处理所有的WRITE操作,使用掩码进行过滤*/ add_msg( WRITE_MEM, iap_write_mem, 0xFF00 ), /* 批量处理所有的READ操作,使用掩码进行过滤 */ add_msg( READ_MEM, iap_read_mem, 0xFF00 ), ) 需要特别注意的是,这种用法只有在以下两种情况下有效:
|
|
来自: 西北望msm66g9f > 《培训》