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

常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。
无参宏定义
无参数宏定义的格式为:
#define 标识符 替换列表

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

说明:

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

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

  1. 标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号
    #define N =5 //虽语法正确,但预处理器会把N替换成=5
    int a[N]; //错误,因为宏替换之后为 int a[=5];
    宏定义不是语句,是预处理指令,故结尾不加分号。如果不小心添加了分号,虽然有时该宏定义没问题,但在宏替换时,可能导致 C 语法错误,或得不到预期结果。例如:
    #define N 5; //虽语法正确,但会把N替换成5;
    int a[N]; //语法错误,宏替换后,为int a[5;];错误

  2. 由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。例如:
    #define N 3+2
    int r=NN;
    宏替换后为:
    int r=3+2
    3+2; //r=11
    如果采用如下形式的宏定义:
    #define N (3+2)
    int r=NN;
    则宏替换后,为:
    int r=(3+2)
    (3+2); //r=25

  3. 当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行。例如:
    #define USA “The United
    States of
    America”
    该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符 \ 把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 \。

如果调用 printf 函数,以串的形式输出该符号常量,即:
printf("%s\n",USA);
则输出结果为:The United States of America

注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。
带参宏定义
带参数的宏定义格式为:
#define 标识符(参数1,参数2,…,参数n) 替换列表

例如,求两个参数中最大值的带参宏定义为:
#define MAX(a,b) ((a)>(b)?(a) : (b))
当有如下语句时:
int c=MAX(5,3);
预处理器会将带参数的宏替换成如下形式:
int c=((5)>(3)?(5) : (3));
故计算结果c=5。

删除宏定义的格式为:
#undef 标识符

说明:

  1. 标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义,故以下是错误的带参宏定义形式。
    #define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //错误的带参宏定义格式
  2. 宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义。

【例 1】以下程序试图定义求两个参数乘积的宏定义,欲使用该宏求 3 与 6 的乘积,分析该程序能否实现预期功能,如果不能,请给出修改方案。
#include <stdio.h>
#define MUL(a,b) (a*b)
int main (void)
{
int c;
c=MUL(3,5+1);
printf(“c=%d\n”,c);
return 0;
}
分析:

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

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

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

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

修改代码为:
#include <stdio.h>
#define MUL(a,b) ((a)*(b))//修改处1
int main (void)
{
int c;
c=MUL(3,(5+1);//修改处2
printf(“c=%d\n”,c);
return 0;
}
带参宏定义 VS 函教调用
接下来将从调用发生时间、参数类型检查、参数是否需要空间、运行速度等几个主要方面进行对比分析带参宏定义与函数调用的差异。

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

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

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

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

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

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

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

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

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

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

宏定义(无参宏定义和带参宏定义)相关推荐

  1. thymeleaf的初次使用(带参请求以及调用带参js方法)

    thymeleaf的初次使用(带参请求以及调用带参js方法) 之前对于前端框架接触较少,第一次接触thymeleaf,虽说看起来并不复杂但我还是花费了好一会儿才弄懂. 话不多少下面就简单说一下我在项目 ...

  2. java十四章带参方法课后_java14带参的方法

    public class jh_01_如何使用带参数的方法31 { public static void main(String[] args) { // 创建对象 ZhaZhiJi zzj = ne ...

  3. oracle有入参的试图,Oracle 带参视图

    创建包: create or replace package p_view_param  is function set_param(num number) return number; functi ...

  4. 05c语言——宏定义、带参宏、带参宏函数

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一.宏定义 1.使用规则 2.注意 二.带参宏 1.定义 2.注意 三.带参函数 1.带参函数的宏与带参宏的区别 2.带参宏 ...

  5. java无参_Java——类的无参、带参方法

    >类的无参方法 什么是类的方法 类是由一组具有相同属性和共同行为的实体抽象而来.对象执行的操作是通过编写类的方法实现的.显而易见,类的方法是一个功能模块,其作用是"做一件事情" ...

  6. Java05-day05【方法(概述、调用过程图解)、带参方法、带返回值方法、重载、方法参数传递(基本类型、引用类型)】

    java零基础入门到精通(2019版)[黑马程序员] 视频+资料:[链接:https://pan.baidu.com/s/1MdFNUADVSFf-lVw3SJRvtg   提取码:zjxs] &qu ...

  7. 关于自定义异常中为什么带参构造器需要显示调用父类异常的带参构造器

    在听课的时候听到自定义异常时,视频上讲的定义异常的时候如果是带参构造器需要显示调用父类异常的带参构造器,原因是什么呢? 首先我们需要看一下Exception和ERROR的父类Throwable的源码: ...

  8. 4.6宏定义之带参宏

    //本文为转载,具体出处已经找不到了.这里引用为了知识传播.感谢原作者. C语言允许宏带有参数.在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数,这点和函数有些类似.就像把函数的实参传递给形 ...

  9. ACMNO.30 C语言-宏交换 定义一个带参的宏,使两个参数的值互换,并写出程序,输入两个数作为使用宏时的实参。输出已交换后的两个值。

    题目描述 定义一个带参的宏,使两个参数的值互换,并写出程序,输入两个数作为使用宏时的实参.输出已交换后的两个值. 输入 两个数,空格隔开 输出 交换后的两个数,空格隔开 样例输入 1 2 样例输出 2 ...

最新文章

  1. TWaver HTML5 + Node.js + express + socket.io + redis(六)
  2. 二、linux最小驱动
  3. android 加减乘除计算器,【03-21求助】写一个简易计算器的安卓app,一按加减乘除就退出...
  4. CentOS 搭建 LAMP服务器
  5. 车牌识别与计算机编程,基于计算机视觉的车牌字符识别技术的研究
  6. 博通2021财年第三季度营收67.8亿美元,半导体业务营收50亿美元
  7. Android模拟器PANIC: Could not open:问题解决方法
  8. Java教程:Java字符串的替换(replace()、replaceFirst()和replaceAll())
  9. .Net应用程序打包部署总结
  10. php ajax跳转,ajax接口的php文件如何实现跳转
  11. 自学考试c语言真题,自学考试《C语言程序设计》复习试题及答案
  12. [Swift]LeetCode198. 打家劫舍 | House Robber
  13. python字符串长度排序_python-对混合类型和不同长度的字符串进行排序
  14. 如何解决CPU过热100度自动关机
  15. c语言规定对程序中所用的变量必须,【判断题】C语言程序中要用到的变量必须先定义,然后再使用...
  16. 银行使用计算机和网络实现个人存款,观察值与算术平均数的差数称为离均差,其总和为( )。...
  17. Prometheus 告警收敛
  18. 微信H5支付----报undened index openid
  19. 【程序员眼中的统计学(11)】卡方分布的应用
  20. android蓝牙双通道意义,BCM43598双通道SDIO接口双频11ac级蓝牙WiFi

热门文章

  1. 裸奔系列之博科SAN交换机(2)---博科SAN交换机接入(登陆)
  2. Python中的爱因斯坦阶梯
  3. SDL Trados 2021 Bilingual 双语文件类型处理
  4. ERRORS: auth.User.groups: (fields.E304) Reverse accessor for ‘User.groups‘ clashes with reverse acce
  5. FineReport传参问题
  6. 重启:COSCon'21 讲师征集令 1+N
  7. C语言输入字符和字符串(所有函数大汇总)
  8. 嵌入式程序猿必知--基本知识
  9. 基于basys2驱动LCDQC12864B的verilog设计图片显示
  10. 郭天祥,我的大学六年