宏定义有无参数宏定义和带参数宏定义两种

2024-02-29 21:38
文章标签 参数 定义 两种 有无

本文主要是介绍宏定义有无参数宏定义和带参数宏定义两种,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

宏定义有无参数宏定义带参数宏定义两种。
   无参数的宏定义的一般形式为
            # define 标识符 字符序列

其中# define之后的标识符称为宏定义名(简称宏名),要求宏名与字符序列之间用空格符分隔。这种宏定义要求编译预处理程序将源程序中随后所有的定名的出现(注释与字符串常量中的除外)均用字符序列替换之。前面经常使用的定义符号常量是宏定义的最简单应用。如有:
            # define TRUE 1
            # define FALSE 0
则在定义它们的源程序文件中,凡定义之后出现的单词TRUE将用1替代之;出现单词FALSE将用0替代之。
       在宏定义的#之前可以有若干个空格、制表符,但不允许有其它字符。宏定义在源程序中单独另起一行,换行符是宏定义的结束标志。如果一个宏定义太长,一行不 够时,可采用续行的方法。续行是在键人回车符之前先键入符号"/"。注意回车要紧接在符号"/"之后,中间不能插入其它符号。
      宏定义的有效范围称为宏定义名的辖域,辖域从宏定义的定义结束处开始到其所在的源程序文件末尾。宏定义名的辖域不受分程序结构的影响。可以用预处理命令#undef终止宏定义名的辖域。
  在新的宏定义中,可以使用前面已定义的宏名。例如,
             # define R 2.5
             # define PI 3.1415926
             # define Circle 2*PI*R
             # define Area PI* R * R
程序中的Circle被展开为2*3.1415926* 2.5, Area被展开为3.1415926*2.5*2.5。
     如有必要,宏名可被重复定义。被重复定义后,宏名原先的意义被新意义所代替。

     通常,无参数的宏定义多用于定义常量。程序中统一用宏名表示常量值,便于程序前后统一,不易出错,也便于修改,能提高程序的可读性和可移植性。特别是给数组元素个数一个宏定义,并用宏名定义数组元素个数能部分弥补数组元素个数固定的不足。
      注意:预处理程序在处理宏定义时,只作字符序列的替换工作,不作任何语法的检查。如果宏定义不当,错误要到预处理之后的编译阶段才能发现。宏定义以换行结束,不需要分号等符号作分隔符。如有以下定定义:
   # define PI 3.1415926;
原希望用PI求圆的周长的语句
   c=2*PI*r;
经宏展开后,变成
   c=2*3.1415926*r;
这就不能达到希望的要求。
   带参数宏定义进一步扩充了无参数宏定义的能力,在字符序列替换同时还能进行参数替换。带参数定定义的一般形式为
   # define 标识符(参数表)字符序列
其中参数表中的参数之间用逗号分隔,字符序列中应包含参数表中的参数。
在定义带参数的宏时,宏名标识符与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。如有宏定义:
   # define MAX(A,B) ((A) > (B)?(A):(B))

则代码 y= MAX( p+q, u+v)将被替换成 y=((p+q) >(u+v)?(p+q):(u+v)。
           程序中的宏调用是这样被替换展开的,分别用宏调用中的实在参数字符序列(如p+q和u+V) 替换宏定义字符序列中对应所有出现的形式参数(如用p+q替代所有形式参数A,用u+V替代所有形式参数B),而宏定义字符序列中的不是形式参数的其它字 符则保留。这样形成的字符序列,即为宏调用的展开替换结果。宏调用提供的实在参数个数必须与宏定义中的形式参数个数相同。
       注意:宏调用与函数调用的区别。函数调用在程序运行时实行,而宏展开是在编译的预处理阶段进行;函数调用占用程序运行时间,宏调用只占编译时间;函数调用 对实参有类型要求,而宏调用实在参数与宏定义形式参数之间没有类型的概念,只有字符序列的对应关系。函数调用可返回一个值,宏调用获得希望的C代码。另 外,函数调用时,实参表达式分别独立求值在前,执行函数体在后。宏调用是实在参数字符序列替换形式参数。替换后,实在参数字符序列就与相邻的字符自然连 接,实在参数的独立性就不一定依旧存在。如下面的宏定义:
   # define SQR(x) x*x
希望实现表达式的平方计算。对于宏调用
   P=SQR(y)
能得到希望的宏展开p= y*y。但对于宏调用q=SQR(u+v)得到的宏展开是q=u+V*u+V。显然,后者的展开结果不是程序设计者所希望的。为能保持实在参数替换后的独立性,应在宏定义中给形式参数加上括号。进一步,为了保证宏调用的独立性,作为算式的宏定义也应加括
号。如 SQR宏定义改写成:
   # define SQR((x)*(x))
才是正确的宏定义。

      对于简短的表达式计算函数,或为了提高程序的执行效率、避免函数调用时的分配存储单元、保留现场、参数值传递、释放存储单元等工作。可将函数定义改写成宏定义。所以合理使用宏定义,可以使程序更简洁。


使用一些宏跟踪调试

A N S I标准说明了五个预定义的宏名。它们是:

_ L I N E _ (两个下划线),对应%d

_ F I L E _    对应%s

_ D A T E _   对应%s

_ T I M E _   对应%s

_ S T D C _

如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序

也许还提供其它预定义的宏名。

_ L I N E _及_ F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。

_ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。

源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。

如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是

非标准的。

可以定义宏,例如:

当定义了_DEBUG,输出数据信息和所在文件所在行

#ifdef _DEBUG

#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%s”,date,_LINE_,_FILE_)

#else

      #define DEBUGMSG(msg,date)

#endif

20,宏定义防止使用是错误

用小括号包含。

例如:#define ADD(a,b) (a+b)

用do{}while(0)语句包含多语句防止错误

例如:#difne DO(a,b) a+b;/

                   a++;

应用时:if(….)

          DO(a,b); //产生错误

        else

        

解决方法: #difne DO(a,b) do{a+b;/

                   a++;}while(0)


宏中"#"和"##"的用法 
一、一般用法 
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起(这里说的是在预处理是对源文件的操作)
用法: 
#include<cstdio> 
#include<climits> 
using namespace std;

#define STR(s)     #s 
#define CONS(a,b) int(a##e##b)

int main() 

printf(STR(vck));           // 输出字符串"vck" 
printf("%d/n", CONS(2,3)); // 2e3 输出:2000 
return 0; 
}

二、当宏参数是另一个宏的时候 
需要注意的是凡宏定义里有用''#''或''##''的地方宏参数是不会再展开.

1, 非''#''和''##''的情况 
#define TOW      (2) 
#define MUL(a,b) (a*b)

printf("%d*%d=%d/n", TOW, TOW, MUL(TOW,TOW)); 
这行的宏会被展开为: 
printf("%d*%d=%d/n", (2), (2), ((2)*(2))); 
MUL里的参数TOW会被展开为(2).

2, 当有''#''或''##''的时候 
#define A          (2) 
#define STR(s)     #s 
#define CONS(a,b) int(a##e##b)

printf("int max: %s/n", STR(INT_MAX));    // INT_MAX #include<climits> 
这行会被展开为: 
printf("int max: %s/n", "INT_MAX");

printf("%s/n", CONS(A, A));               // compile error 
这一行则是: 
printf("%s/n", int(AeA));

INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏. 
加这层宏的用意是把所有宏的参数在中间层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

#define A           (2) 
#define _STR(s)     #s 
#define STR(s)      _STR(s)          // 转换宏 
#define _CONS(a,b) int(a##e##b) 
#define CONS(a,b)   _CONS(a,b)       // 转换宏

printf("int max: %s/n", STR(INT_MAX));          // INT_MAX,int型的最大值,为一个变量 #include<climits> 
输出为: int max: 0x7fffffff 
STR(INT_MAX) --> _STR(0x7fffffff) 然后再转换成字符串;

printf("%d/n", CONS(A, A)); 
输出为:200 
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))

三、''#''和''##''的一些应用特例 
1、合并匿名变量名 
#define ___ANONYMOUS1(type, var, line) type var##line 
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line) 
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__) 
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示该行行号; 
第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__); 
第二层:                        --> ___ANONYMOUS1(static int, _anonymous, 70); 
第三层:                        --> static int _anonymous70; 
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

2、填充结构 
#define FILL(a)   {a, #a}

enum IDD{OPEN, CLOSE}; 
typedef struct MSG{ 
IDD id; 
const char * msg; 
}MSG;

MSG _msg[] = {FILL(OPEN), FILL(CLOSE)}; 
相当于: 
MSG _msg[] = {{OPEN, "OPEN"}, 
{CLOSE, "CLOSE"}};

3、记录文件名 
#define _GET_FILE_NAME(f)   #f 
#define GET_FILE_NAME(f)    _GET_FILE_NAME(f) 
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);

4、得到一个数值类型所对应的字符串缓冲大小 
#define _TYPE_BUF_SIZE(type) sizeof #type 
#define TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type) 
char buf[TYPE_BUF_SIZE(INT_MAX)]; 
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)]; 
--> char buf[sizeof "0x7fffffff"]; 
这里相当于: 

char buf[11]

本文转载自http://hi.baidu.com/ufo008ahw/blog/item/5e943d4f5f49513caec3abd1.html)

这篇关于宏定义有无参数宏定义和带参数宏定义两种的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760176

相关文章

SpringBoot服务获取Pod当前IP的两种方案

《SpringBoot服务获取Pod当前IP的两种方案》在Kubernetes集群中,SpringBoot服务获取Pod当前IP的方案主要有两种,通过环境变量注入或通过Java代码动态获取网络接口IP... 目录方案一:通过 Kubernetes Downward API 注入环境变量原理步骤方案二:通过

golang实现延迟队列(delay queue)的两种实现

《golang实现延迟队列(delayqueue)的两种实现》本文主要介绍了golang实现延迟队列(delayqueue)的两种实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录1 延迟队列:邮件提醒、订单自动取消2 实现2.1 simplChina编程e简单版:go自带的time

CentOS7增加Swap空间的两种方法

《CentOS7增加Swap空间的两种方法》当服务器物理内存不足时,增加Swap空间可以作为虚拟内存使用,帮助系统处理内存压力,本文给大家介绍了CentOS7增加Swap空间的两种方法:创建新的Swa... 目录在Centos 7上增加Swap空间的方法方法一:创建新的Swap文件(推荐)方法二:调整Sww

QT6中绘制UI的两种方法详解与示例代码

《QT6中绘制UI的两种方法详解与示例代码》Qt6提供了两种主要的UI绘制技术:​​QML(QtMeta-ObjectLanguage)​​和​​C++Widgets​​,这两种技术各有优势,适用于不... 目录一、QML 技术详解1.1 QML 简介1.2 QML 的核心概念1.3 QML 示例:简单按钮

一文详解PostgreSQL复制参数

《一文详解PostgreSQL复制参数》PostgreSQL作为一款功能强大的开源关系型数据库,其复制功能对于构建高可用性系统至关重要,本文给大家详细介绍了PostgreSQL的复制参数,需要的朋友可... 目录一、复制参数基础概念二、核心复制参数深度解析1. max_wal_seChina编程nders:WAL

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

史上最全nginx详细参数配置

《史上最全nginx详细参数配置》Nginx是一个轻量级高性能的HTTP和反向代理服务器,同时也是一个通用代理服务器(TCP/UDP/IMAP/POP3/SMTP),最初由俄罗斯人IgorSyso... 目录基本命令默认配置搭建站点根据文件类型设置过期时间禁止文件缓存防盗链静态文件压缩指定定错误页面跨域问题

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置