# 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. Mybatis 一级缓存和二级缓存原理区别 (图文详解)

    Java面试经常问到Mybatis一级缓存和二级缓存,今天就给大家重点详解Mybatis一级缓存和二级缓存原理与区别@mikechen Mybatis缓存 缓存就是内存中的数据,常常来自对数据库查询结 ...

  2. 第六章:Django 综合篇 - 15:Django与缓存

    我们都知道Django建立的是动态网站,正常情况下,每次请求过来都经历了这样一个过程: 接收请求 -> url路由 -> 视图处理 -> 数据库读写 -> 视图处理 -> ...

  3. 将 Docker Engine 节点从 dockershim 迁移到 cri-dockerd

    官网地址:https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/migrating-from-dockershim/migrate-doc ...

  4. 常见Content-Type(MIME)列表

    Content-Type(MIME)用于标识发送或接收数据的类型,浏览器根据该参数来决定数据的打开方式.多用于指定一些客户端自定义的文件,以及一些媒体文件的打开方式. 文件扩展名 Content-Ty ...

  5. Python实现给图片加水印功能

    前言 最近忙得连轴转,很久没更新博客了,代码倒是没啥写,积累了好些东西,接下来一有时间就来更新吧~ 本文记录使用Python实现给图片添加水印的功能实现过程 先看效果 把公众号的封面作为素材 原图是这 ...

  6. 为什么 CRM 能帮助装备制造企业进行数字化转型?

    CRM确实能帮助装备制造企业进行数字化转型,体现在销售管理端,能方便的服务于"客户需求"为主旨的数字化转型思想,用客户的需求来指导生产等.但这个说法有点片面,毕竟那只是客户管理部分 ...

  7. 撸了一个简易的配置中心,顺带整合到了SpringCloud

    大家好,我是三友~~ 最近突然心血来潮(就是闲的)就想着撸一个简单的配置中心,顺便也照葫芦画瓢给整合到SpringCloud. 本文大纲 配置中心的概述 随着历史的车轮不断的前进,技术不断的进步,单体 ...

  8. MySQL安装卸载、idea中Database的使用、常用的sql语句

    MySQL安装卸载 MySQL安装 在下面的资源链接中下载MySQL软件压缩包(绿色版),这个版本是MySQL5.7.29的,本教程也只适用于这个绿色版的,如果下载的是安装包那就可能有些地方不一样了, ...

  9. Jedis测试redis。(redis在linux虚拟机中)

    文章目录 1.确保虚拟机开启.并且连接到redis 2.建立一个maven工程 3.在pom中加入jedis的依赖 4.编写测试用例 5.测试结果 1.确保虚拟机开启.并且连接到redis 2.建立一 ...

  10. Java函数式编程:一、函数式接口,lambda表达式和方法引用

    Java函数式编程 什么是函数式编程 通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此我们会得到更加可靠的代码,并获得更高的效率 我们可以这样理解:面向对象编程抽象数据,函数式编程抽象 ...