C语言宏中“#”和“##”的用法


前言

使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起。只有充分了解到其中的用法,才能更好的看懂别人的代码以及自己手到擒来的使用。

#和##都属于预处理标记,即编译器会在预处理阶段进行相关的替换或者处理

一、(#) 字符串化操作符

"#"的功能是将其后面的宏参数进行字符串化操作,等同于把后面的宏变量加上双引号。
宏定义函数的参数与预处理标记 ‘#’ 之间出现的每一个空格都会被删除,并删除第一个预处理标记之前和最后一个预处理标记之后的空白字符,但是宏定义函数参数中的空格会保留。
空参数转化为空,即宏定义函数入参为空,那么展开的时候也为空。

#include <stdio.h>

#define MAKE_STR(s) (#s)

int main(void)
{
    printf("hello\n");
    printf(MAKE_STR(hello\n));

    return 0;
}

输出结果:
hello
hello

它具体是怎么实现的呢?其实很简单就是在其宏变量被替换,在其左右两侧加上双引号。为了探究这个过程,我们在用gcc编译的时候加上-E选项来看下编译器的预处理过程:

int main(void)
{
    printf("hello\n");
    printf(("hello\n"));

    return 0;
}

C语言会自动将相邻的两个字符串合并:

#include <stdio.h>

#define MAKE_STR(s) (#s"\n")

int main(void)
{
    printf("hello\n");
    printf(MAKE_STR(hello));

    return 0;
}

参数详解

宏定义函数的参数与预处理标记 ‘#’ 之间出现的每一个空格都会被删除,并删除第一个预处理标记之前和最后一个预处理标记之后的空白字符,但是宏定义函数参数中的空格会保留。

#include <stdio.h>

#define MAKE_STR(s) ("321" # s "654\n")

int main(void)
{
    printf("hello world\n");
    printf(MAKE_STR(hello world));

    return 0;
}

输出结果:
hello world
321hello world654

#和宏定义参数s之间的空格被删除掉了。字符串"321"和字符串"654"和# s之间的空格也被删除掉了。
但是宏参数hello world之间的空格并没有被删除。
注意:'#'只能用于传入参数的宏定义中,且必须置于宏定义体中的参数名前。

对于#的参数,即便是另一个宏,也不展开,仍然作为字符串字面信息输出。

#include<stdio.h>

#define dprint(expr) printf(#expr"=%d\n",expr); 

int main() 
{  
    int a=20,b=10;
    dprint(a/b);
    return 0; 
}

/*
   输出:a/b=2
 */

二、(##)符号连接操作符

作用:"##"被称为预处理拼接标记,宏定义展开的时候,用来将其左右两边两个token连接为一个token。注意这里连接的对象是token就行,不一定是宏的变量。

#include <stdio.h>

#define CONS(a,b)  int(a##e##b)
int main()
{
   printf("%d\n", CONS(2,3));  // 2e3 输出:2000
   return 0;
}

输出结果:
2000

常见用法1:在结构体定义中的妙用

比较多开源代码中惯用的做法,相比常规的结构体定义法,确实省去很多重复的代码。

#include <stdio.h>

#define DF_STRUCT(name) typedef struct tag##name name;\
                         struct tag##name
DF_STRUCT(DevManage)
{
    int index;   //索引 
    int Access;  //权限
                 //...  
};

int main(int argc, char *argv[]) {

    DevManage stDevManage;

    stDevManage.index  = 1;
    stDevManage.Access = 666;

    printf("Dev Index :%d\n",stDevManage.index );
    printf("Dev Access:%d\n",stDevManage.Access );

    return 1;
}

常见用法2:统一宏替换

拼接标识符意味着符号的粒度更高,而这碎片化的符号进行有效的管理,就可以使得符号更加具有通用性和灵活性。
其实这种思想跟我们代码模块话是同样的道理。

#include <stdio.h>
#include <stdlib.h>

//假如这是stm32库中的宏 
#define GPIO_Pin_0                 ((int)0x0001)  /*!< Pin 0 selected */
#define GPIO_Pin_1                 ((int)0x0002)  /*!< Pin 1 selected */
#define GPIO_Pin_2                 ((int)0x0004)  /*!< Pin 2 selected */
#define GPIO_Pin_3                 ((int)0x0008)  /*!< Pin 3 selected */

#define USART1              ((int *) 0x1000)
#define USART2              ((int *) 0x2000)

//拼接变量 
#define UARTX 1

//最终的组合标识符 
#define UART1_CORE  USART1
#define UART1_RX    GPIO_Pin_0
#define UART1_TX    GPIO_Pin_1

#define UART2_CORE  USART2
#define UART2_RX    GPIO_Pin_2
#define UART2_TX    GPIO_Pin_3

//拼接过程 
#define _UARTX_CORE(uartx)   UART##uartx##_CORE 
#define UARTX_CORE(uartx)    _UARTX_CORE(uartx)

#define _UARTX_RX(uartx)   UART##uartx##_RX
#define UARTX_RX(uartx)    _UARTX_RX(uartx) 

#define _UARTX_TX(uartx)   UART##uartx##_TX
#define UARTX_TX(uartx)    _UARTX_TX(uartx)

int main(int argc, char *argv[]) {

    //组合标识符的使用 
    printf("0x%x\n",UARTX_CORE(UARTX));
    printf("0x%x\n",UARTX_RX(UARTX));
    printf("0x%x\n",UARTX_TX(UARTX));

    return 1;
}
c C语言提供的一种定义功能,它可以将一段代码片段定义为一个,并使用名来替代这段代码,从而简化代码的书写。 定义的格式为: #define 名(参数列表) 代码片段 其中,名可以是任意合法的标识符,参数列表可以为空或包含多个参数,代码片段则是需要被替代的代码。 在使用时,我们只需使用名加上参数列表来表示该,编译器会在预处理阶段将名替换为对应的代码。 定义可以用于定义常量、函数以及代码块等,有利于提高代码的可读性和可维护性。例如,我们可以使用定义来定义一个常量: #define PI 3.14159 这样在代码中使用PI时就可以替代为3.14159,方便代码的书写和阅读。还可以定义一个函数: #define MAX(a, b) ((a) > (b) ? (a) : (b)) 这个函数可以取两个数中的最大值,使用时可以直接调用MAX(a, b)来得到结果。 但是定义也有一些潜在的问题,例如展开可能会导致代码膨胀、替换可能产生副作用以及参数不具备类型信息等。因此,在使用定义时需要慎重考虑,确保合理使用定义,避免出现问题。 综上所述,c 是一种 C 语言提供的定义功能,可以将一段代码片段定义为一个,并使用名来替代这段代码,方便代码的书写和阅读,提高代码的可读性和可维护性。同时也需要注意定义的潜在问题,合理使用定义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诊断协议那些事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值