宏定义(无参宏定义和带参宏定义)


宏定义是比较常用的预处理指令,即使用“标识符”来表示“替换列表”中的内容。标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏定义中替换列表中的内容。

常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。

无参宏定义

无参数宏定义的格式为:

#define 标识符 替换列表

替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一常量,或称符号常量。

说明:
1) # 可以不在行首,但只允许它前面有空格符。例如:

  1. #define PI 3.1416 //正确,该行#前允许有空格
  2. int a;#define N 5 //错误,该行#前不允许有空格外的其他字符

2) 标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号

  1. #define N =5 //虽语法正确,但预处理器会把N替换成=5
  2. int a[N]; //错误,因为宏替换之后为 int a[=5];

宏定义不是语句,是预处理指令,故结尾不加分号。如果不小心添加了分号,虽然有时该宏定义没问题,但在宏替换时,可能导致 C 语法错误,或得不到预期结果。例如:

  1. #define N 5; //虽语法正确,但会把N替换成5;
  2. int a[N]; //语法错误,宏替换后,为int a[5;];错误

3) 由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。例如:

  1. #define N 3+2
  2. int r=N*N;

宏替换后为:

  1. int r=3+2*3+2; //r=11

如果采用如下形式的宏定义:

  1. #define N (3+2)
  2. int r=N*N;

则宏替换后,为:

  1. int r=(3+2)*(3+2); //r=25

4) 当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行。例如:

  1. #define USA "The United \
  2. States of \
  3. America"

该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符 \ 把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 \。

如果调用 printf 函数,以串的形式输出该符号常量,即:

  1. printf("%s\n",USA);

则输出结果为:The United States of America

注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。

带参宏定义

带参数的宏定义格式为:

#define 标识符(参数1,参数2,...,参数n) 替换列表

例如,求两个参数中最大值的带参宏定义为:

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

当有如下语句时:

  1. int c=MAX(5,3);

预处理器会将带参数的宏替换成如下形式:

  1. int c=((5)>(3)?(5) : (3));

故计算结果c=5。

删除宏定义的格式为:

#undef 标识符

说明:
1) 标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义,故以下是错误的带参宏定义形式。

  1. #define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //错误的带参宏定义格式

2) 宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义。

【例 1】以下程序试图定义求两个参数乘积的宏定义,欲使用该宏求 3 与 6 的乘积,分析该程序能否实现预期功能,如果不能,请给出修改方案。

  1. #include <stdio.h>
  2. #define MUL(a,b) (a*b)
  3. int main (void)
  4. {
  5. int c;
  6. c=MUL(3,5+1);
  7. printf("c=%d\n",c);
  8. return 0;
  9. }

分析:
1) 由于该宏定义中的替换列表中的参数没有加括号,故宏调用时,如果参数是个表达式,可能会出现歧义,得不到预期结果。

本例中宏调用 c=MUL(3,5+1); 会替换成 c=(3*5+1)=16;,与预期功能不符。

2) 虽然把宏调用时的参数 5+1 括起来,可达到题目要求的效果,但这属于治标不治本。为统一编程规范,把替换列表中的每个参数均加括号,整个替换列表也加括号。

同时,为达到标本兼治,在宏定义时,除单一值参数外,应显式加括号。

修改代码为:

  1. #include <stdio.h>
  2. #define MUL(a,b) ((a)*(b))//修改处1
  3. int main (void)
  4. {
  5. int c;
  6. c=MUL(3,(5+1);//修改处2
  7. printf("c=%d\n",c);
  8. return 0;
  9. }

带参宏定义 VS 函教调用

接下来将从调用发生时间、参数类型检查、参数是否需要空间、运行速度等几个主要方面进行对比分析带参宏定义与函数调用的差异。

调用发生的时间

在源程序进行编译之前,即预处理阶段进行宏替换;而函数调用则发生在程序运行期间。

参数类型检查

函数参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。

在预处理阶段,对带参宏调用中的参数不做检查。即宏定义时不需要指定参数类型,既可以认为这是宏的优点,即适用于多种数据类型,又可以认为这是宏的一个缺点,即类型不安全。故在宏调用时,需要程序设计者自行确保宏调用参数的类型正确。

参数是否需要空间

函数调用时,需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中。而宏替换,仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,即不需要分配空间。

执行速度

函数在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法,而宏替换仅 是简单文本替换,不做任何语法或逻辑检查。

函数在运行阶段参数需入栈和出栈操作,速度相对较慢。

代码长度

由于宏替换是文本替换,即如果需替换的文本较长,则替换后会影响代码长度;而函数不会影响代码长度。

故使用较频繁且代码量较小的功能,一般采用宏定义的形式,比采用函数形式更合适。前面章节频繁使用的 getchar(),准确地说,是宏而非函数。

为了使该宏调用像函数调用,故把该宏设计成了带参数的宏定义:

  1. #define getchar() getc(stdin)

故调用该宏时,需要加括号,即传空参数:getchar()。

C/C++ 宏定义的更多相关文章

  1. c++宏定义命令

    在程序开始以#开头的命令,他们是预编译命令.有三类预编译命令:宏定义命令.文件包含命令.条件编译命令:今天聊聊宏定义: 宏定义命令将一个标识符定义为一个字符串,源程序中的该标识符均以指定的字符串来代替 ...

  2. dll导入导出宏定义,出现“不允许 dllimport 函数 的定义”的问题分析

    建立dll项目后,在头文件中,定义API宏 #ifndef API_S_H #define API_S_H ...... #ifndef DLL_S_20160424 #define API _dec ...

  3. iOS之常用宏定义

    下面我为大家提供一些常用的宏定义! 将这些宏定义 加入到.pch使用 再也不用 用一次写一次这么长的程序了 //-------------------获取设备大小------------------- ...

  4. linux中offsetof与container_of宏定义

    linux内核中offsetof与container_of的宏定义 #define offsetof(TYPE, MEMBER)    ((size_t) &((TYPE *)0)->M ...

  5. Linux Kernel代码艺术——系统调用宏定义

    我们习惯在SI(Source Insight)中阅读Linux内核,SI会建立符号表数据库,能非常方便地跳转到变量.宏.函数等的定义处.但在处理系统调用的函数时,却会遇到一些麻烦:我们知道系统调用函数 ...

  6. 面试问题5:const 与 define 宏定义之间的区别

    问题描述:const 与 define 宏定义之间的区别 (1) 编译器处理方式不同     define宏是在预处理阶段展开:     const常量是编译运行阶段使用: (2) 类型和安全检查不同 ...

  7. 关于Xcode8.1 / iOS10+ 真机测试系统打印或者宏定义打印不显示问题

    前言: 最近做项目时遇到了很多莫名其妙的问题,其中就有这个打印(NSLog).也不多废话了,我们先来回顾一下Xcode8发布以来,我们遇到的一些关于打印的问题,当然也有解决方法: 1.Xcode8打印 ...

  8. JDStatusBarNotification和一些宏定义

    // //  AddTopicViewController.m //  vMeet2 // //  Created by 张源海 on 16/6/30. //  Copyright © 2016年 h ...

  9. #define宏定义形式的"函数"导致的bug

    定义了一个宏定义形式的"函数": #define  SUM8(YY)\ {\ int Y = YY>>2;\ ...\ } 然后使用的时候,传入了一个同名的变量Y: i ...

  10. 黑马程序员——C语言基础 枚举 宏定义 自定义 static exterm

    Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结) (一)枚举 1)枚举类型的定义 枚举是C语言中的一种基本数据类型,并不是构 ...

随机推荐

  1. isNotBlank()方法和isNotEmpty()方法的区别

  2. 破坏正方形UVA1603

    题目大意 有一个由火柴棍组成的边长为n的正方形网格,每条边有n根火柴,共2n(n+1)根火柴.从上至下,从左到右给每个火柴编号,现在拿走一些火柴,问在剩下的后拆当中ongoing,至少还要拿走多少根火 ...

  3. srec_cat 常用参数的使用

    前言 下面介绍映像文件工具 srec_cat 的使用,如何通过相关参数实现自己需要的功能. 下载链接:SRecord 使用方式 文件类型 在输入文件和输出文件文件时要指明文件类型,常用的如: test ...

  4. IP地址和端口号

    IP地址 IP地址:指互联网协议地址(Internet Protocol Address),俗称IP.IP地址用来给一个网络中的计算机设备做唯一的编号.加入我们吧"个人电脑"比作一 ...

  5. 什么是双网口以太网IO模块

    MXXXE系列远程IO模块工业级设计,适用于工业物联网和自动化控制系统,MxxxE工业以太网远程 I/O 配备 2 个mac层数据交换芯片的以太网端口,允许数据通过可扩展的菊花链以太网远程 I/O 阵 ...

  6. MyBatis-知识点详解

    Mybatis 中$与#的区别 1 #是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的 ...

  7. 【原创】Selenium获取请求头、响应头

    本文仅供学习交流使用,如侵立删! Selenium获取请求头.响应头 操作环境 win10 . mac Python3.9 selenium.seleniumwire selenium是没有办法直接获 ...

  8. 如果Controller里有私有的方法,能成功访问吗?

    目录 背景 原因 cglib代理的锅 换成JDK动态代理呢 参考 背景 写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了Controller里有了一个私有的方法,然后访问这个接口的时候就报了 ...

  9. 巨细靡遗流程控制,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang流程结构详解EP09

    流程结构就是指程序逻辑到底怎么执行,进而言之,程序执行逻辑的顺序.众所周知,程序整体都是自上由下执行的,但有的时候,又不仅仅是从上往下执行那么简单,大体上,Go lang程序的流程控制结构一共有三种: ...

  10. BZOJ3224/LuoguP3369 普通平衡树 (splay)

    终末のcode #include <iostream> #include <cstdio> #include <cstring> #include <algo ...