目录

1、无参宏定义

1.1 无参数宏定义的格式:

1.2 使用说明:

2、带参宏定义

2.1 带参数宏定义的格式:

2.2 使用说明:

3、带参宏定义与函数调用的区别

4、头文件中常用的宏定义

5、宏中#和##的用法


从开始写C语言到生成执行程序的流程大致如下:


预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令 #include,宏命令 #define 等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。

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

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

1、无参宏定义

1.1 无参数宏定义的格式:

#define 标识符 替换列表

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

1.2 使用说明:

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

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

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

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

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

#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

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

#define USA "The United \
States of \
America"printf("%s\n",USA);//输出结果为:The United States of America

该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符 \ 把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 \。注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。

5)宏可以嵌套,但不参与运算:

#define M 5                 // 宏定义
#define MM M * M            // 宏的嵌套
printf("MM = %d\n", MM);    // MM 被替换为: MM = M * M, 然后又变成 MM = 5 * 5

实际的 5 * 5 相乘过程在编译阶段完成,而不是在预处理器工作阶段完成,宏不进行运算,它只是按照指令进行文字的替换操作,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段发现。

6)宏定义必须写在函数之外,作用域从 #define 开始,到源程序结束。如果要提前结束它的作用域则用 #undef 命令:

#define M 5                 // 宏定义
printf("M = %d\n", M);      // 输出结果为: M = 5
#undef M             // 取消宏定义
printf("M = %d\n", M);      // error:… main.c:138:24: Use of undeclared identifier 'M'

7)可以用宏定义表示数据类型,可以使代码简便:

#define STU struct Student      // 宏定义STU
struct Student{                 // 定义结构体Studentchar *name;int sNo;
};
STU stu = {"Jack", 20};         // 被替换为:struct Student stu = {"Jack", 20};
printf("name: %s, sNo: %d\n", stu.name, stu.sNo);

8)#define 与 typedef 的区别:

两者都可以用来表示数据类型:

#define INT1 int
typedef int INT2;两者是等效的,调用也一样:
INT1 a1 = 3;
INT2 a2 = 5;

但当如下使用时,问题就来了:

#define INT1 int *
typedef int * INT2;
INT1 a1, b1;
INT2 a2, b2;
b1 = &m;         //... main.c:185:8: Incompatible pointer to integer conversion assigning to 'int' from 'int *'; remove &
b2 = &n;         // OK

INT1 a1, b1; 被宏代换后为: int * a1, b1;

即定义的是一个指向int型变量的指针 a1 和一个int型的变量b1.

INT2 a2, b2;表示定义的是两个变量a2和b2,这两个变量的类型都是INT2的,也就是int *的,

两个都是指向int型变量的指针

所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typede不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。

2、带参宏定义

2.1 带参数宏定义的格式:

#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。

2.2 使用说明:

1) 标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义,

#define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //错误的带参宏定义格式
#define SUM (a,b) a + b              //定义有参宏
printf("SUM = %d\n", SUM(1,2));      //调用有参宏。Build Failed!
因为 SUM 被替换为:(a,b) a + b

和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参 

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

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

3、带参宏定义与函数调用的区别

带参宏定义 函数调用
调用发生时间 预处理阶段 程序运行期间

参数类型检查

在预处理阶段,对带参宏调用中的参数不做检查。即宏定义时不需要指定参数类型,适用于多种数据类型。 函数参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。
参数是否需要空间 宏替换,仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,不需要分配空间。 需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中。
运行速度 宏替换仅是简单文本替换,不做任何语法或逻辑检查。速度较快 函数在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法,函数在运行阶段参数需入栈和出栈操作,速度相对较慢。

代码长度

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

故使用较频繁且代码量较小的功能,一般采用宏定义的形式,比采用函数形式更合适。

#define getchar() getc(stdin)

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

4、头文件中常用的宏定义

1)防止头文件被重复包含

#ifndef cTest_Header_h
#define cTest_Header_h
//头文件内容
#endif

在我们常用的 stdio.h 头文件中也可以见到很多宏定义,如:

备注:#ifndef 和 #endif 要一起使用,如果丢失#endif,可能会报错。

#define BUFSIZ 1024 //缓冲区大小
#define EOF (-1)    //表文件末尾
#ifndef SEEK_SET
#define SEEK_SET 0  //表示文件指针从文件的开头开始
#endif
#ifndef SEEK_CUR
#define SEEK_CUR 1  //表示文件指针从现在的位置开始
#endif
#ifndef SEEK_END
#define SEEK_END 2  //表示文件指针从文件的末尾开始
#endif

知识补充:

#ifndef是"if not defined"的简写是宏定义的一种,它是可以根据是否已经定义了一个变量来进行分支选择,一般用于调试(防止头文件的重复包含和编译)

第一种:

        #ifndef x

        #define x

程序段1

        #else

程序段2

        #endif//终止if

 //先测试x是否被宏定义过 //如果x没有被宏定义过,定义x,并编译程序段 1; //如果x已经定义过了则编译程序段2的语句,“忽视”程序段 1

第二种:

语句1        #ifndef 标识1

语句2        #define 标识1

语句3        #endif

语句4 ……

语句5 ……

//如果标识1没有被定义,则重定义标识1,即执行语句2、语句3;如果标识1已经被定义,则直接跳过语句2、语句3,直接执行语句4、语句5、……

第三种:

//例如要编写头文件test.h,在头文件开头写上两行:

#ifndef _TEST_H

#define _TEST_H //一般是文件名的大写

头文件结尾写上一行:

#endif

当第一次包含test.h时,由于没有定义_TEST_H,条件为真,这样就会执行#ifndef _TEST_H和#endif之间的代码,当第二次包含test.h时前面已经定义了_TEST_H,条件为假,#ifndef _TEST_H和#endif之间的代码也就不会再次被包含,这样就避免了重定义。

<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯有的。标识的命名规则一般是头文件名全大写,前面加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

#ifndef _STDIO_H

#define _STDIO_H

......

#endif

5、宏中#和##的用法

使用#把宏参数变为一个字符串,例如,如果 a 是一个宏的形参,则替换文本中的 #a 则被系统转化为 “a”。而这个转化的过程成为 “字符串化(stringizing)”

#define SUM(a,b) printf(#a " + "#b" = %d\n",((a) + (b)))    //宏定义,运用 # 运算符
SUM(1 + 2, 3 + 4);                                          //宏调用
//输出结果:1 + 2 + 3 + 4 = 10调用宏时,用 1 + 2 代替 a,用 3 + 4 代替b,则替换文本为:
printf(“1 + 2” ” + ” “3 + 4” ” = %d\n”,((1 + 2) + (3 + 4))),
接着字符串连接功能将四个相邻的字符串转换为一个字符串:
"1 + 2 + 3 + 4 = %d\n"

##把两个宏参数贴合在一起,又称为“预处理器的粘合剂(Preprocessor Glue)”

#define NAME(n) num ## n            //宏定义,使用 ## 运算符
int num0 = 10;
printf("num0 = %d\n", NAME(0));     //宏调用NAME(0)被替换为 num ## 0,被粘合为: num0。

可变宏在这里不进行介绍

参考:

宏定义(无参宏定义和带参宏定义),C语言宏定义详解

详解宏定义(#define)___Sunshine_的博客-CSDN博客_宏定义

C语言宏定义、宏函数、内置宏与常用宏_Apollon_krj的博客-CSDN博客_c语言宏函数

C语言中的宏函数与宏定义相关推荐

  1. C语言中内联函数的作用 inline

    C语言中内联函数的作用 inline C语言中内联函数到底有什么作用? 试想一下,每当我们在假设就在主函数中调用另外一个函数的时候,那么这个函数就要入栈或者出栈,比如说下面的一个例子: 点击(此处)折 ...

  2. C语言中比较大小的函数模板,C语言中实现模板函数小结 : 不敢流泪

    --by boluor 2009/5/20 如果要写个函数支持多种数据类型,首先想到的就是C++的模板了,但是有时候只能用C语言,比如在linux内核开发中,为了减少代码量,或者是某面试官的要求- 考 ...

  3. c语言函数编写格式,在c语言中如何实现函数模板?

    如果要写个函数支持多种数据类型,首先想到的就是C++的模板了,但是有时候只能用C语言,比如在linux内核开发中,为了减少代码量,或者是某面试官的要求- 考虑了一阵子后,就想到了qsort上.qsor ...

  4. C 语言中的 time 函数总结

    C 语言中的 time 函数总结 分类 编程中经常用到时间表达及转换的函数,它们都定义在 time.h 库函数中,在此做一下总结,以方便后续查看使用. 几个时间概念: 1:Coordinated Un ...

  5. php 定义宏函数,汇编语言宏函数

    宏函数与宏过程有相似的地方,它也为汇编语言语句列表分配一个名称.不同的地方在于,宏函数通过 EXITM 伪指令总是返回一个常量(整数或字符串).如下例所示,如果给定符号已定义则宏 IsDefined ...

  6. C语言中文件定位函数总结

    C语言中文件定位函数主要是:fseek, ftell, fsetpos, fgetpos. 先来讲前两个函数,这是最基本的定位函数: fseek函数:能把文件指针移动到文件任何位置,其原型是:int ...

  7. C语言中的回调函数(Callback Function)

    C语言中的回调函数(Callback Function) 1 定义和使用场合 回调函数是指 使用者自己定义一个函数,实现这个函数的程序内容,然后把这个函数(入口地址)作为参数传入别人(或系统)的函数中 ...

  8. python文件定位函数_C语言中文件定位函数总结

    C语言中文件定位函数主要是:fseek, ftell, fsetpos, fgetpos. 先来讲前两个函数,这是最基本的定位函数: fseek函数:能把文件指针移动到文件任何位置,其原型是:int ...

  9. C语言中利用Swap函数交换变量a,b

    C语言中利用Swap函数交换变量a,b 常见错误写法 error1 void Sawp_error1(int a,int b) {int tmp;tmp=a;a=b;b=tmp; } int main ...

  10. c语言读取文件字节数,怎么在C语言中利用fstat函数获取文件的大小

    怎么在C语言中利用fstat函数获取文件的大小 发布时间:2021-01-22 17:03:17 来源:亿速云 阅读:110 作者:Leah 怎么在C语言中利用fstat函数获取文件的大小?针对这个问 ...

最新文章

  1. Go 语言编程 — 高级数据类型 — Map 集合
  2. 前端路由以及浏览器回退,hash history location
  3. ISO9000互联网管理办法
  4. CENTOS7配置静态IP后无法ping通外部网络的问题
  5. 神奇的[Caller*]属性
  6. P4774-[NOI2018]屠龙勇士【EXCRT】
  7. windows下解决pip安装出错问题
  8. 作者:唐华(1973-),男,华南师范大学软件学院院长助理、副教授。
  9. 二次规划问题转换为半正定问题(QPtoSDP)
  10. java 课程设计数据库_人事管理系统(java数据库课程设计)+SQL数据库
  11. printf和sprintf
  12. 416. Partition Equal Subset Sum
  13. php+mysql+zend+一键_PHP+MySQL+phpMyAdmin+ZendOptimizer环境一键安装包下载及安装手
  14. shell命令的退出状态码(exit status)
  15. java 详情页_电商网站详情页系统架构
  16. 欧美古风格html网站模板
  17. w10计算机用户名密码忘了,电脑w10系统开机密码忘了
  18. 在校外,如何免费下载知网上的文献论文的方法
  19. ImportError: DLL load failed while importing win32api
  20. 开启及清除NV_RESTORE信息的方法

热门文章

  1. Java整合APNS
  2. 计算机点开一直压缩在屏幕下方,电脑屏幕显示被压缩了怎么变
  3. qgc 区域外扩_绝地求生:QGC新春邀请赛总决赛解读,大神吃鸡不走寻常路
  4. thusc2016游记滚粗记酱油记
  5. RNG:The relative neighbourhood graph of finite planar set
  6. Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
  7. 黑苹果键盘键盘驱动_苹果糟糕的键盘以及为何可维修性如此重要
  8. C#中分割字符串输出字符数组
  9. 【Netty 从成神到升仙系列 大结局】全网一图流死磕解析 Netty 源码
  10. 世上不可能有真正的完美,但应该有一个追求完美的心态