一:预定义符号
在c语言中有一些预定义的符号,这些符号已经定义好了,用户不需要再次定义只需要使用即可。
- __FILE__ :显示当前进行编译的源文件
- __LINE__:显示当前代码行号
- __DATE__:显示当前文件被编译的日期
- __TMIE__:显示当前文件被编译的时间
- __STDC__:查看当前编译器是否遵循ANI C。如果返回值为1,反之结构未定义
由此可见vs编辑器是不遵循ANI C标准的。
二:#define
1.#define定义标识符:
从上示例可以看出,#define不仅可以定义符号常量,还可以定义字符串量。
2.#define定义宏
#define机制包括了一个定义,它允许把参数替换到文本中,这种实现方法成为定义宏。
这里需要注意 ADD后面的左括号需要紧跟着ADD,如果两者之间有空格那么都会解释成ADD宏的一部分。
我们观察上图例子,可以看到MUL1与MUL2中的表达式一样只是区别在于,一个有加()一个没有,但输入相同参数的时,产生的结果确实两个不同的值。原因在于宏在使用时候会将参数直接替换,而上图示例宏将参数替换后变成两个不同的表达式,后根据符号优先级产生了不同的结果。 使用请注意:在写宏时请不要吝啬括号。
3.#define的替换规则
1.简单的文本替换: #define
指令可以将标识符替换为指定的文本。
2.带参数的宏: 宏也可以带有参数,类似于函数,在替换时,宏的名字会被他们的参数替换。
3.宏定义的范围: 宏定义的作用范围从定义点开始一直到文件末尾,除非被undef
指令取消。
4.宏应避免递归
三:#与##
1.#:
首先来看下面一段代码
可以看到在第二个printf语句中有两个字符串,而打印时会合并成一个字符串。
可以看到a和b的输出是相似的,此时可以想到是不是可以用函数来进行简化。但肯定的是函数是做不到这样的,因为函数只能改变其值,但不能改变文本如 of a 与 of b,函数做不到这样的替换,此时就可以就可以用到宏。
上示例 定义了一个宏 将刚才printf语句变成宏的参数,将printf语句拆分成两个字符串,而在两个字符串中间 是#N,那么#的作用是:将#后的参数变成字符串。将原本的a的参数10替换成变量名a。那么下次在想输出类似的语句只需要调用宏传入参数即可。
2.##:
##可以将位于它两边的标识符合成为一个标识符。
观察上图,这定义了一个宏CAT,参数为(Class,num),其值为Class##num。意思是将Class与num连接,组成Classnum参数。 此时在main函数里定义int类型的变量Classnum,接着打印宏,将参数Class与num带入宏,就会组成刚才定义的Classnum的变量。如果此时将main函数里定义的Classnum参数注释掉或者改变成别的参数,那么结果是未定义的。
四:带副作用的宏参数
如果宏参数在宏定义中出现超过一次的时候,如果此时宏参数带有副作用,那么在使用这个宏时可能会出现意想不到的危险,会与预想的结果出现严重偏差。
例如: x+1 不带副作用(不会改变其x值) , x++带副作用 (会改变其x值)。
从上图可以看到,在宏定义中使用了三元运算符表达式。接着在main() 函数中,定义了两个整型变量 a 和 b,分别初始化为 5 和 4。然后调用了 MAX(a++,
b++)。
这会被展开成((5++)>(4++))? 在这个表达式中,(5++) 和 (4++) 会被进行比较,比较完再经行后置++操作,这里5与4先进行比较,接着5++变成了6,4++变成了5,而因为5>4,返回的是第二个表达式(a),而a这里会被替换成(6++)因为刚才5已经变成6了。又因为是后置++,先将数值6返回给ret,最后6++变成7。所以a=7,b=5(因为第三个表达式并没有执行),ret=6 。
宏的缺点:
- 宏是无法进行调试的,宏展开是在预处理阶段完成的,因此在编译器看到代码之前,它们已经被替换为具体的代码。
- 每一次使用宏时,一份宏定义的代码都会插入到程序中,除非宏比较短,否则可能会增加代码长度。
- 宏因为与类型无关,所以宏不够严谨。
- 使用宏的代码可能难以理解和维护,因为宏展开后的代码可能与原始代码有很大差异,而且不容易阅读。
-
宏是不安全的。由于宏是简单的文本替换,没有类型检查或作用域限制,因此可能会导致意想不到的行为。例如,如果在宏中使用了传递给它的表达式多次求值,就会导致副作用,如上面例子中的 a++ 和 b++ 被多次执行。
移除宏:#undef
#undef用来移除宏命令。
五:条件编译
条件编译是一种编程技术,通常用于根据预定义的条件来控制程序的编译过程。条件编译通过预处理器指令来实现,比如#ifdef、#ifndef等指令。通过在源代码中使用这些指令,可以根据预定义的宏或条件来选择性地排除或添加代码块。
关于判断宏是否被定义的指令:
1.#if defined(宏) || #ifdef 宏
2.#if !defined(宏) || #ifndef 宏
#if defined(MAX):定义了MAX就打印
#if !defined(MAX):没有定义MAX就打印
#ifdef MAX:定义了MAX就打印
#ifndef:没有定义MAX就打印
六:头文件包含
文件包含是通过预处理器指令 #include来实现的。#include 指令告诉编译器在编译时将指定的文件内容包含到当前文件中。这种机制使得可以将一些通用的代码放在单独的文件中,然后通过 #include 指令将其包含到需要使用的程序中。
从上图示例可以看出 我的test.c的文件只有#include"head.h"的文件包含,编译器会在源文件下的目录进行查找头文件,如果该文件未找到,编译器就会像查找库函数头文件一样在标准位置下查找头文件。如果找不到就会发生编译错误提示。文件还可以进行嵌套包含,上图例子就是在head.h的文件中包含着库文件。
关于< >与" "的区别:
< >与" "的查找策略不同。
#include<stdion.h> :查找策略为直接去库目录下查找。
#include"head.h":查找策略为先去代码所在的路径下查找,如果找不到再去库目录下找。