C语言系列文章之#和##

很久就知道了 # 和 ## ,但是都没怎么使用,直到最近的项目涉及到需要编写大量相似的代码之后才决定尝试使用 ## 去简化代码的书写。

比如说我的项目需要控制四个通道的电机,四个通道的逻辑控制代码都是类似的,只是对应的硬件和数据信息不同而已。而我是一个讨厌做重复工作的人,所以就想利用 ## 去简化我的代码书写。

就比如说代码初始化这一块,总共有四份相似的代码,如果每一个都要去源码的位置进行修改,麻烦不说,还有可能忘记修改某部分代码,导致程序的bug。对此比较好的方式就是使用宏定义,将所有需要修改的东西整合在一个位置,这样进行修改的时候就能一次性修改完成,就不用在源码中到处找哪里需要修改了。而为了更好的配合这种修改方式,使用 ## 就很有必要了。

下面看看在我项目中的应用吧!

以两个通道为例:

.c文件

640?wx_fmt=png

.h文件

640?wx_fmt=png

使用 ## 的宏定义

640?wx_fmt=png

当我需要初始化新的通道的时候,在源码位置只要修改一个地方就行就行,而头文件部分的修改不管采用哪种方式都是必不可少的,这没什么好说的。而当采用 ## 的方式编写代码,你后期如果需要增加一个通道的初始化代码,你的工作就是复制前面通道的代码,然后修改源代码的那一个宏定义,最后在头文件中统一修改引脚信息就行了。

而实现这种方式的关键就是 ##,它的作用就是将你写的标志符进行拼接,相当于自动实现代码的编写了。

上面那么多话如果没有实际使用经验其实不是特别好理解,现在以比较简单的方式进行说明。

在对引脚进行初始化的时候,一般需要端口信息、引脚信息(当然还需要对应的时钟信息,暂时不考虑)

使用库函数进行开发时一般是采用以下方式进行引脚初始化:

640?wx_fmt=png

只有单个引脚的时候,只要修改这两个地方就能实现一个引脚的初始化,还是比较简单的。但是如果有多个相同模式的引脚需要进行这样的初始化,就会显得很麻烦。而且一旦硬件修改了,就可能会有很多地方需要修改,很可能就忘记某个地方的修改了。所以为了方便的对需要修改的地方进行修改,就很有必要使用一个标志符去代替具体的信息。所以一般就会采用宏定义的方式:

640?wx_fmt=png

这样一旦后期要改了,因为已经将所有要修改的地方都放在一块了,那么即使过去了很久,也能很快的根据硬件开发板进行修改。而不会说忘记修改哪部分代码了。

但这是比较常规的写法,在没大量相似代码的情况下还是很方便的,但是如果说我有两、三个SPI_MISO引脚呢?我怎样才能更快的写出我的代码呢?就是使用 ## 进行拼接了。

首先你要确定你变化的是什么,不变的又是什么。变化的就是不同的引脚,不变的就是都是开漏复用输出等配置。

现在看看如何实现。

首先看看常规的操作:

640?wx_fmt=png

当修改完这两个地方时,第二个SPI的引脚就算是配置完成了。但是万一你忘记修改了一个地方呢?所以,现在想办法只修改一个地方,从而达到修改两个地方的目的(多个地方修改也是同理)。

640?wx_fmt=png

这就是 ## 的好处了。

现在讲讲如何正确使用 ##。

和宏定义一样,遇到相同字符就会被替换,比如:

640?wx_fmt=png

这里有两个spix,所以两个都被替换了成了1,然后通过 ## 进行拼接。这个可以通过编译看出来最终拼出来的是什么(在拼出问题的情况下才能看到。当然你也可以直接让鼠标指针选择被拼接的地方也能看出来,这样就不用进行编译了)。

640?wx_fmt=png

如果说你传入的参数不是 1,而是一个宏定义(就像SPIx是一个宏定义),那么你就需要加一个外壳进行二次替换才行。

640?wx_fmt=png

很多时候我们要求被替换的东西加入一对括号 (),但使用 ## 进行拼接的时候却最好不要,否则可能拼接失败(如果拼接失败,看看是否是因为这个):

640?wx_fmt=png

如果有多个变化的地方,那么宏中传入多个参数就可以了,类似这样的:

640?wx_fmt=png

如果拼接失败,请先确保你传入的参数是 MISO,而不是 MOSI:

640?wx_fmt=png

以上是比较常用的使用方式,但有时候我们真的很懒,两个没有直接联系但有间接联系的参数我也想通过某种方式进行拼接:

640?wx_fmt=png

可以看到,在其他宏没有改变的情况下,虽然我传入的参数是Sx为4,4可以说和最终的拼接 SPI1_MISO_GPIO_Pin_x 没有任何关系,但是通过再次拼接参数宏的方式硬是让最后的拼接结果变成了 SPI1_MISO_GPIO_Pin_x。

现在简单分析一下拼接的过程:

首先编译器碰到了 SIPx(Sx),然后 Sx是 4,通过中间转换宏:

#define SPIx(x)                           _SPIx(x)

                                            

变成了 _SPI(4),然后通过宏:

#define _SPIx(x)                           SPI##x##_MISO

拼接成了 SPI4_MISO,也就是:

SPIx_MISO_GPIO_Pin_x(SPI4_MISO)

因为SPI4_MISO也是一个宏,所以继续通过转换宏:

#define SPIx_MISO_GPIO_Pin_x(spix)       _SPIx_MISO_GPIO_Pin_x(spix)

在这里 SPI4_MISO 被替换成了 1,也就变成了这个:

_SPIx_MISO_GPIO_Pin_x(1)

但还没完,继续替换,通过下面的宏:

#define _SPIx_MISO_GPIO_Pin_x(spix)     SPI##spix##_MISO_GPIO_Pin_x

拼接为:

SPI1_MISO_GPIO_Pin_x

但是它还是一个宏,所以 SPI1_MISO_GPIO_Pin_x继续被替换为:

GPIO_Pin_4

这就算替换完了吗?非也,实际上 GPIO_Pin_4 也是一个宏:

640?wx_fmt=png

所以继续替换,最终替换成了:

((uint16_t)0x0010)

是不是感觉好麻烦啊,为了一个 ((uint16_t)0x0010) 不知道走了多少弯路,不过总算是替换完了。你一个新手确实觉得难,但理解之后也就不难了。而高手写代码时写这么多的宏,不会觉得累吗?其实不是的,相比于不写宏造成的后果,还是写宏来的简单一些,高手经历的更多,当然也就考虑的更多,他们既然选择写宏,就有他们的道理,等新手有足够的经验之后,也就明白了写宏的用意,也就乐意写宏了。

以上就是我使用 ## 时遇到的一些问题。要理解 ## 其实不难,多想多尝试就可以了。现在你从头开始看看吧,或许会有不一样的理解。

现在讲讲 #。这个比较简单一点,鱼鹰也没怎么用过,就简单介绍一下吧。

1、 使用 # 让传入的宏参数变成一个字符串:

#define  STRING(x)    #x

则STRING (1+1)  相当于  “1+1”,即把 1+1 变成了可以放在数组中的数据了。

2、 使用 #@ ,让参数变成字符

#define CHAR(x)     #@x

则CHAR (a) 即‘a’,B(1)即’1’这就变成了可以把它放入变量中的字符了。但B(abc)

却不甚有效。这是因为 abc不是一个字符。

-----------------2018/12/16 Osprey

640?wx_fmt=png

看以上示例,包含 ## 的宏既可以传入字符,也可以传入你需要的参数。可以根据你的需要设计所需的宏。       

----------更新 2019/01/07 Osprey

640?wx_fmt=png

640?wx_fmt=jpeg

扫码或长按关注

回复「 加群 」进入技术群聊

   

640?wx_fmt=gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java ioc和aop的含义_Spring核心IoC和AOP的理解

spring 框架的优点是一个轻量级笔记简单易学的框架,实际使用中的有点优点有哪些呢!1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦2.可以使用容易提供的众多服务,如事务管理,消息服务等3.容器提供单例模式支持4.…

springboot 上传文件_基于SpringBoot的文件上传

在实际的企业开发中,文件上传是最常见的功能之一,SpringBoot集成了SpringMVC常用的功能,当然也包含了文件上传的功能,实现起来没有太多的区别。下面我们来讲解一下,使用SpringBoot如何实现多个文件上传操作。使用的环境…

看雪KSSD-windows驱动

KanXue Software Security Documentation ,简称KSSD ,是一个在线的文档浏览查阅系统 1. 书籍推荐 《windows 2000 内部揭密>>--------------------------------------这里面可以学到什么是IRP、内存管理、文件系统之类 《驱动模型设计>>-------------------------…

Linux的 i2c 驱动框架分析

1.基本概念总线设备驱动模型,是Linux 内核的一个基础,基本理论可以说按照大企业的分工原则,每个人只要负责自己的事情,向其他部门给出标准的接口调用,后勤部就负责后勤工作,厨房有可能跟后勤部产生工作上的…

java ee程序设计师_软件设计师:Java EE开发四大常用框架[1]

StrutsStruts是一个基于Sun Java EE平台的MVC框架,主要是采用Servlet和JSP技术来实现的。Struts框架可分为以下四个主要部分,其中三个就和MVC模式紧密相关:1、模型 (Model),本质上来说在Struts中Model是一个Action类(这个会在后面…

26、字符串的操作

字符串的操作 一、字符串的反向输出(逆置) /*2017年3月17日13:18:39功能:将字符串反向输出 */ #include"stdio.h" int main () {char a[100];char b[100];printf("please input one array: ");gets(a);char *pa a;char …

matlab fftshift_数字信号处理没有Matlab?用Python一样很爽

通常,在数字信号处理时,我们避不开matlab这个工具,因其它的强大的功能受到广大工程师的好评,也一直都是业界的不二之选。但是,matlab毕竟是商业软件,公司里如果使用的话,就需要支付高昂的费用。…

栈,C语言实现

什么是数据结构?数据结构是什么?要了解数据结构,我们要先明白数据和结构,数据就是一些int char 这样的变量,这些就是数据,如果你是一个篮球爱好者,那么你的球鞋就是你的数据,结构就是…

Camera摄像头工作原理

回想这工作的这几年,尝尽社会的辛酸艰难,从一开始什么都没有到30万,从30万到200万,从200万到1300万,不是炫耀,我只是想通过我自己的经历告诉我的朋友们「手机像素越高,拍的照片越清晰」摄像头结…

es6一维数组转二维数组_技术图文:Numpy 一维数组 VS. Pandas Series

背景Numpy 提供的最重要的数据结构是 ndarray,它是 Python 中 list 的扩展。Pandas 提供了两种非常重要的数据结构 Series和DataFrame。Numpy 中的一维数组与 Series 相似,一维数组只是提供了从0开始与位置有关的索引,而Series除了位置索引之…

unity UI事件

由于工作需要到持续按键,所以了解了一下unity UI事件,本文主要转载于http://www.cnblogs.com/zou90512/p/3995932.html?utm_sourcetuicool&utm_mediumreferral,并对相关问题进行解释。 我们最常用到的就是unity的button组件,…

java sar包_linux下查看最占性能的JAVA进程

记录一下自己常用的linux系统命令,方便以后查阅,发觉记忆越来越不行了找到最耗CPU的线程ps命令命令:ps -mp pid -oTHREAD,tid,time或者ps -Lfp pid结果展示:这个命令的作用,主要是可以获取到对应一个进程下的线程的一些…

电子工程学院的师兄弟姐们们,老师叫你们回家

昨天写了很长的文章,接收到推送的同学们应该也会很开心,但是由于我的原因,需要把文章删除「你们能想到的原因肯定不是我删文的原因」,但是呢,也因为这样,又可以重写一篇,刚好可以多加点内容。后…

auto.js停止所有线程_Java多线程编程基础知识 概念介绍,以及线程状态

一、进程进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动。操作系统中,几乎所有运行中的任务对应一条进程(Process)。一个程序进入内存运行,即变成一个进程。进程是处于运行过程中…

android与js交互

首先引用一篇文章,看过这篇文章基本上就明白android大致与js是如何交互的了 Android与HTMLJS交互入门 ----------------------------分割线----------------------------------- 首先要知道js是啥,js就相当于在html内的函数方法,全称为javasc…

php调用md5.js,js中怎么使用md5加密

首先引入用法:hex_md5("123456");md5.js 文件下载:/** A JavaScript implementation of the RSA Data Security, Inc. MD5 Message* Digest Algorithm, as defined in RFC 1321.* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.* Othe…

Linux 进程管理数据结构

文末集赞留言抽奖,我会选出留言点赞数前 3 名送出小米耳机。别刷赞啊,刷赞被举报无效,相信真的是公众号粉丝的读者,不会做这样的行为,刷赞指的是购买外挂刷,如果是转发到朋友圈和微信群的,不算刷…

USB设备驱动之设备初始化(设备枚举)

USB设备从接入HUB到正常工作之前。都属于设备枚举阶段。所谓设备枚举。就是让host控制器认识USB设备,并为其准备资源。建立好主机与设备间的数据传递机制。 该阶段的工作,是USB通信协议规定的,所以属于ISO标准流程。设备枚举阶段也相应了USB设…

如何安装python3.8_python3.8下载及安装步骤详解

1.操作系统:Windows7 64bit executable installer 2.安装步骤: 双击安装文件python-3.8.0-amd64.exe 勾选下方“Add Python 3.8 to PATH”,并选择“Customize installation”3.把Optional Features全部勾选上,点击“Next"4.A…

两台linux之间互传php脚本,linux下两台服务器实现同步的方法

本文主要和大家分享linux下实现两台服务器实时同步方法介绍,假设两个服务器:192.168.0.1 源服务器 有目录 /opt/test/和192.168.0.2 目标服务器 有目录 /opt/bak/test/,实现的目的就是保持这两个服务器某个文件目录保持实时同步。实现方式&am…