预处理

编译一个C语言程序的第一步骤就是预处理阶段,这一阶段就是宏发挥作用的阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见

C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译,为了区分预处理指令和一般的C语句,所有预处理指令都以符号“#”开头,并且结尾不用分号,预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件,C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

注意点:

  1. 宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误
  2. 对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作
  3. 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串.
  4. 带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用小括号括住相关计算,这样会避免运算符优先级的问题
  5. 在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查
  6. 宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令
  7. 定义一个宏时可以引用已经定义的宏名
  8. 可用宏定义表示数据类型,使书写方便

宏定义

被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。##不带参数的宏定义:#define 标识符 字符串 其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。

#include <stdio.h>// 源程序中所有的宏名PI在编译预处理的时候都会被3.14所代替
#define PI 3.14// 根据圆的半径计radius算周长float girth(float radius) {return 2 * PI *radius;
}int main (){float g = girth(2);printf("周长为:%f", g);return 0;
}

带参数的宏定义

C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参#define 宏名(形参表) 字符串

// 第1行中定义了一个带有2个参数的宏average,#define average(a, b) (a+b)/2int main (){// 第4行其实会被替换成:int a = (10 + 4)/2;,int a = average(10, 4);// 输出结果为:7是不是感觉这个宏有点像函数呢?printf("平均值:%d", a);return 0;}

宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串.

宏函数的巧用

我们知道函数虽然可以传递参数,但是却不能把类型作为参数传递,有时我们为了实现函数的复用性,就要使用STL模板,但是我们这个时候还有另外一种选择,就是写成宏函数

一个开辟内存的函数

#define Malloc(type,size) (type*)malloc(sizeof(type)*size)

这个时候,我们只有把类型,容量作为参数传递进行,就可以开辟各种类型的内存了

int *p=Malloc(int,100); //开辟int类型的内存
char *q=Malloc(char,100); //开辟字符类型的内存

传递数组

由数组作为函数参数传递时,会失去其数组特性,也就是无法使用sizeof()函数计算出数组的大小,比如我们写一个排序函数,排序时我们不仅需要知道数组的首地址,还需要知道数组的大小,但是仅仅把数组名作为参数传递时,无法得知其数组大小,这时我们的函数就需要传递第二个参数,也就是数组的大小,于是函数就要写成Sort(int *a,int size).但宏函数就可以解决这个问题

#define InsertSort(list)\
{\int s=sizeof(list)/4;\int i,j;\for(i=2;i<=s;i++)\{\list[0]=list[i];\for(j=i-1;list[j]>list[0];j--)\list[j+1]=list[j];\ list[j+1]=list[0];\       }\
}
int main()
{int num[]={0,2,5,7,3,1,8,0,8,22,57,56,74,18,99,34,31,55,41,12,9,4};InsertSort(num);for(int i=1;i<sizeof(num)/4;i++) printf("%d ",num[i]);return 0;
}

宏定义相关作用符

换行符 “”

我们定义宏语句或者宏函数时不可能总是一条语句呀,那要是有很多条语句时怎么办?都写在一行吗?这样显然代码就不美观,可读性不好,所以有多条语句时,我们就在每行末尾(除了最后一行)加上"",代表换行的意思

#include"stdio.h"
#define Print   printf("这是第1条语句\n");\printf("这是第2条语句\n");\printf("这是第3条语句\n")#define Show(str1,str2,str3)\
{\printf("%s\n",str1);\printf("%s\n",str2);\printf("%s\n",str3);\
}

字符串化符 “#”

"#"是“字符串化”的意思,将出现在宏定义中的#是把跟在后面的参数转换成一个字符串

#define Print(str)\
{\printf(#str"的值是%d",str);\
}

片段连接符"##"

“##”是一种分隔连接方式,它的作用是进行强制连接。, 简单来说就是类似字符串拼接的方式, 但是这种方式是可以执行的

#define TEXT_A "Hello, world!"
#define msg(x) puts( TEXT_ ## x )
msg(A);

无论标识符 A 是否定义为一个宏名称,预处理器会先将形参 x 替换成实参 A,然后进行记号粘贴。当这两个步骤做完后,结果如下:

puts( TEXT_A );

现在,因为 TEXT_A 是一个宏名称,后续的宏替换会生成下面的语句

puts( "Hello, world!" );

案例:

#define trace(x, format) printf(#x " = %" #format "\n", x)
#define trace2(i) trace(x##i, d)int main(int argc, char* argv[])
{int i = 1;char *s = "Hello";float y = 2.0;trace(i, d);                // 相当于 printf("i = %d\n", i)trace(y, f);                // 相当于 printf("y = %f\n", y)trace(s, s);                // 相当于 printf("s = %s\n", s)int x1 = 1, x2 = 2, x3 = 3;trace2(1);                  // 相当于 trace(x1, d)trace2(2);                  // 相当于 trace(x2, d)trace2(3);                  // 相当于 trace(x3, d)return 0;
}

可变参数

#define PRINT(fmt, ...) printf(# fmt, ##__VA_ARGS__)
#define SHOW_LIST(...) printf(# __VA_ARGS__)
int main(int argc, char* argv[])
{SHOW_LIST(HELLO, 250, 3.14); //输出:HELLO, 250, 3.14PRINT(Hello); //输出:Helloreturn 0;
}

常见的预处理指令


预处理指令使用注意事项
1)预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的,程序员在程序中用预处理命令来调用这些功能。
2)宏定义可以带有参数,宏调用时是以实参代换形参,而不是“值传递”。
3)为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。
4)文件包含是预处理的一个重要功能,它可以用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
5)条件编译允许只编译程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率
6)使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。

宏定义和函数的区别


我们常用到的比较大小,宏定义:

#define MAX( a, b) ( (a) > (b) (a) : (b) )

自定义函数实现就是:

int max( int a, int b)
{return (a > b a : b)
}

常用预定义宏

 __FUNTION__  获取当前函数名 __LINE__ 获取当前代码行号 __FILE__ 获取当前文件名 __DATE__ 获取当前日期 __TIME__ 获取当前时间__STDC_VERSION__
#define assert(expression) (void)(                                                       \(!!(expression)) ||                                                              \(_wassert(_CRT_WIDE(>>++#expression++<<), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \)

区别:

  1. 最直观的来讲,自定义的函数已经指定了类型,只能比较两个整数的大小,却不能再去比较浮点数的大小,或者两个ASCII码的大小;而宏定义是不会限定类型的,只要比较的类型一致即可。

  2. 自定义接口在程序运行时,会产生临时的堆空间,有临时的空间消耗,如果是递归的话,需要的临时栈空间可能更多;宏定义是在程序运行时,会将宏定义这段代码插入到程序中执行,会有额外的代码段。

  3. 自定义接口在调用时,实际的开销要比代码段大,规模更大; 而宏比自定义函数在程序的规模和速度方面,比自定义函数更胜一筹。

点赞 -收藏-关注-便于以后复习和收到最新内容 有其他问题在评论区讨论-或者私信我-收到会在第一时间回复 在本博客学习的技术不得以任何方式直接或者间接的从事违反中华人民共和国法律,内容仅供学习、交流与参考 免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我、以迅速采取适当措施,避免给双方造成不必要的经济损失。 感谢,配合,希望我的努力对你有帮助^_^

C语言-入门-宏定义(十七)相关推荐

  1. 如何用C语言改变宏定义的大小,C语言中宏定义使用的小细节

    C语言中宏定义使用的小细节 #pragma#pragma 预处理指令详解 在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#p ...

  2. c语言用宏定义常量_使用宏定义常量以在C的数组声明中使用

    c语言用宏定义常量 As we know that, while declaring an array we need to pass maximum number of elements, for ...

  3. C/C++编程笔记:浅析 C 语言中宏定义的使用,知识点全解

    宏定义是用一个标识符来表示一个字符串,在宏调用中将用该字符串代替宏名.给程序员提供了便利,使程序更加清晰,便于阅读和理解,进一步提高了程序的运行效率,对于嵌入式系统而言,为了能达到性能要求,宏是一种很 ...

  4. c语言中宏定义的字符替换#define M(x,y,z) x*y+z

    C语言中宏定义的字符替换问题 例子: 在c语言中定义如下宏 #define M(x,y,z) x*y+z 给定如下程序 #include<stdio.h> #include<stdl ...

  5. C 语言编程 — 宏定义与预处理器指令

    目录 文章目录 目录 前文列表 宏 预处理器 预处理器指令 预处理器指令示例 预处理器指令运算符 宏定义 简单宏定义 带参数的宏定义 符号吞噬问题 使用 do{}while(0) 结构 预定义的宏 常 ...

  6. 大牛深入浅出讲解C语言#define宏定义应用及使用方法

    在C语言中,我们使用#define来定义宏.在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本. 不带参数的宏 关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形 ...

  7. C语言常用宏定义(#define)使用方法

    ·  正  ·  文  ·  来  ·  啦  · 前言 ------在上篇文章里面,我们分析了预处理的一个完整过程,这能够让我们理解一个写好的程序,在生成一个可执行文件,到底发生了什么,对我们在大型 ...

  8. c语言数组宏定义标识符,C语言学习笔记--预编译/宏定义/数组/参数传递/函数指针...

    目录 预编译 值传递.指针传递.引用传递 数组 typedef 函数指针 预编译 预编译又叫预处理.预编译不是编译,而是编译前的处理.这个操作是在正式编译之前由系统自动完成的.#define 和 #i ...

  9. 【C语言】----宏定义,预处理宏

    什么是宏? 宏是学习任何语言所不可缺少的,优秀的宏定义可以使得代码变得很简洁且高效,有效地提高编程效率. 宏是一种预处理指令,它提供了一种机制,可以用来替换源代码中的字符串,解释器或编译器在遇到宏时会 ...

最新文章

  1. MapReduce框架Mapper和Reducer类源码分析
  2. python画图代码星星-Python利用for循环打印星号三角形的案例
  3. spring_整体系统
  4. facebook 邀请好友_如何在Facebook上与某人解除好友
  5. datetime unix php,PHP基于DateTime类解决Unix时间戳与日期互转问题【针对1970年前及2038年后时间戳】...
  6. 【python教程入门学习】python入门:来来来,每天10点定时签到拿京豆啦
  7. appium在android7.0上无法启动问题
  8. [学习笔记]C语言深度剖析
  9. DataRow[] /数组转换datatable!
  10. 北航计算机组成原理课程设计-2020秋 PreProject-Logisim-入门指南与Logisim门电路
  11. Nginx基本配置参数说明与文档
  12. [精简]快速认识钢琴键盘
  13. React实现简单图片放大缩小旋转还原模块
  14. 动漫插画培训班有哪些
  15. 你的新媒体写作工具横向测评,请查收!
  16. cs231n计算机视觉课程笔记
  17. Spring Boot(三):RestTemplate提交表单数据的三种方法
  18. SQL的update语句
  19. pb9 日历控件(源码)
  20. Docker容器化实战第二课 镜像、容器、仓库详解

热门文章

  1. NET 5连mysql数据库遇到的问题-1252;PublicKeyToken=cc7b13ffcd2ddd51
  2. 领扣LintCode问题答案-33. N皇后问题
  3. 【芝麻背调百科】HR们小心,一张离职证明,有可能让企业损失惨重!
  4. html目录结构怎么创建,如何在Word文档上创建目录结构?
  5. 【虚幻引擎】UE4源码解析FWorldContent、UWorld、ULevel、UGameInstance、UEngine
  6. LINUX串口发送数据,包数据被拆分发送
  7. 20220323有道云笔记如何收藏文章和剪报功能
  8. 【Python】爬虫入门6:爬取百度图片搜索结果(基于关键字爬图)
  9. 我们逃离北上广,美国人逃离硅谷
  10. 通过 JDK 源码学习灵活应用设计模式(上)