📄 第5章 编译预处理.txt
字号:
不过,用enum关键字说明常量比用#define指令说明常量要占用更多的内存,因为前者需要分配内存来存储常量。
以下是一个在检测程序错误时使用的枚举常量的例子:
enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE_NOT_FOUND
} ;
与用#define说明常量相比,用enum说明常量还有其它好处,这一点将在5.7中作更详细的介绍。
请参见:
5.5 用#define指令说明常量有什么好处?
5.7 与用#denne指令说明常量相比,用enum关键字说明常量有什么好处?
5.7 与用#define指令说明常量相比,用enum关键字说明常量有什么好处?
与用#define指令说明常量(即说明标识符常量)相比,用enum关键字说明常量(即说明枚举常量)有以下几点好处:
(1) 使程序更容易维护,因为枚举常量是由编译程序自动生成的,而标识符常量必须由程序员手工赋值。例如,你可以定义一组枚举常量,作为程序中可能发生的错误的错误号,请看下例:
enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE+NOT_FOUND
} ;
在上例中,OUT_OF_MEMORY等枚举常量依次被编译程序自动赋值为0,1,2和3。
同样,你也可以用#define指令说明类似的一组常量,请看下例:
#define OUT_OF_MEMORY 0
#define INSUFFICIENT_DISK_SPACE 1
#define LOGIC_ERROR 2
#define FILE_NOT_FOUND 3
上述两例的结果是相同的。
假设你要增加两个新的常量,例如DRIVE_NOT_READY和CORRUPT_FILE。如果常量原来是用enum关键字说明的,你可以在原来的常量中的任意一个位置插入这两个常量,因为编译程序会自动赋给每一个枚举常量一个唯一的值;如果常量原来是用#define指令说明的,你就不得不手工为新的常量赋值。在上面的例子中,你并不关心常量的实际值,而只关心常量的值是否唯一,因此,用enum关键字说明常量使程序更容易维护,并且能防止给不同的常量赋予相同的值。
(2)使程序更易读,这样别人修改你的程序时就比较方便。请看下例:
void copy_file(char* source_file_name, char * dest_file_name)
{
......
Error_Code,err;
......
if(drive_ready()!=TRUE)
err=DRIVE_NOT_READY;
......
}
在上例中,从变量err的定义就可以看出;赋予err的值只能是枚举类型Error_Code中的数值。因此,当另一个程序员想修改或增加上例的功能时,他只要检查一下Error_Code的定义,就能知道赋给err的有效值都有哪些。
注意:将变量定义为枚举类型后,并不能保证赋予该变量的值就是枚举类型中的有效值。
在上例中,编译程序并不要求赋予err的值只能是Error—Code类型中的有效值,因此,程序员自己必须保证程序能实现这一点。
相反,如果常量原来是用#define指令说明的,那么上例就可能如下所示:
void copy_file(char *source *file,char *dest_file)
{
......
int err;
......
if(drive_ready()!=TRUE)
err=DRIVE_NOT_READY;
......
}
当另一个程序员想修改或增加上例的功能时,他就无法立即知道变量err的有效值都有哪些,他必须先检查头文件中的#defineDRIVE_NOT_READY语句,并且寄希望于所有相关的常量都在同一个头文件中定义。
(3)使程序调试起来更方便,因为某些标识符调试程序能打印枚举常量的值。这一点在调试程序时是非常用的,因为如果你的程序在使用枚举常量的一行语句中停住了,你就能马上检查出这个常量的值;反之,绝大多数调试程序无法打印标识符常量的值,因此你不得不在头文件中手工检查该常量的值。
请参见:
5.5 用#dehne指令说明常量有什么好处?
5.6 用enum关键字说明常量有什么好处?
5.8 如何使部分程序在演示版中失效?
如果你在为你的程序制作一个演示版,你可以通过预处理指令使你的程序的一部分生效或失效。以下是一个用#if和#endif指令实现上述功能的例子:
int save_document(char * doc_name)
{
#if DEMO_VERSION
printf("Sorry! You can't save documents using the DEMO version Of
->this program!\n");
return(0);
#endif
}
在编写演示版程序的源代码时,如果插入了#define DEMO_VERSION这行语句,预处理程序就会将上述save_document()函数中符合编译条件的代码包含进来,这样,使用演示版的用户就无法保存他们的文件。更好的方法是,在编译选项中定义DEMO_VERSION,这样就不必修改程序的源代码了。
上述技巧在许多不同的情况下都很有用。例如,如果你编写的程序可能要在多种操作系统或操作环境下使用,你就可以定义一些象WINDOWS_VER,UNIX_VER和DOS_VER这样的宏,通过它们指示预处理程序如何根据具体条件将相应的代码包含到你的程序中去。
请参见:
5.32怎样检查一个符号是否已被定义?
5.9 什么时候应该用宏代替函数?
见5.1-0。
请参见:
5.1什么是宏(macro)?怎样使用宏?
5.1-0使用宏更好,还是使用函数更好?
5.17怎样建立对类型不敏感的宏?
5.10 使用宏更好,还是使用函数更好?
这取决于你的代码是为哪种情况编写的。宏与函数相比有一个明显的优势,即它比函数效率更高(并且更快),因为宏可以直接在源代码中展开,而调用函数还需要额外的开销。但是,宏一般比较小,无法处理大的、复杂的代码结构,而函数可以。此外,宏需要逐行展开,因此宏每出现一次,宏的代码就要复制一次,这样你的程序就会变大,而使用函数不会使程序变大。
一般来说,应该用宏去替换小的、可重复的代码段,这样可以使程序运行速度更快;当任务比较复杂,需要多行代码才能实现时,或者要求程序越小越好时,就应该使用函数。
请参见:
5.1 什么是宏(macro)?怎样使用宏?
5.17 怎样建立对类型不敏感的宏?
5.11 在程序中加入注释的最好方法是什么?
大部分C编译程序为在程序中加注释提供了以下两种方法:
(1)分别是用符号“/*”和“*/”标出注释的开始和结束,在符号“/*”和“*/”之间的任何内容都将被编译程序当作注释来处理。这种方法是在程序中加入注释的最好方法。
例如,你可以在程序中加入下述注释:
/*
This portion Of the program contains
a comment that is several lines long
and is not included in the compiled
Version Of the program.
*/
(2)用符号“// ”标出注释行,从符号“// ”到当前行末尾之间的任何内容都将被编译程序当作注释来处理。当要加入一行独立的注释时,使用符号“//”是最方便的。但是,对于上面的例子,由于一段独立的注释中有4行内容,因此使用符号“//”是不合适的,请看下例:
// This portion Of the program contains
// a comment that is several lines long
// and is not included in the compiled
// Version Ofthe program.
需要注意的是,用符号"// "加入注释的方法与ANSI标准是不兼容的,许多版本较早的编译程序不支持这种方法。
请参见:
5.8 如何使部分程序在演示版中失效?
5.12 #include <file>和#include“file”有什么不同?
在C程序中包含文件有以下两种方法:
(1)用符号“<”和“>”将要包含的文件的文件名括起来。这种方法指示预处理程序到预定义的缺省路径下寻找文件。预定义的缺省路径通常是在INCLUDE环境变量中指定的,请看下例:
INCLUDE=C:\COMPILER\INCLUDE;S:\SOURCE\HEADERS;
对于上述INCLUDE环境变量,如果用#include<file>语句包含文件,编译程序将首先到C:\COMPILER\INCLUDE目录下寻找文件;如果未找到,则到S:\SOURCE\HEADERS
目录下继续寻找;如果还未找到,则到当前目录下继续寻找。
(2)用双引号将要包含的文件的文件名括起来。这种方法指示预处理程序先到当前目录下寻找文件,再到预定义的缺省路径下寻找文件。
对于上例中的INCLUDE环境变量,如果用#include“file”语句包含文件,编译程序将首先到当前目录下寻找文件;如果未找到,则到C:\COMPILER\INCLUDE目录下继续寻找;如果还未找到,则到S:\SOURCE\HEADERS目录下继续寻找。
#include<file>语句一般用来包含标准头文件(例如stdio.h或stdlib.h),因为这些头文件极少被修改,并且它们总是存放在编译程序的标准包含文件目录下。#include“file”语句一般用来包含非标准头文件,因为这些头文件一般存放在当前目录下,你可以经常修改它们,并且要求编译程序总是使用这些头文件的最新版本。
请参见:
5.3 怎样避免多次包含同一个头文件?
5. 4 可以用#include指令包含类型名不是“.h”的文件吗?
5.14 包含文件可以嵌套吗?
5.15 包含文件最多可以嵌套几层?
5.13 你能指定在编译时包含哪一个头文件吗?
你可以通过#if,#else和#endif这组指令实现这一点。例如,头文件alloc.h和malloc.h的作用和内容基本相同,但前者供BorlandC++编译程序使用,后者供MicrosoftC++编译程序使用。如果你在编写一个既支持BorlandC++又支持MicrosoftC++的程序,你就应该指定在编译时是包含alloc.h头文件还是包含malloc.h头文件,请看下例:
#ifdef __BORLANDC__
#include<alloc.h>
#else
#include<malloc.h>
#endif
当用BorlandC++编译程序处理上例时,编译程序会自动定义__BORLANDC__标识符名称,因此alloc.h头文件将被包含进来;当用microsoftC++编译程序处理上例时,由于编译程序检查到__BORLANDC__标识符名称没有被定义,因此malloc.h头文件将被包含进来。
请参见:
5.21怎样判断一个程序是用C编译程序还是用C++编译程序编译的?
5.32怎样检查一个符号是否已被定义?
5.14 包含文件可以嵌套吗?
包含文件可以嵌套,但你应该避免多次包含同一个文件(见5.3)。
过去,人们认为头文件嵌套是一种不可取的编程方法,因为它增加了MAKE程序中的依赖跟踪函数(dependencytrackingfunction)的工作负担,从而降低了编译速度。现在,通过引入预编译头文件(precompiledheaders,即所有头文件和相关的依赖文件都以一种已被预编译的状态存储起来)这样一种技术,许多流行的编译程序已经解决了上述问题。
许多程序员都喜欢建立一个自己的头文件,使其包含每个模块所需的每个头文件。这是一个头文件。
请参见:
5.3 怎样避免多次包含同一个头文件?
5.4 可以用#include指令包含类型名不是“.h”的文件吗?
5.12 #include<file~和#include“file”有什么不同?
5.15 包含文件最多可以嵌套几层?
5.15 包含文件最多可以嵌套几层?
尽管理论上包含文件可以嵌套任意多层,但是,如果嵌套层数太多,编译程序就会用光它的堆栈空间。因此,实际的嵌套层数是有限的,它一般取决于你的硬件设置和编译程序的版本。
在编写程序时,你应该避免过多的嵌套。一般来说,只有在确实需要时才嵌套包含文件,例如建立一个头文件,使其包含每个模块所需的每个头文件。
请参见:
5.3 怎样避免多次包含同一个头文件?
5.4 可以用#include指令包含类型名不是“.h”的文件吗?
5.12 #include<file>和#include“file”有什么不同?
5.14 包含文件可以嵌套吗?
5.16 连接运算符“##”有什么作用?
连接运算符“##”可以把两个独立的字符串连接成一个字符串。在C的宏中,经常要用到“##”运算符,请看下例:
#include<stdio.h>
#define SORT(X) sort_function # # X
void main(vOid);
void main(vOid)
{
char *array;
int elements,element_size;.
SORT(3) (array,elements,element_size);
}
在上例中,宏SORT利用“##”运算符把字符串sort_function和经参数x传递过来的字符串连接起来,这意味着语句
SORT(3)(array,elemnts,element_size);
将被预处理程序转换为语句
sort_function3(array,elements,element_size);
从宏SORT的用法中你可以看出,如果在运行时才能确定要调用哪个函数,你可以利用“##”运算符动态地构造要调用的函数的名称。
请参见:
5.1什么是宏?怎样使用宏?
5.17怎样建立对类型敏感的宏?
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -