前言
众所周知define用于宏定义,在预编译阶段处理;下面举例些define的骚操作,看如下三条语句:
#define Conn(x,y) x##y
#define ToChar(x) #@x
#define ToString(x) #x
一、##操作连接符
##
表示连接 ,x##y
表示什么?表示x
连接y
,举例说:
int n = Conn(123,456);
==> int n=123456;
char* str = Conn("asdf", "adf");
==> char* str = "asdfadf";
怎么样,很神奇吧
需要注意的是,##
的左右符号必须能够组成一个有意义的符号,否则预处理器会报错。
二、#@字符化操作符
#@x
只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。作用是将传的单字符参数名转换成字符,以一对单引用括起来其实就是给x
加上单引号,结果返回是一个const char
。
char a = ToChar(1);
==> char a='1';
做一个越界试验:
char a = ToChar(123);
==> char a='3';
但是如果你的参数超过四个字符,编译器就给给你报错了
!error C2015: too many characters in constant :P
三、#字符串化操作符
#
表示字符串化操作符(stringification
)。其作用是:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。说白了,他是给x
加双引号:
char* str = ToString(123132);
==> char* str="123132";
如果想要对展开后的宏参数进行字符串化,则需要使用两层宏。
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)
==> "foo"
xstr (foo)
==> xstr (4)
==> str (4)
==> "4"
s
参数在str
宏中被字符串化,所以它不是优先被宏展开。然而s
参数是xstr
宏的一个普通参数,在被传递到str
宏之前已经被宏展开。
四、\行继续操作
\
行继续操作当定义的宏不能用一行表达完整时,可以用\
(反斜线)表示下一行继续此宏的定义。
例子1:
#include <stdio.h>
#define ELE 123, \
234, \
456
int main(int argc, char *argv[])
{
int i = 0;
int arr[] = { ELE };
printf("Elements of Array are:\n");
for (i = 0; i < 3; i++)
{
printf("%d \n", arr[i]);
}
return 0;
}
注意:最后一行不要加续行符!!!!!!
例子1运行结果如下:
例子2:
#include <stdio.h>
#define MACRO(n, limit) while (n < limit) \
{ \
printf("minger\n"); \
n++; \
}
int main(int argc, char *argv[])
{
int n = 0;
MACRO(n, 6);
return 0;
}
例子2运行结果如下:
VC的预处理器在编译之前会自动将\与换行回车去掉 (写成多行时,反斜杠后不能有空格,否则编译器(ARM或VC)会报错!),这样一来既不影响阅读,又不影响逻辑,皆大欢喜。
五、__VA_ARGS__可变参数宏
可变参数宏是具有可变数量参数的宏(也可以用 C 编写可变参数函数)。下面是一个例子:
#include <stdio.h>
#define debugPrintf(...) printf("DEBUG: " __VA_ARGS__);
int main(int argc, char** argv)
{
debugPrintf("Hello World!\n");
return 0;
}
简单来说,...表示所有剩下的参数,__VA_ARGS__被宏定义中的...参数所替换。这在c语言的GNU扩展语法里是一个特殊规则:当__VA_ARGS__为空时,会消除前面这个逗号。
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
eprintf ("%s:%d: ", input_file, lineno)
==> fprintf (stderr, "%s:%d: ", input_file, lineno)
当__VA_ARGS__
宏前面##
时,可以省略参数输入。例如:
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
eprintf ("success!\n")
==> fprintf(stderr, "success!\n");
六、应用:使用##简化函数名
#ifndef RINGBUFF_HDR_H
#define RINGBUFF_HDR_H
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include <stdint.h>
/**
* \defgroup RINGBUFF Ring buffer
* \brief Generic ring buffer manager
* \{
*/
/* --- Buffer unique part starts --- */
/**
* \brief Buffer function/typedef prefix string
*
* It is used to change function names in zero time to easily re-use same library between applications.
* Use `#define BUF_PREF(x) my_prefix_ ## x` to change all function names to (for example) `my_prefix_buff_init`
*
* \note Modification of this macro must be done in header and source file aswell
*/
#define BUF_PREF(x) ring ## x
/* --- Buffer unique part ends --- */
/**
* \brief Buffer structure
*/
typedef struct {
uint8_t* buff; /*!< Pointer to buffer data.
Buffer is considered initialized when `buff != NULL` and `size` */
size_t size; /*!< Size of buffer data. Size of actual buffer is `1` byte less than value holds */
size_t r; /*!< Next read pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */
size_t w; /*!< Next write pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */
} BUF_PREF(buff_t);
uint8_t BUF_PREF(buff_init)(BUF_PREF(buff_t)* buff, void* buffdata, size_t size);
void BUF_PREF(buff_free)(BUF_PREF(buff_t)* buff);
void BUF_PREF(buff_reset)(BUF_PREF(buff_t)* buff);
/* Read/Write functions */
size_t BUF_PREF(buff_write)(BUF_PREF(buff_t)* buff, const void* data, size_t btw);
size_t BUF_PREF(buff_read)(BUF_PREF(buff_t)* buff, void* data, size_t btr);
size_t BUF_PREF(buff_peek)(BUF_PREF(buff_t)* buff, size_t skip_count, void* data, size_t btp);
/* Buffer size information */
size_t BUF_PREF(buff_get_free)(BUF_PREF(buff_t)* buff);
size_t BUF_PREF(buff_get_full)(BUF_PREF(buff_t)* buff);
/* Read data block management */
void * BUF_PREF(buff_get_linear_block_read_address)(BUF_PREF(buff_t)* buff);
size_t BUF_PREF(buff_get_linear_block_read_length)(BUF_PREF(buff_t)* buff);
size_t BUF_PREF(buff_skip)(BUF_PREF(buff_t)* buff, size_t len);
/* Write data block management */
void * BUF_PREF(buff_get_linear_block_write_address)(BUF_PREF(buff_t)* buff);
size_t BUF_PREF(buff_get_linear_block_write_length)(BUF_PREF(buff_t)* buff);
size_t BUF_PREF(buff_advance)(BUF_PREF(buff_t)* buff, size_t len);
#undef BUF_PREF /* Prefix not needed anymore */
/**
* \}
*/
#ifdef __cplusplus
}
#endif
#endif /* RINGBUFF_HDR_H */
以上代码实现的是一个环形缓冲,然而他巧妙的将ring这个字串去掉,最后阅读代码看到的是非常整齐的:
BUF_PREF(buffer_init)
BUF_PREF(buff_free)
BUF_PREF(buff_write)
BUF_PREF(buff_read)
等等。。。
接下来看看到底是怎么用的:
#define BUF_PREF(x) ring ## x
"##" 表示将左边的字符串和右边的字符串连接起来,但是只能黏贴C语言除了关键字以外的合法标识符 于是上面展开的效果如下:
ring_buffer_init
ring_buffer_free
ring_buffer_write
ring_buffer_read
等等。。。
扩展下,之前写LED驱动或者别的可能是这样的,定义了这么多个函数
void led_device_open(void);
void led_device_close(void);
uint8_t led_device_read(void);
uint8_t led_device_write(uint8_t status);
。。。
看起来很统一,可一眼看出这是一个LED的操作方法,但操作一个LED不就是open,close,read,write方法吗?
可以让它看起来更优雅:
#define LED_CLASS(x) led_device_ ## x
void LED_CLASS(open)(void);
void LED_CLASS(close)(void);
uint8_t LED_CLASS(read)(void);
uint8_t LED_CLASS(write)(uint8_t status);
如果我写另外一个驱动,也是一样有open,close,read,write接口,假设是个FLASH设备。那还是一样的:
#define FLASH_CLASS(x) flash_device_ ## x
void FLASH_CLASS(open)(void);
void FLASH_CLASS(close)(void);
uint8_t FLASH_CLASS(read)(void);
uint8_t FLASH_CLASS(write)(uint8_t status);
另一个例子
#include <stdio.h>
#define Print(x) printf("%s %d\n",#x,x);
int main(void)
{
Print(100);
return 0 ;
}
运行结果:
七、应用:行继续操作定义枚举
/*使用*/
#define ENUM_ITEM(ITEM) ITEM,
/*使用字符串化操作符,将ITEM字符串化*/
#define ENUM_STRING(ITEM) #ITEM,
/*使用define定义以简化枚举*/
#define KEY_STATUS_ENUM(STATUS) \
STATUS(KS_RELEASE) /*稳定松开状态*/ \
STATUS(KS_PRESS_SHAKE) /*按下抖动状态*/ \
STATUS(KS_PRESS) /*稳定按下状态*/ \
STATUS(KS_RELEASE_SHAKE) /*松开抖动状态*/ \
STATUS(KS_NUM) /*状态总数(无效状态)*/ \
/*由于上面使用了define,此处枚举得以简化*/
typedef enum
{
KEY_STATUS_ENUM(ENUM_ITEM)
}KEY_STATUS;
/*
char型数组指针
*/
const char* key_status_name[] = {
KEY_STATUS_ENUM(ENUM_STRING)
};
KEY_STATUS g_keyStatus = KS_RELEASE; //当前循环结束的(状态机的)状态
八、define与typedef使用中的区别
区别一
#define 与 typedef 都可以用来给现有类型起别名,但是 #define 只是简单宏替换,而 typedef
不是的,#define 在预编译时被处理,typedef 是在编译时被处理。
#define dpchar char *;
typedef char * tpchar;
dpchar p1, p2; // 只是做简单的替换,等价于 char *p1, p2; 只有 p1 才是指针变量
tpchar p1, p2; // 不是简单的类型替换,等价于 char *p1, *p2; p1,p2 都是指针变量
区别二
#define 方式可实现类型组合,但是 typedef 不行,如下所示。
#define dint int;
typedef int tint;
unsigned dInt p1, p2; // 正确,等价于 unsigned int p1, p2
unsigned tInt p1, p2; // 不可以
区别三
typedef 可以组建新类型,但是 #define 不行,如下所示
typedef char[200] charBuf;
charBuf buf; // 等价于 char buf[200],但是 #define 不可以