1、宏定义说明

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

2、无参宏定义

  • 无参数宏定义的格式为:

    #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;];错误
    1. 由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。例如:
#define N 3+2
int r = N * N;
  • 宏替换后为:
int r=3+2*3+2; //r=11
  • 如果采用如下形式的宏定义:
#define N (3+2)
int r=N*N;
  • 则宏替换后,为:
int r=(3+2)*(3+2); //r=25
    1. 当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行。例如:
#define USA "The United \
States of \
America"
  • 该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符 \ 把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 \。
  • 如果调用 printf 函数,以串的形式输出该符号常量,即:
printf("%s\n",USA);
  • 则输出结果为:The United States of America
  • 注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。

3、带参宏定义

  • 带参数的宏定义格式为:

    #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) ) //错误的带参宏定义格式
      1. 宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义。
  • 【例 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()。

宏定义(无参宏定义和带参宏定义),C语言宏定义详解的更多相关文章

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

    之前对于前端框架接触较少,第一次接触thymeleaf,虽说看起来并不复杂但我还是花费了好一会儿才弄懂. 话不多少下面就简单说一下我在项目中的应用. 首先是java代码 controller层 将需要 ...

  2. 如何在微信小程序定义全局变量、全局函数、如何实现 函数复用 模块化开发等问题详解

    1.如何定义全局数据 在app.js的App({})中定义的数据或函数都是全局的,在页面中可以通过var app = getApp();  app.function/key的方式调用,不过我们没有必要 ...

  3. net core体系-web应用程序-4net core2.0大白话带你入门-5asp.net core环境变量详解

    asp.net core环境变量详解   环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的 ...

  4. 带参宏定义和inline修饰的内联函数

    带参宏定义和inline修饰的内联函数都是在编译时,用函数体替换掉宏调用或函数调用.这样用的好处是减少调用函数所花费的时间. 例如: 算法导论在讲到堆排序时说的,好的堆排序实现一般是把Left(i), ...

  5. [C++] C++中的宏定义详解

    转载自:C++中的宏定义 和 C++宏定义详解 一.#define解析     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率 ...

  6. ytu 1057: 输入两个整数,求他们相除的余数(带参的宏 + 模板函数 练习)

    1057: 输入两个整数,求他们相除的余数 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 136[Submit][Status ...

  7. C语言宏定义时#(井号)和##(双井号)的用法

    C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念).下面对常遇到的宏的使用问题做了简单总结. 关于#和## 在C语言的宏中,#的功能是将其后面 ...

  8. ytu 1058: 三角形面积(带参的宏 练习)

    1058: 三角形面积 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 190  Solved: 128[Submit][Status][Web Boar ...

  9. C语言宏定义相关

    写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性 等等.下面列举一些成熟软件中常用得宏定义......1,防止一个头文件被重复包含#ifndef COMDEF_H# ...

随机推荐

  1. 实战MvcPager(PagerOptions自定义样式&同、异步)

    ASP.NET MVC下的分页控件MvcPager用起来简直太嗨呸了,两句代码实现一个分页,而且需要改变样式的时候直接构造PagerOptions类 实战无需多说,拿来用之即可.个人觉得对性能影响不大 ...

  2. python arp欺骗

    使用python构造一个arp欺骗脚本 import os import sys from scapy.all import * import optparse def main(): usage=& ...

  3. pandas分组和聚合

    Pandas分组与聚合 分组 (groupby) 对数据集进行分组,然后对每组进行统计分析 SQL能够对数据进行过滤,分组聚合 pandas能利用groupby进行更加复杂的分组运算 分组运算过程:s ...

  4. 只能在执行Render() 的过程中调用 RegisterForEventValidation;

    October 17, 2008  3:28 PMMarch 29, 2013  8:28 PM Aillo 编程   0 Comments 在实现"将GridView中的数据导出到Exce ...

  5. PHP 取网页变量

    $_POST["test"]; $_GET["test"];isset();  if(isset($_GET["yyuid"]))

  6. matlab GPU 操作

    从Matlab2013版本开始,matlab将可以直接调用gpu进行并行计算,而不再需要安装GPUmat库.这一改动的好处是原有的matlab内置函数都可以直接运用,只要数据格式是gpuArray格式 ...

  7. RedHat&nbsp;Enterprise&nbsp;Linu…

    Abstract 在嵌入式开发中有宿主机和目标机之分:宿主机是执行编译.链接嵌入式软件的计算机:目标机是运行嵌入式软件的硬件平台. TFTP服务器作为工作于宿主机的软件,主要提供对目标机的主要映像文件 ...

  8. 【原创】7. MYSQL++中的查询结果获取(各种Result类型)

    在本节中,我将首先介绍MYSQL++中的查询的几个简单例子用法,然后看一下mysqlpp::Query中的几个与查询相关的方法原型(重点关注返回值),最后对几个关键类型进行解释. 1. MYSQL++ ...

  9. 用java和汇编开发一个hello world系统内核

  10. 1-2 开发环境搭建-Windows平台

    C:\Program Files\nodejs\node_modules\npm\npmrc C:\Users\ZHONGZHENHUA\.android\avd H:\heimaandroidadt ...