二级考试C++基础:C++标准头文件结构介绍
在C语言中,没有内部机制来完成以下功能:包含其他源文件,定义宏,根据条件决定编译时是否包含某些代码。为了完成这些任务,我们需要使用预处理器。虽然目前大多数编译器都包含预处理器,但一般认为它们是独立于编译器的。预处理读取源代码,检查包含预处理指令的语句和宏定义,并作为响应转换源代码。预处理过程还会删除程序中的注释和多余的空白字符。
预处理指令是以#开头的代码行。#符号必须是除任何空白色字符以外的该行的第一个字符。#后跟指令关键字,关键字和#之间允许任意数量的空白色字符。整行语句构成一条预处理指令,它会在编译器编译之前对源代码进行一些转换。下面是一些预处理指令:
指令用法
# 空指令,没有作用
#include包含一个源代码文件
#define宏
#undef取消已定义的宏
#如果给编译下面的代码
#ifdef。如果定义了宏,则编译下面的代码
#ifndef。如果没有定义宏,编译下面的代码
#elif。如果前面的#if给定条件不为真,而当前条件为真,则编译以下代码
#endif以结束#if……#else条件编译块
#error以停止编译并显示错误消息
。首先,文件包含
#include预处理指令用于扩展包含在指令中的文件。包容可以是多重的,也就是说,一个被包容的文件也可以包含其他文件。标准C编译器支持至少八个嵌套包含。
预处理过程不检查文件是否已经包含在转换单元中,并防止它被多次包含。这样,当同一个头文件被多次包含时,通过在编译时给出条件,可以达到不同的效果。例如:
# define aa
# include " t . c "
# unde AAA
# include " t . c "
为了防止只能包含一次的头文件被多次包含,可以使用编译时条件对其进行控制。例如:
/* my . h */
# ifndefmy _ h
# definemy _ h
…
# endif
程序中包含头文件有两种格式:[/br这种格式告诉预处理器在编译器或外部库的头文件中搜索包含的头文件。第二种方法是用双引号将头文件括起来。这种格式告诉预处理器在当前编译的应用程序的源代码文件中搜索包含的头文件,如果找不到,那么就搜索编译器自带的头文件。
之所以采用两种不同的包含格式,是因为编译器安装在公共子目录中,而编译后的应用程序在自己的私有子目录中。应用程序包含编译器提供的公共头文件和用户定义的私有头文件。使用两种不同的包含格式使编译器能够在许多头文件中区分一组公共的头文件。
二。宏
宏定义了代表特定内容的标识符。定义宏时,预处理过程将用值替换源代码中的宏标识符。宏最常见的用途是定义一个表示值的全局符号。宏的第二个用途是定义带参数的宏。这样的宏可以像函数一样被调用,但它在调用语句处扩展了宏,并在调用时用实际参数替换了定义中的形参。
1。#define指令
#define预处理指令用于定义宏。这个指令最简单的格式是:先设计一个标识符,然后给出这个标识符代表的代码。在下面的源代码中,这些代码用于替换标识符。这个宏提取一些要在程序中使用的全局值,并将它们分配给一些内存标识符。
# define MAX _ NUM 10
int array[MAX _ NUM];
for(I = 0;在这个例子中,符号MAX_NUM对于阅读这个程序的人来说有特定的含义。它表示的值给出了数组中可以容纳的元素数量。这个值可以在程序中多次使用。按照惯例,定义宏时通常都用大写字母,这样很容易区分program red的宏标识符和通用变量标识符。如果要改变数组的大小,只需改变宏定义,重新编译程序即可。
宏表示的值可以是一个常量表达式,允许包含前面定义的宏标识符。例如:
# define one 1
# define two 2
# define teree(one+two)
注意,上面的宏定义使用了括号。尽管它们不是必需的。但出于谨慎,还是要加上括号。例:
六=三*二;
预处理过程将上面一行代码转换成:
six =(ONE+TWO)* TWO;
如果没有括号,就换算成六=一+二*二;走吧。
宏也可以表示一个字符串常量,例如:
# define version " version 1.0 copyright(c)2003 "
2。# define instruction with parameters
带参数的宏和函数调用看起来有些相似。看一个例子:
# define cube(x)(x)*(x)
可以用任何数值表达式甚至函数调用代替参数x。这里再次提醒大家注意括号的使用。扩展后的宏完全包含在一对括号中,参数也包含在括号中,从而保证了宏和参数的完整性。看一个用法:
int num = 8+2;
volume = Cube(num);
展开后是(8+2)*(8+2)*(8+2);
没有那些括号,就是8+2*8+2*8+2。
以下用法不安全:
volume = Cube(num++);
如果Cube是一个函数,那么上面的写法是可以理解的。但是因为Cube是宏,所以会有副作用。这里的删书不是简单的表达,它们会产生意想不到的结果。展开后它们是这样的:
volume =(num++) *(num++) *(num++);
很明显,结果是10*11*12而不是10 * 10 * 10;
那么如何安全使用Cube宏呢?可能产生副作用的操作必须移到宏调用之外:
int num = 8+2;
volume = Cube(num);
num++;
3。# operator
出现在宏定义中的#运算符将下列参数转换为字符串。有时#的这种用法被称为字符串运算符。例如:
# define paste(n)" adhfkj " # n
main()
{
printf(" % s \ n ",paste(15));
}
宏定义中的#运算符告诉预处理器将源代码中传递给宏的任何参数转换为字符串。所以输出应该是adhfkj15。
4。##运算符
##运算符用于将参数连接在一起。预处理器将出现在# #两边的参数组合成一个符号。看下面这个例子:
# definenum (a,b,c) a # # b # # c
# definestr (a,b,c)a # # b # # c
main()
{
printf(" % s \ n ",STR("aa "," bb "," cc "));
}
最终程序的输出是:
123
aabbcc
不用担心,很少有程序员会知道# #运算符,除非需要或者宏的用法恰好与手头的工作有关。大部分程序员都没用过。
三。条件编译指令
条件编译指令将决定编译哪些代码,不编译哪些代码。编译条件可以根据表达式的值或者是否定义了特定的宏来确定。
1。#if指令
#if指令检测到另一个关键字后面的常量表达式。如果表达式为true,则编译以下代码,直到出现#else、#elif或# endif否则不会被编译。
2。#endif指令
#endif用于终止#if预处理指令。
# define DEBUG 0
main()
{
# if DEBUG
printf(" DEBUG \ n ");
# endif
printf(" Running \ n ");
}
因为程序定义了DEBUG宏表示0,所以#if条件为false,下面的代码直到#endif才编译,所以程序直接输出Running。
如果删除#define语句,效果是一样的。
3。#ifdef和# ifndef
# define debug
main()
{
# ifdef debug
printf(" yes \ n ");
# endif
# ifndef DEBUG
printf(" no \ n ");
# endif
}
# if defined等效于# ifdef#如果!Defined等效于#ifndef
4。#else指令之后的#else指令
在某个#if指令中使用,当前面的#if指令的条件不为真时,编译# else之后的代码。#endif指令将引用上述条件块。
# define DEBUG
main()
{
# ifdef DEBUG
printf(" DEBUG \ n ");
#else
printf("未调试\ n ");
# endif
printf(" Running \ n ");
}
5。#elif指令
#elif预处理指令结合了#else和#if指令的功能。
# define TWO
main()
{
# ifdef ONE
printf(" 1 \ n ");
#elif定义了两个
printf(" 2 \ n ");
# else
printf(" 3 \ n ");
#endif
}
程序简单易懂,最终输出结果为2。
6。其他一些标准指令
#error指令会导致编译器显示一条错误消息,然后停止编译。
#line指令可以更改编译器用来指示警告和错误信息的文件号和行号。
#pragma指令没有正式定义。编译器可以自定义它的用法。典型用法是禁止或允许一些讨厌的警告消息。
0条评论