C语言#define和宏

C语言#define和宏

  • #define定义标识符
  • #define定义宏
  • #define的替换规则
  • '#' 和 '##'
  • '##'的作用
  • 带副作用的宏参数
  • 宏和函数对比
  • 命名约定

#define定义标识符

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字 reg
#define do_forever for(;;)     //用更形象的符号来替换一种实现死循环 
#define CASE break;case        //在写case语句的时候自动把 break写上。

如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。

例如:

#define DEBUG_PRINT \
printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" , \
__FILE__,__LINE__ , \    
__DATE__,__TIME__ )  

#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义(define macro)。

下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
如:

#define SQUARE(X) X*X

这个宏接收一个参数 x .
如果在上述声明之后,你把

SQUARE( 5 );

置于程序中,预处理器就会用下面这个表达式替换上面的表达式:

5*5

警告:
这个宏存在一个问题:
观察下面的代码段:

int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

乍一看,你可能觉得这段代码将打印36这个值。
事实上,它将打印11.
为什么?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf (“%d\n”,a + 1 * a + 1 );

这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。
在宏定义上加上两个括号,这个问题便轻松的解决了:

#define SQUARE(x) (x) * (x)

这样预处理之后就产生了预期的效果:

printf ("%d\n",(a + 1) * (a + 1) );

这里还有一个宏定义:

#define DOUBLE(X) (X)+(X)

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

这将打印什么值呢?
warning:
看上去,好像打印100,但事实上打印的是55.
我们发现替换之后:

printf ("%d\n",10 * (5) + (5));

乘法运算先于宏定义的加法,所以出现了
55 .
这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE(X) ((X)+(X))

总结:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,不要吝啬括号,有整体要加括号。避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
例如:

#define MAX(X,Y) ((X)>(Y)?(X):(Y))

#define的替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
    被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
    述处理过程。
    注意:
  4. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
  5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

‘#’ 和 ‘##’

如何把参数插入到字符串当中去?

首先我们看这样的代码:

char* p="hello ""BOB!!!\n";
printf("hello""BOB!!!\n");
printf("%s",p):

这里输出的是:
在这里插入图片描述
这里我们发现字符串是有自动连接的特点的。

  1. 那我们是不是可以写这样的代码?:
int a=10;
#define PRINT(FORMAT,VALUE)\
printf("the value is "FORMAT"\n",VALUE)
PRINT("%d",a);

运行结果:
在这里插入图片描述

这里只有当字符串(上面的例子对应的就是 “%d”)作为宏参数的时候才可以把字符串放在字符串中。
预处理器预处理结果:

printf("the value is ""%d""\n",VALUE);//字符串又有自动连接的特点

错误代码:

#define PRINT(FORMAT,VALUE)\
printf("the "VALUE " is "FORMAT"\n",VALUE)

语法报错:
在这里插入图片描述
因为这里VALUE传参传的是10 ,并不像FORMAT一样是字符串 (“%d” ),所以会报错

这里就给大家讲一个技巧是:

使用 # ,把一个宏参数变成对应的字符串。

比如:

int a=10;
#define PRINT(FORMAT,VALUE)\
printf("the "#VALUE " is "FORMAT"\n",VALUE)
PRINT("%d",a);

运行结果:
在这里插入图片描述
代码中的 #VALUE 会预处理器处理为:
“VALUE” ,因为编译器会自动将字符串连接起来所以不会报错

'##'的作用

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
直接上代码:

#define ADD_TO_SUM(num, value) \sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.

这里代码中的ADD_TO_SUM(5,10);会预处理成

sum5+=10;

也就是给sum5增加10

带副作用的宏参数

宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题。

#define MAX(X,Y) ((X)>(Y)?(X):(Y))#include<stdio.h>
int main()
{int x = 5;int y = 6;int z = MAX(x++,y++);printf("x=%d\ny=%d\nz=%d\n", x, y, z);return 0;
}

运行结果:
在这里插入图片描述
这里预处理器处理后结果为:

z = ( (x++) > (y++) ? (x++) : (y++));

因为这是一个三目操作符 : 语句1 ?语句2:语句3
首先执行语句1 若语句1返回真则执行语句2,若假执行语句3
在这里插入图片描述

所以这里x=6,y=8, z=7。

宏和函数对比

宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个

#define MAX(a, b) ((a)>(b)?(a):(b))

那为什么不用函数来完成这个任务?
原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
    所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。
    所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。
    宏是类型无关的。

宏的缺点:当然和函数相比宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
    宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num, type)\(type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));
属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只会于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对比较慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数传参数只在函数调用时候要求值一次,他的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数传参只在传参的时候求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
调试宏是不方便调试的函数可以逐句调试
递归宏不可以递归函数可以递归

命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:

把宏名全部大写
函数名不要全部大写

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

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

相关文章

不满足于RPC,详解Dubbo的服务调用链路

系列文章目录 【收藏向】从用法到源码&#xff0c;一篇文章让你精通Dubbo的SPI机制 面试Dubbo &#xff0c;却问我和Springcloud有什么区别&#xff1f; 超简单&#xff0c;手把手教你搭建Dubbo工程&#xff08;内附源码&#xff09; Dubbo最核心功能——服务暴露的配置、使用…

代码随想录day20

654. 最大二叉树 思路&#xff1a;这道题的目的是从给定的nums数组中找到一个最大值当做二叉树的根节点&#xff0c;然后以最大值左右两边的元素分别构建二叉树。也就是一个左中右的顺序&#xff0c;所以构建二叉树这种题用前序遍历是再合适不过了&#xff0c;因为根节点把左右…

ETHERCAT转ETHERCAT网关西门子为什么不支持ethercat两个ETHERCAT设备互联

1.1 产品功能 远创智控YC-ECT-ECT是自主研发的一款ETHERCAT从站功能的通讯网关。该产品主要功能是将2个ETHERCAT网络连接起来。 本网关连接到ETHERCAT总线中做为从站使用。 1.2 技术参数 1.2.1 远创智控YC-ECT-ECT技术参数 ● 网关做为ETHERCAT网络的从站&#xff0c;可以连接…

将已删除的 Safari 历史记录恢复到 iPhone 的 6 种最佳方法

Safari 是 iPhone 上的默认浏览器。其直观且易于使用的界面是许多 iPhone 用户更喜欢 Safari 而非其他浏览器的原因之一。但是&#xff0c;如果您无意或故意删除了浏览历史记录&#xff0c;但发现稍后仍需要恢复已删除的 Safari 历史记录&#xff0c;这可能会给您带来一些麻烦。…

131、仿真-基于51单片机智能电子称HX711报警仿真设计(程序+原理图+PCB图+Proteus仿真+参考论文+元器件清单等)

摘 要 电子秤是将检测与转换技术、计算机技术、信息处理、数字技术等技术综合一体的现代新型称重仪器。它与我们日常生活紧密结合息息相关。 电子称主要以单片机作为中心控制单元&#xff0c;通过称重传感器进行模数转换单元&#xff0c;在配以键盘、显示电路及强大软件来组成…

RocketMQ第四节(部署模式、监控面板等)

1&#xff1a;mq的部署模式 部署方式 | RocketMQ 参考官网。 单机模式&#xff1a;抗风险能力差&#xff0c;单机挂机没服务&#xff0c;单机硬盘损坏&#xff0c;丢失数据 多机&#xff08;多master没有Slave副本&#xff09;: 多个master采用RAID10磁盘&#xff0c;不会丢…

Spring Cloud Alibaba【创建支付服务生产者、创建服务消费者 、Dubbo和OpenFeign区别 、微服务接入OpenFeign】(二)

目录 分布式服务治理_创建支付服务生产者 分布式服务治理_创建服务消费者 服务调用_Dubbo和OpenFeign区别 服务调用_微服务接入OpenFeign 分布式服务治理_创建支付服务生产者 创建服务提供者工程cloud-provider-payment8001 POM文件引入依赖 <dependencies><…

密码学学习笔记(十三):哈希函数 - Merkle–Damgård结构

Merkle–Damgrd是一种算法&#xff0c;由Ralph Merkle和Ivan Damgrd提出。它通过迭代调用压缩函数来计算消息的哈希值。 应用 拿SHA-2举例&#xff0c;首先我们需要对需要进行哈希运算的输入做填充&#xff0c;然后将填充后的输入划分为等长的分组&#xff0c;每个分组的长度…

设计模式07-责任链模式

责任链模式属于行为设计模式&#xff0c;常见的过滤器链就是使用责任链模式设计的。 文章目录 1、真实开发场景的问题引入2、责任链模式讲解2.1 核心类及类图2.2 基本代码 3、利用构建者模式解决问题4、责任链模式的应用实例5、总结5.1 解决的问题5.2 使用场景5.3 优缺点 1、真…

Docker基础(二)

1、Docker工作原理 Docker是一个Clinet-Server结构的系统&#xff0c;Docker守护进程运行在主机上&#xff0c;然后通过Socket连接从客户端访问&#xff0c;守护进程从客户端接受命令并管理运行在主机上的容器。 容器&#xff0c;是一个运行时环境&#xff0c;就是我们前面说的…

编程小白的自学笔记十(python爬虫入门二+实例代码详解)

系列文章目录 编程小白的自学笔记九&#xff08;python爬虫入门代码详解&#xff09; 编程小白的自学笔记八&#xff08;python中的多线程&#xff09; 编程小白的自学笔记七&#xff08;python中类的继承&#xff09; 编程小白的自学笔记六&#xff08;python中类的静态方法…

Servlet的监听器

Servlet常用的监听器 ServletContextAttributeListener 用来感知ServlerContext对象属性变化&#xff0c;比如添加或删除属性变化 ServletContextListener 用来感知ServlerContext对象的创建和销毁的 ServletRequestListener 可以用来监听感知ServletRequest对象的创建和销毁的…