c预处理器指令,c和c 的预处理命令区别
C/C编译系统编译器的过程是预处理、编译和链接。预处理器是在程序源文件被编译之前,根据预处理指令对其进行处理的程序。预处理器指令以#开头,末尾不包含分号。预处理命令不是C/C语言本身的一部分,所以不能直接编译链接。C/C语言的一个重要功能就是可以使用预处理指令,具有预处理的功能。C/C提供的预处理功能主要包括文件包含、宏替换、条件编译等。
1.该文件包含
预处理指令#include用于包含头文件,有两种形式:#include xxx.h,#include xxx.h 。
尖括号表示包含的文件位于系统目录中。如果包含的文件不一定在系统目录中,它应该用双引号引起来。
您可以用双引号指示文件路径和文件名。如果绝对路径没有用双引号引起来,默认情况下,它是用户当前目录中的文件。此时,系统首先在用户的当前目录中查找要包含的文件,如果找不到,则在系统目录中查找。
对于用户自己编写的头文件,应该使用双引号。对于系统提供的头文件,您可以使用尖括号或双引号来查找包含的文件,但显然使用尖括号更直接、更高效。
./表示当前目录,并且./表示当前目录的父目录。
2.宏替换
宏定义
宏定义的作用一般是用一个短名称来表示一个长代码序列。宏定义包括无参数宏定义和参数宏定义。宏名和宏参数表示的代码序列可以是任何有意义的内容,如类型、常量、变量、运算符、表达式、语句、函数、代码块等。但需要特别注意的是,宏名和宏参数必须是合法的标识符,它们所代表的内容和含义必须在宏展开前后始终独立不变,不能单独解释和执行。
无参数宏定义。代码序列由用户指定的名为宏名的标识符表示。这个定义的一般形式是#定义标识符代码序列。#define后面的标识符称为宏定义名(简称宏名)。宏定义#define之前可以有几个空格和制表符,但不允许有其他字符。宏名与代码序列之间用一个空格分隔。
带参数的宏定义。参数宏定义进一步扩展了无参数宏定义的能力。这时宏展开既可以替换宏名,也可以替换宏参数。带参数的宏定义一般形式是#define identifier(参数表)代码序列,其中参数表中的参数用逗号分隔,参数表中的参数必须包含在代码序列中。定义带参数的宏时,宏名和左括号之间不能有空格,要立即连接起来,否则就成了不带参数的宏定义。带参数的宏调用提供的实参数个数必须与宏定义中的形参数个数相同。
宏定义的有效范围称为宏名的范围,宏名的范围是从宏定义的末尾到其源代码文件的末尾。宏的作用域不受程序结构的影响。如果需要终止宏名的作用域,可以用预处理指令#undef添加宏名。
宏通常是大写的,以区别于变量名。如果需要,宏名可以重复定义。经过反复定义后,宏名的原有含义被新的含义所取代。
定义宏代码序列时,必须成对出现,字符串不能反汇编。比如#define NAME vrmozart是非法的,但应该是#define NAME vrmozart 。
定义的宏名可以在宏代码序列中引用,即宏定义可以嵌套。
多行宏
宏定义在源文件中必须从新行开始,换行符是宏定义的结束标记,所以宏定义以换行符结束,不需要分号等符号作为分隔符。如果一个宏定义中的代码序列太长,一行不够,可以采用连续行的方法。延续行是在键入回车之前键入符号\\。注意回车紧跟在符号\之后,中间不能插入其他符号。当然,代码序列的最后一行末尾不能有\。请注意,多行宏只能在一行上调用,不能在表达式中使用或作为函数参数使用。
宏观扩张
预处理程序在处理宏定义时,会对宏进行扩展(即替换宏)。宏替换首先将源文件中宏定义后出现的所有宏名替换为它们所代表的代码序列,如果是带参数的宏,再将代码序列中的宏参数名替换为宏参数名。宏仅用于替换代码字符序列,不进行任何语法检查或中间计算,其他所有操作只能在替换后进行。如果没有正确定义宏,那么在预处理后的编译阶段才会发现错误。
源代码中的宏名和宏定义代码序列中的宏参数名必须是要替换的标识符,即只替换标识符,其他的都不替换,比如注释、字符串常量和宏名或者标识符中出现的宏参数名。例如:
#定义名字vrmozart,源代码中的宏名//NAME,/*NAME*/, NAME 和my_NAME_blog不会被替换。
# define blog(name)my _ name _ blog= name ,宏定义代码序列中的宏参数名不会被替换。
如果希望替换出现在宏定义代码序列标识符中的宏参数名,可以在宏参数名和标识符之间添加一个连接符# #。在宏替换过程中,宏参数名和连接器# #将一起被宏参数名替换。# #用于将宏参数名与宏定义代码序列中的标识符连接起来,形成一个新的标识符。例如:
#定义BLOG(name) my_##name,BLOG(vrmozart)的意思是my_vrmozart。
#define BLOG(name) name##_ blog,BLOG(vrmozart)的意思是vrmozart_ blog。
#定义blog (name) my _ # # name # # _ blog,BLOG(vrmozart)的意思是我的_vrmozart_ blog。
如果希望宏定义代码序列中的宏参数名称由宏参数名称的字符串形式(即宏参数名称两端的双引号)代替宏参数名称,可以在宏定义代码序列中的宏参数名称前添加符号#。#用于将宏参数名改为字符串形式。例如:
# define str (name) # vrmozzart,STR(VR mozzar)的意思是‘VR mozzar t’
当宏参数是另一个宏时,需要注意的是,宏定义代码序列中带#或# #的宏参数不会再展开。
宏观的独立性
在宏的定义中说,宏名和宏参数名所代表的内容和意义在宏展开前后必须始终独立不变,不能分开解释和执行。原因如下:调用宏时,宏名被宏定义的代码序列代替,宏参数名被宏参数名代替。替换后,宏定义的代码序列与源文件中的相邻代码自然相连,宏参数名也与代码序列中的相邻代码自然相连,因此宏定义的代码序列和宏参数名的独立性可能不再存在。例如:
#定义SQR(x) x*x,希望实现表达式的平方计算。
对于宏调用p=sqr (y),可以得到所需的宏展开p=y*y。但对于宏调用Q=sqr (U+V),宏展开就是Q=U+V * U+V,显然后者的开发结果并不是程序员所期望的。为了保持替换后宏参数名的独立性,在宏定义中要给形参加上括号。再者,为了保证宏名调用的独立性,作为公式的宏定义代码序列也要加括号。正确的宏定义是将SQ宏定义改写为#define SQR(x) ((x)*(x))。
宏调用和函数调用的区别
函数调用在程序运行时进行,而宏扩展在编译的预处理阶段进行;函数调用占用程序运行时间,而宏调用只占用编译时间;函数调用需要参数的类型,但是宏调用的实际参数和宏定义的形式参数之间没有类型的概念,只有字符序列的对应。函数调用可以返回值,宏调用可以得到想要的代码序列。此外,调用函数时,首先独立计算自变量表达式,然后执行函数体。这个宏调用是一个参数形式的实参数字符序列替换。
预定义宏
__DATE__,字符串常量类型,表示当前源文件的编译日期,输出格式为mmmdyyyy(如2006年5月27日)。
__TIME__,字符串常量类型,表示当前源文件的编译日期。输出格式为hh:mm:ss(例如09:11:10)。
__FILE__,字符串常量类型,表示当前源文件的名称,包含文件路径。
__LINE__,整数常量类型,表示当前源文件中的行号。
__FUNCTION__,字符串常量类型,指示当前函数名。
这些预定义的宏在调试程序时非常有用,因为您可以很容易地知道程序运行到文件的哪一行,以及它是哪个函数。
用户不仅可以在源文件的开头使用#define来定义宏,还可以在编译器项目的预处理器的属性页上定义宏。这个宏定义方法支持数字和字符串。一般形式是:标识符=数字或字符串常量。如果=并且省略了后面的内容,则宏名标识符默认为整数1。宏定义方法是在预处理器定义属性中输入宏定义内容,多个宏定义之间用分号隔开。预处理器定义中的宏定义应该在源文件中的宏定义之前处理,其有效范围是整个项目。除非在源文件中遇到重定义或者用#undef取消宏定义名称,否则宏定义名称在源文件中仍然有效。
3.条件编译指令
编译时一般要编译源程序中的每一行,但有时希望只在满足一定条件时才编译程序的某一部分。如果不满足这个条件,这部分就不编译,这就是条件编译。编译条件主要是在编译时选择并注释一些指定的代码,实现多版本控制和防止文件重复包含的功能。# if、# ifndef、# ifdef、# else、# elif、# endif是条件编译常用的预处理指令,可以根据表达式的值或者是否定义了特定的宏来确定编译条件。
说明意义
#如果expression非零,则编译代码;
#ifdef编译宏(如果已定义);
如果宏未定义,则#ifndef编译;
#else编译为其他预处理的剩余选项;
#elif这是#else和#if的组合选项;
#endif结束对编译块的控制。
常见形式
#if_#endif格式:
#if常量表达式或#ifdef宏名或#ifndef宏名
程序段
#endif
如果常量表达式为真,或者定义了宏名或者宏名未定义,则编译以下程序段;否则不要编译跳过这个程序。
#if_#else_#endif格式:
#if常量表达式或#ifdef宏名或#ifndef宏名
程序段1
#否则
程序段2
#endif
如果常量表达式为真或者宏名已定义或者宏名未定义,编译如下程序段1;否则,编译下面的程序段2。
#if_#elif_#endif格式:
#if常量表达式1
程序段1
#elif常量表达式2
程序段2
.
#elif常量表达式n
第n节
#endif
请注意,这种形式#elif不能在#ifdef和#ifndef中使用,但#else可以。
表达
预处理器表达式中包含的运算符主要涉及单个数运算(,-,~,),多个数运算(*,/,%,-,),关系比较(,=,=,=,=!=)、宏定义判断(已定义)、逻辑运算(!),它与C表达式运算符具有相同的优先级和行为。对于预处理器表达式,重要的是要记住它们是在编译器预处理器上执行的,而不是在编译之前。
下表列出了运算符的优先级顺序,从上到下,从高到低。您可以用括号更改优先级顺序。
例如:#ifndef和#if!Defined含义相同,#ifdef和#if defined含义相同。
4.其他预处理指令
除了上面讨论的常见预处理指令之外,还有三种不太常见的预处理指令:#line、#error和#pragma,下面将对它们进行介绍。
#线
#line指令用于重置当前由__FILE__和__LINE__宏指定的源文件的名称和行号。
#line的一般形式是#line number filename ,其中行号是任意正整数,filename是可选的。#line主要用于调试和其他特殊应用。请注意,在#line之后指定的行号表示下一行的行号。
#错误
#error指令使预处理程序发送一条错误消息,然后停止预处理。
#error的一般形式是#error info,比如# errormfccompilation。
#杂注
#pragma指令可能是最复杂的预处理指令。它的作用是设置编译器的状态或者指示编译器完成一些特定的动作。
#pragma的一般形式是#pragma para,其中para是一个参数。下面是一些常用的参数。
#pragma一次,只需在头文件的开头加上这条指令,保证头文件编译一次。
#pragma message(info ),并在编译后的信息输出窗口中输出相应的信息,如#pragma message(Hello )。
#pragma warning,设置编译器处理编译警告信息的方式,如# pragma warning(disable:4507 34;曾经:4385;Error:164)相当于#pragma warning(disable:4507 34)(不显示警告消息4507和34)、# pragma warning (once: 4385)(警告消息4385只报告一次)、#pragma warning(error:164)(警告消息164被视为错误
#pragma comment(…),在目标文件或可执行文件中设置要记录的注释。一种常见的lib注释类型,用于将库文件链接到目标文件。它的一般形式是#pragma comment(lib, *。lib’),其效果与在项目属性链接器“附加依赖项”中输入库文件相同。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。