# define 的神奇操作

一、宏定义中的 #、## 符号的神奇用法

1.1 # 的用法

1.1.1 作用

#表示字符串化操作符(stringification),其作用是将宏定义中的传入参数名转换成用双引号括起来的参数名字符串

现在对这句话是不是还不甚理解,没关系,让我们接着往下走。

1.1.2 举例说明

#include <stdio.h>

#define ToString(str) #str

int main()
{
char str[] = ToString(hello world); // 等价于 "hello world";
}

根据例子,我们再来理解一下#的作用:将宏定义(ToString)中的传入参数名(hello world)转化成用双引号括起来的参数名字符串("hello world")。

是不是有种恍然大明白的感觉~

1.1.3 实战

假设给你一个 JSON 字符串:

{"name" : "张三"}

如何以 C 语言的形式输出呢?

你可以这么做char str[] = "{\"name\":\"张三\"}";,通过使用转义字符\"使得代码可以保存引号"

既然学习了宏定义中#的作用,那么我们通过#修改一下:

#define ToString(str)   #str
int main()
{
char str[] = ToString({"name":"张三"});
}

好处就是不用添加\,阅读起来更直观。

1.2 ## 的用法

1.2.1 作用

##表示连接,将宏定义中的一个或多个形参转换成一个实际的参数名

1.2.2 举例说明

老规矩,先上代码再解释:

#include <stdio.h>

#define Conn(x, y) x##y

int main()
{
int num1 = 10;
int n = Conn(num, 1); // n = num1 return 0;
}

根据例子,我们再来理解一下##的作用:将宏定义(Conn)中的多个形参(num 和 1)转换成一个实际的参数名(num1)。

或者你也可以这么写:

#include <stdio.h>

#define Conn(x) num##x

int main()
{
int num1 = 10;
int n = Conn(1); // n = num1 return 0;
}

将宏定义(Conn)中的一个形参(1)转化为实际的参数名(num1)。

1.2.3 错误用法

通过上面的举例,你现在是不是已经明白了##的作用了,那么来分析一下下述代码的输出结果:

#include <stdio.h>

#define Conn(x) num##x

int main()
{
int num1 = 1;
int num2 = 2;
int num3 = 3;
int numi = 100; for (int i = 1; i <= 3; i++)
{
int num = Conn(i);
printf("num = %d\n", num);
} return 0;
}

期望的输出结果是不是:

  • num = 1
  • num = 2
  • num = 3

下面让我们看一下实际运行结果:

是不是很意外?Conn 宏不是起连接作用吗为什么输出的结果不是预期呢?

还记得 gcc 的 -E 指令吗,让我们通过 -E 看一下 gcc 预编译的文件:

下面让我们来分析一下 main.i 文件内容:

int main()
{
int num1 = 1;
int num2 = 2;
int num3 = 3;
int numi = 100; for (int i = 1; i <= 3; i++)
{
int num = numi;
printf("num = %d\n", num);
} return 0;
}

是不是找到问题所在了。原因在于Conn(i)并没有按照我们的预期依次替换为 num1、num2、num3,而是替换为了 numi。

将宏 Conn(i) 中 i 的值替换为其实际的整数值是不可能的,这是因为宏替换发生在代码编译之前,也就是说宏替换的时候并不知道 i 是一个 int 型的变量,仅仅将其当做一个字符处理。

解释参考自问题评论:将变量的值传递给C中的宏 - 或代码 (orcode.com)

二、实际使用

最近在看 ONVIF 代码的时候,看到一个神奇的操作,下面是简化版本:

#include <stdio.h>

/* 函数声明 */
#define DECLARE(x) \
void Func_##x(int a); /* 函数定义 */
#define DEFINE(x) \
void Func_##x(int a) \
{ \
printf("a 的值为 %d, %s 型\n", a, #x); \
} /* 函数创建 */
#define CREATE(x) \
Func_##x DECLARE(int)
DEFINE(int) int main()
{
CREATE(int)(10); return 0;
}

在学习了###的用法后,上面的代码就迎刃而解了,转化成普通的代码为:

#include <stdio.h>

void Func_int(int a);       // 声明函数 --- DECLARE(int)
void Func_int(int a) // 定义函数 --- DEFINE(int)
{
printf("a 的值为 %d, %s 型\n", a, "int");
} int main()
{
Func_int(10); // 使用函数 --- CREATE(int)(10) return 0;
}

参考资料

#define 的神奇操作的更多相关文章

  1. Python 5 行代码的神奇操作

    Python 语言实现功能直接了当,简明扼要,今天咱们就来一起看看 Python 5 行代码的神奇操作! 1.古典兔子问题 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语 ...

  2. 前缀和的n个神奇操作

    前情回顾 前缀和的基础用法戳这里->传送门 众所周知,简单的前缀和解决的一般都是静态查询的问题,例如区间和.区间积等 操作的时候也很简单,就是根据需要来维护一个数组,每次查询的时候就用到tr[r ...

  3. JZs3c2440裸板程序GPIO操作总结

    分别用汇编,汇编+C两种方式 ***************************************汇编编程led_on.s********************************** ...

  4. 使用iOS原生sqlite3框架对sqlite数据库进行操作

    摘要: iOS中sqlite3框架可以很好的对sqlite数据库进行支持,通过面向对象的封装,可以更易于开发者使用. 使用iOS原生sqlite3框架对sqlite数据库进行操作 一.引言 sqlit ...

  5. 图的存储结构与操作--C语言实现

    图(graph)是一种比树结构还要复杂的数据结构,它的术语,存储方式,遍历方式,用途都比较广,所以如果想要一次性完成所有的代码,那代码会非常长.所以,我将分两次来完成图的代码.这一次,我会完成图的五种 ...

  6. Delphi 2007体验!

    Delphi 2007体验! baidu 内容摘要:CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本号.作为一个 Delphi 的使用者,第 ...

  7. 【题解】 bzoj3693: 圆桌会议 (线段树+霍尔定理)

    bzoj3693 Solution: 显然我们可以把人和位置抽象成点,就成了一个二分图,然后就可以用霍尔定理判断是否能有解 一开始我随便YY了一个\(check\)的方法:就是每次向后一组,我们就把那 ...

  8. 在平衡树的海洋中畅游(四)——FHQ Treap

    Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...

  9. 【[SHOI2007]园丁的烦恼】

    \(CDQ\) 分治的神奇操作 这个问题跟偏序问题好像差的不小啊 但是就是可以转化过去 对于一个查询我们可以把它拆成四个,也就是用二维前缀和的方式来查询 我们发现其实前缀和的定义就是多少个点的横纵坐标 ...

  10. 牛客网 暑期ACM多校训练营(第二场)J.farm-STL(vector)+二维树状数组区间更新、单点查询 or 大暴力?

    开心.jpg J.farm 先解释一下题意,题意就是一个n*m的矩形区域,每个点代表一个植物,然后不同的植物对应不同的适合的肥料k,如果植物被撒上不适合的肥料就会死掉.然后题目将每个点适合的肥料种类( ...

随机推荐

  1. thinkphp5.1 cookie跨域、thinkphp5.1 session跨域、tp5.1cookie跨域

    cookie跨域: //config/cookie.php return [ //... //仅7.3.0及以上适用 'samesite' => 'None', //是否加密cookie值,fa ...

  2. .env[mode]文件中如何添加注释

    前言 Vue-Cli 允许我们在项目根目录创建.env.[mode]文件来设置一些打包编译的启动参数,通过执行脚本的时候加mode参数,指定不同环境需要加载的配置文件 形如: .env.prod NO ...

  3. Elasticsearch:fielddata 介绍

    默认情况下,大多数字段都已编入索引,这使它们可搜索. 但是,脚本中的排序,聚合和访问字段值需要与搜索不同的访问模式. 搜索需要回答"哪个文档包含该术语?"这个问题,而排序和汇总则需 ...

  4. Docker Compose配置文件详解(V3)

    Docker Compose配置文件是Docker Compose的核心,用于定义服务.网络和数据卷.格式为YAML,默认路径为./docker-compose.yml,可以使用.yml或.yaml扩 ...

  5. 【前端必会】Prettier,有了ESlint,还要Prettier

    介绍 已经安装了ESLint为什么还要Prettier,主要是让ESLint专注于语法相关的验证,检查潜在问题.而代码格式化则有Prettier来接管 对比参考: https://baijiahao. ...

  6. ToroiseGit/GitBash 设置提交信息模板设置

    一.背景:当使用git提交代码时,每次的提交信息固定,却又比较长不好记的时,还需要将模板的地址保存下来,如果能设置一个固定的模板就可以很好的解决这个问题. 提交前的提交信息需要手动输入: 二.Toro ...

  7. P2216 [HAOI2007]理想的正方形 方法记录

    [HAOI2007]理想的正方形 题目描述 有一个 \(a \times b\) 的整数组成的矩阵,现请你从中找出一个 \(n \times n\) 的正方形区域,使得该区域所有数中的最大值和最小值的 ...

  8. 华为交换机STP常用命令

    STP配置和选路规则 stp enable 在交换机上启用STP stp mode stp dis stp 查看stp配置 dis stp brief 查看接口摘要信息 stp priority 40 ...

  9. 齐博x1工单碎片模板制作教程

    可以把工单插入到任何频道的内容里边,如下图所示 碎片模板制作标准如下 <form action="{:urls('order/add')}" class="wn_f ...

  10. 5.websocket原理

      websocket协议原理 1.WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符.它实现了浏览器与 ...