define的一些骚操作:##操作连接符、#@字符化操作符、#字符串化操作符、\行继续操作 您所在的位置:网站首页 骚操作歇后语 define的一些骚操作:##操作连接符、#@字符化操作符、#字符串化操作符、\行继续操作

define的一些骚操作:##操作连接符、#@字符化操作符、#字符串化操作符、\行继续操作

2024-07-17 23:02| 来源: 网络整理| 查看: 265

前言

众所周知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 #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 #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 #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 #include /** * \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 #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 不可以



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有