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

前言

众所周知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 不可以

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值