时间:2022-10-07 11:27:23 | 栏目:C代码 | 点击:次
C语言中源代码到可执行文件的第一阶段,也就是预处理阶段,会检查源文件中的预处理指令语句和宏定义,并对源代码进行相应的替换,预处理过程还会删除程序中的注释和多余的空白符号。
预处理指令是以#开头的代码行,#必须是该行除了空白符外的第一个字符,#后是指令关键字,在#和指令关键字之间允许存在若干个空白字符,define是宏定义命令。在C语言程序中允许用一个标识符来表示一个字符串,称为“宏”,“宏”又分为有参和无参,有参又称为“宏函数”,被定义为“宏”的标识符称为“宏名”。
语法规定:
#define name stuff
name:标识符名\宏名
stuff:可以是关键字、常量、关键字、标识符、标点符号、运算符,表达式
在预处理阶段,编译器会在程序中使用#define定义的标识符替换成stuff,可以通过预处理生成的.i文件查看效果。
//stuff是数值常量 #define NUM 10
//stuff是关键字 #define reg register
//stuff是标点符号 #define GREATER_THAN >
//stuff的更多表达方式 #define do_forever for(;;) //若定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个\(续行符) #define DBBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n",\ __FILE__,__LINE__,\ __DATE__,__TIME__)
总结:#define定义的name(宏名),在预编译阶段会将所有的宏名替换成stuff,stuff内容被替换到源代码中。称为“宏代换”或“宏展开”。
注意:define定义的标识符的时候,后面加上“;”会将“;”认为是stuff中的内容。
宏函数的申明方式:#define name(parament-list) stuff
parament-list:参数列表
注意:参数列表的左括号必须与name紧邻,如果两者之间有空白存在,参数列表就会被解释为stuff的部分。
宏函数存在的问题1
#include <stdio.h> #define SQUARE(x) x*x//定义一个宏函数求平方 int main() { int x = SQUARE(3+1);//替换后x的计算结果是多少?答案是:7 }
为什么呢?
在给宏函数传参时,如果传递的是一个表达式,不会先计算表达式的结果再进行传参,而是直接将表达式整体作为参数传递。
那么如何防止发生这样的情况呢?+()
宏函数存在的问题2
#include <stdio.h> #define SUM(x,y) (x)+(y) int main() { int a = 10; int b = 5; int c = SUM(a,b)*2;//替换后c的结果为20,why return 0; }
我们看看替换后的结果
这又该如何解决呢?
总结:在对数值表达式进行求值的宏定义应该用这两种方式加上括号,避免在使用宏参数的操作符或邻近操作符之间不可预料的相互作用。
#define替换规则:
1.在使用宏函数时,首先对参数进行检查,看看参数中是否包含任何#define定义的标识符,如果有,他们首先被替换。
2.替换的内容被插入到源文件原来的位置。对于宏函数,参数名被他们的值替换
1.宏名一般用大写
2.使用宏可提高程序的通用性和易读性,便于修改。
3.宏定义末尾不加分号
4.宏定义写在函数的大括号外面,作用域为其后的程序,通常放在开头
5.宏函数不可递归
6.宏定义不分配内存,变量定义分配内存
7.字符串" "中永远不包含宏
8.宏定义不存在类型问题,他的参数也没有类型
1.宏不能调试
2.宏由于与类型无关,不够严谨
3.宏可能带来运算符优先级的问题,导致容易出错
#define:宏定义
#undef:撤销已经定义过的宏名
#include:将另一个源文件嵌入到#include源文件中
#if~#endif:如果#if后面的常量表达式为真,则编译#if~#endif之间的代码,如果为假,跳过这些代码不编译。
#if~#elif~#else~#endif:和if~else if~else类似,可以建立更分支。
#ifdef symbol~endif:判断是否被定义,定义了编译他们之间内容
#ifndef symbol~endif:判断是否被定义,没定义编译他们之间的内容
#line:改变当前行数和文件名称,是在编译程序中预先定义的标识符命令的基本形式:#line number["filename"]
#error:编译程序时,只要遇到#error就会生成一个编译错误的提示信息,并停止编译。
#pragma: 可以设定编译程序完成一些特点的动作(可以通过编译程序的菜单中设置),可以向编译程序传送各种指令。