基于Proteus的AVR单片机C语言程序设计与仿真
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.8 编译预处理

编译预处理是C语言编译系统的一个重要组成部分。很好地利用C语言的预处理命令可以增强代码的可读性、灵活性,使其易于修改,并便于程序的结构化。在C语言程序中,凡是以“#”开头的均表示这是一条预处理命令语句,如包含命令#include、宏定义命令#define等。C提供的预处理功能有3种:宏定义、文件包含和条件编译。

1.宏定义

宏定义命令为#define,它的作用是实现用一个简单易读的字符串来代替另一个字符串。宏定义可以增强程序的可读性和维护性。宏定义分为不带参数的宏定义和带参数的宏定义。

1)不带参数的宏定义不带参数的宏定义,其宏名后不带参数。不带参数宏定义的一般形式为

            #define  标识符  字符串

其中,“#”表示这是一条预处理命令;“define”表示为宏定义命令;“标识符”为所定义的宏名;“字符串”可以是常数、表达式等。

例如:

            #define  PI  3.1415926

该语句的作用是指定用标识符(即宏名)PI代替“3.1415926”字符串,这种方法使用户能以一个简单的标识符代替一个长的字符串。当程序中出现3.1415926这个常数时,就可以用PI这个字符来代替了。如果想修改这个常数,只需要修改这个宏定义中的常数即可,这就是增加程序的维护性的体现。

对于宏定义,需要说明以下几点。

(1)宏定义是用宏名代替一个字符串,在宏展开时又以该字符串取代宏名,因此它是一种简单的替换。通过这种宏定义的方法,可以减少程序中重复书写某些字符串的工作量。字符串中可以包含任何字符、常数或表达式,预处理程序对它不进行任何检查。

(2)宏名可以用大写或小写字母表示,但为了区别于一般的变量名,通常采用大写字母。

(3)宏定义不是C语句,不用加分号;如果加分号,则在编译时会连同分号一起转换。

(4)当宏定义在一行中书写不下,需要在下一行继续书写时,应该在最后一字符后紧跟着加一个反斜线“\”,并在新的一行的起始位置继续书写,起始位置不能插入空格。

(5)可以用#undef终止一个宏定义的作用域。

(6)一个宏命令只能定义一个宏名。

2)带参数的宏定义带参数的宏在预编译时不但要进行字符串替换,还要进行参数替换。带参数的宏定义的一般形式为

            #define  宏名(形参表)字符串

带参数的宏调用的一般形式:

            宏名 (实参表);

例如:

            #define MIN(x,y)  ((x)<(y))?(x):(y))    //宏定义
            a=MIN(3,7)                       //宏调用

对于带参数的宏定义,有以下问题需要说明。

(1)在带参数的宏定义中,宏名和形参表之间不能有空格出现,否则会将空格以后的字符都当做替换字符串的一部分。

(2)在宏定义中,字符串内的形参最好用“()”括起来以避免出错。

(3)带参数的宏与函数是不同的:①函数调用时,会先求出表达式的值,然后代入形参,而使用带参数的宏只是进行简单的字符替换,在宏展开时并不求解表达式的值;②函数调用是在程序运行时处理的,会分配临时的内存单元,而使用带参数的宏只是在编译时进行处理的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念;③对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换,而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号而已,展开时代入指定的字符即可;④调用函数只能得到一个返回值,而使用宏可以设法都到几个结果。

2.文件包含

所谓“文件包含”是指一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件中。C语言中的“#include”为文件包含命令,其一般形式为

            #include<文件名>

            #include“文件名”

例如:

            #include<mega16.h>
            #include<stdio.h>
            #include<delay.h>

上述程序的文件包含命令的功能是将“mega16.h”文件插入#include <mega16.h>位置;将“stdio.h”文件插入#include <stdio.h>命令行位置;将“delay.h”文件插入#include<delay.h>命令行位置。也就是说,在编译预处理时,源程序将“mega16.h”、“stdio.h”和“delay.h”这3个文件的全部内容复制并分别插入#include <mega16.h>、#include <stdio.h>、#include<delay.h>命令行位置。

在程序设计中,文件包含是很有用的。它可以节省程序设计人员的重复工作,或者可以先将一个大的程序分成多个源文件,由多个编程人员分别编写程序,然后再用文件包含命令把源文件包含到主文件中。使用文件包含命令时,需注意以下事项。

(1)在#include命令中,文件名可以用双引号或尖括号的形式括起来,但这两种形式有所区别:采用双引号将文件括起来时,系统首先在引用被包含文件的源文件所在的C文件目录中寻找要包含的文件,如果找不到,再按系统指定的标准方式搜索\inc目录;使用尖括号将文件括起来时,不检查源文件所在的文件目录而直接按系统指定的标准方式搜索\inc目录。

(2)一个#include命令只能指定一个被包含文件,如果要包含多个文件,则需要使用多个include命令。

(3)#include命令行包含的文件称为“头文件”。头文件名可以由用户指定,也可以是系统头文件,其后缀名为.h。

(4)在一个被包含的文件中同时又可以包含另一个被包含的文件,即文件包含可以嵌套。通常,嵌套有深度的限制,这种限制根据编译器的不同而不同。在CodeVisionAVR C编译器中,最多允许16层文件的嵌套。

(5)当被包含文件修改后,对包含该文件的源程序必须重新进行编译。

(6)#include语句可以位于代码的任何位置,但它通常设置在程序模块的顶部,以提高程序的可读性。

3.条件编译

通常情况下,在编译器中单击文件编译时,将会对源程序中所有的行都进行编译(注释行除外)。如果程序员想要源程序中的部分内容只在满足一定条件时才进行编译,可通过“条件编译”对一部分内容指定编译的条件来实现相应操作。条件编译命令有以下3种形式。

1)第1种形式

            #ifdef  标识符
            程序段1
            #else
            程序段2
            #endif

其作用是:当标识符已经被定义过(通常是用#define命令定义)时,只对程序段1进行编译,否则编译程序段2。如果没有程序段2,则本格式中的“#else”可以没有。程序段1可以是语句组,也可以是命令行。

2)第2种形式

            #ifndef  标识符
            程序段1
            #else
            程序段2
            #endif

其作用是:当标识符没有被定义时,只对程序段1进行编译,否则编译程序段2。这种形式的作用与第1种形式的作用正好相反。在书写上将第1种形式中的“#ifdef”改为“#ifndef”即可。

3)第3种形式

            #if  常量表达式
            程序段1
            #else
            程序段2
            #endif

其作用是:如果常量表达式的值为逻辑“真”,则对程序段1进行编译,否则编译程序段2。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。