• 沒有找到結果。

在 file2.c 文件中没有定义变量 x,y,ch,而是用 extern 声明 x,y,ch 是外部变量,因此 在 file1.c 中已定义的变量在 file2.c 中可以引用。x,y 在 file1.c 中被赋值,它们在 file2.c 中也 作为全局变量,因此 printf 语句输出 12 和 24。同样,在 file2.c 中对 ch 赋值'a',在 file1.c 中也 能引用它的值。当然,要注意操作的先后顺序,只有先赋值才能引用。

(2)限定本文件的外部变量只在本文件中使用。如果有的外部变量只允许本文件使用而 不允许其他文件使用,则可以在此外部变量前加一个  static,使其有限局部化,称作静态外部 变量。例如:

static int a=3,b=5; 

void main() 

…… 

void f1() 

…… 

void f() 

…… 

}

在本文件中,a,b 为全局变量,但作用域也仅限于本文件。

使用静态外部变量的好处是:当多人分别编写一个程序的不同文件时,可以按照需要命 名变量,而不必考虑是否会与其他文件中的变量同名,以保证文件的独立性。

由此,可以将这四类存储属性的性质总结如表 6­1 所示。

表 6­1  四类存储属性的性质

存储属性  register  auto  static  extern 

存储位置 寄存器 主存

生存期 动态存储 永久存储

作用域 局部 局部或全局 全局 

6.6  编译预处理

编译预处理是在编译前对源程序进行的一些预处理。预处理由编译系统中的预处理程序 按源程序中的预处理命令进行。 

C 语言的预处理命令均以“#”打头,末尾不加分号,以区别于 C 语句。它们可以出现在 程序中的任何位置, 其作用域是自出现点到所在源程序的末尾。 前面我们已经使用过两个预处 理命令:#define 和#include。

编译预处理是  C  语言的一个重要特点,合理地使用预处理功能编写的程序便于阅读、修

改、移植和调试,也有利于模块化程序设计。本节介绍常用的几种预处理功能。 

6.6.1  宏定义

在 C 语言源程序中允许用一个标识符来表示一个字符串,称为“宏” 。被定义为“宏”的 标识符称为“宏名” 。在编译预处理时,对程序中所有出现的“宏名” ,都用宏定义中的字符串 去代换,这称为“宏代换”或“宏展开” 。

宏定义是由源程序中的宏定义命令完成的,宏代换是由预处理程序自动完成的。

在 C 语言中, “宏”分为有参数和无参数两种。下面分别讨论这两种“宏”的定义和调用。 

1.无参宏定义

无参宏的宏名后不带参数。其定义的一般形式为: 

#define  宏名 字符串

其中,作为宏名的标识符习惯上用有意义且易理解的大写字母来表示, “字符串”可以是 常数、表达式或格式串等。宏定义一般写在文件开头函数体的外面,有效范围是从定义宏命令 之后到遇到终止宏定义命令#undef 为止,否则其作用域将一直到源文件结束。

例如:

#define PI 3.1415926

即定义了宏名 PI 来代表 3.1415926。在编译预处理时,系统将把该命令之后作用域之内的所有  PI 都自动用 3.1415926 代换, 即进行宏展开。 实际上这就是前面介绍过的符号常量的定义形式。

使用宏定义,一方面可以减少频繁使用的字符串的重复书写;另一方面也使得修改重复 使用的字符串的工作变得简单了,因为只需在宏定义处修改一次即可。

对于宏定义还要说明以下几点:

(1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一 种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不 作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。

例如:

#define PI  "3.1415926"

那么,在预处理时将把 PI 替换为带双引号的字符串"3.1415926",代入表达式中就会出错。

(2)如果在一行中写不下整个宏定义,需要用两行或更多行来书写时,只需在每行的最 后一个字符的后面加上反斜杆“\” ,并在下一行的最开始接着写即可。

(3)宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作 用域可使用#undef 命令。

例如:

#define PI 3.14159  void main() 

…… 

#undef PI  f1() 

…… 

}

表示 PI 只在 main 函数中有效,在 f1 中无效。

(4)宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。

例如,如果程序中有以下语句:

printf("PI=",PI);

在预处理时,将只对第二个 PI 进行代换,而对第一个双引号中的 PI,系统并不对其作代换。

执行该语句会输出:

PI=3.1415926

(5)宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预 处理程序层层代换。

例如:

#define R 5.6 

#define PI 3.1415926 

#define  L  2*PI*R 

#define S PI*R*R  /* PI,R 是已定义的宏名*/

在宏展开时,编译器会把程序中的 R 用 5.6 来代换,把 PI 用 3.1415926 来代换,而将 S  用 3.1415926*5.6*5.6 来代换。

【例 6.17】 已知半径为 5.0,计算以此为半径的圆的周长和面积,以及圆球体的体积。

#include"stdio.h" 

#define  R  5.0 

#define  PI  3.1415926 

#define  S  PI*R*R 

#define  V  4.0/3.0*PI*R*R*R  void main() 

printf("L=%f\nS=%f\nV=%f\n",L,S,V) ;  }

运行结果如下:

L=31.415926  S=78.539815  V=523.598767  2.带参宏定义 

C 语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参 数。对带参数的宏,在调用中,不仅要进行宏展开,而且要用实参去代换形参。

带参宏定义的一般形式为: 

#define  宏名(形参表列)  字符串 在字符串中含有各个形参。

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

宏名(实参表列); 

例如:

#define  M(y)  y*y+3*y      /*宏定义*/ 

…… 

k=M(5);       /*宏调用*/ 

……

在宏调用时,用实参 5 去代替形参 y,经预处理宏展开后的语句为:

k=5*5+3*5;

【例 6.18】 求两数中较大者。

#include"stdio.h"

#define MAX(a,b) (a>b)?a:b  void main() 

int x,y,mymax; 

printf("请输入 x,y 的值:\n"); 

scanf("%d,%d",&x,&y); 

mymax=MAX(x,y); 

printf("max=%d\n",mymax); 

}

上例程序的第二行进行带参宏定义,用宏名  MAX  表示条件表达式(a>b)?a:b,形参  a,b  均出现在条件表达式中。程序第 8 行 mymax=MAX(x,y)为宏调用,实参 x,y 将代换形参 a,b。

宏展开后该语句为:

mymax=(x>y)?x:y;

用于计算 x,y 中的较大者。

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

(1)带参宏定义中,宏名和形参表之间不能有空格出现。

例如,把

#define MAX(a,b) (a>b)?a:b 写为:

#define MAX(a,b) (a>b)?a:b

将被认为是无参宏定义,宏名 MAX 代表字符串  (a,b) (a>b)?a:b。宏展开时,宏调用语句:

mymax=MAX(x,y);

将变为:

mymax=(a,b)(a>b)?a:b(x,y);

这显然是错误的。

(2)在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的 实参有具体的值,要用它们去替换形参,因此必须作类型说明,这与函数中的情况是不同的。

在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进 行“值传递” 。而在带参宏中,只是符号代换,不存在值传递的问题。

(3)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。

例如:

#define SQ(y) (y)*(y) 

…… 

sq=SQ(a+1); 

……

宏定义中形参为 y,宏调用时实参为 a+1,是一个表达式,在宏展开时,用 a+1 代换 y,

再用(y)*(y)  代换 SQ,得到如下语句:

sq=(a+1)*(a+1);

这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再传递给形参。而宏 代换中对实参表达式不作计算直接照原样代换。

(4)在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

【例6.19】 计算下列函数值: 

3 3 

f (x)=x +(x+ 1) 其中自变量 x 的值从键盘输入。

如果将计算 x  的值定义为一个带参数的宏,即

#define F(x) x*x*x

计算函数值 f (x) 的 C 程序写成如下:

#include<stdio.h> 

#define F(x) x*x*x  main() 

{  double f,x; 

printf("input x:"); 

scanf("%lf",&x); 

f=F(x)+F(x+1); 

printf("f=%f\n",f); 

}

运行时, 当输入 1 后, 结果为 5.0, 而不是期望的 9.0。 因为当 C 编译系统编译预处理 F(x+1)  时,经宏展开后,等价于“x+1*x+1*x+1” ,而不等价于“(x+1)*(x+1)*(x+1)” 。因此,为了使 定义的宏展开合理,就必须将宏定义字符串中的参数都用括号括起来,即将上述程序改为:

#include<stdio.h> 

#define F(x) (x)*(x)*(x)  main() 

{  double f,x; 

printf("input x:"); 

scanf("%lf",&x); 

f=F(x)+F(x+1); 

printf("f=%f\n",f); 

}

经编译预处理后等价于下面的程序:

#include<stdio.h> 

main() 

{  double f,x; 

printf("input x:"); 

scanf("%lf",&x); 

f=(x)*(x)*(x)+(x+1)*(x+1)*(x+1);; 

printf("f=%f\n",f); 

}

在使用带参数的宏定义时,除了应将宏定义字符串中的参数都要用括号括起来,还需要 将整个字符串部分也用括号括起来,否则经过宏展开后,还可能出现意想不到的错误。下面的 例子说明了这个问题。

【例 6.20】 计算下列函数值: 

3 2 3 2 

f (x, y)=(x +x )[(y 1)+ +(y 1) ] + 其中子变量 x 的值从键盘输入。

如果将计算 x3+ x 的值定义为一个带参数的宏,即

#define F(x) (x)*(x)*(x)+(x)*(x)

此时,在程序中就可以将 x3+ x 写成  F(x),将 (y 1)+ 3+(y 1) + 写成  F(x+1)。计算函数  f (x, y) 的 C 程序就可以写成:

#include<stdio.h> 

#define F(x) (x)*(x)*(x)+(x)*(x)  main() 

double f,x,y; 

printf("input x,y:"); 

scanf("%lf,%lf",&x,&y);  /*输入的两个数之间用逗号做分隔符*/

f=F(x)*F(y+1); 

printf("f=%f\n",f); 

}

这个程序经编译预处理宏展开后 ,赋值语句:

f=F(x)*F(y+1);

等价于语句: 

3 2 3 2 

f =x +x * (y 1)+ +(y 1) + 这显然是错误的。正确的应该是: 

3 2 3 2 

f =(x +x ) * ((y 1)+ +(y 1) ) +

有这个例子可以看出,为了使定义的宏展开合理,不仅需要将宏定义字符串中的参数都 括起来,还必须将整个字符串用括号括起来。即将上述程序修改为:

#include<stdio.h> 

#define F(x) ((x)*(x)*(x)+(x)*(x))  main() 

double f,x,y; 

printf("input x,y:"); 

scanf("%lf,%lf",&x,&y);/*输入的两个数之间用逗号做分隔符*/ 

f=F(x)*F(y+1); 

printf("f=%f\n",f); 

}

此时上面的程序等价于下面的程序:

#include<stdio.h> 

main() 

{  double f,x,y; 

printf("input x,y:"); 

scanf("%lf,%lf",&x,&y);/*输入的两个数之间用逗号做分隔符*/ 

f=(x +x )*((y+1) +(y+1) );

printf("f=%f\n",f); 

}

有上面的分析可以看出,在使用带参数的宏定义时,一般应将宏定义字符串中的参数都 用括号括起来,并且整个字符串部分也要用括号括起来,这样才能保证在任何替代情况下,把 宏定义作为一个整体来看待, 从而得到一个合理的计算顺序, 否则经过宏展开后可能出现意想 不到的错误。

在 C 程序中,可以利用带参数的宏定义来表示一些比较简单的函数表达式。

(5)带参的宏和带参函数很相似,但有本质上的不同,除上面已讲到的几点外,把同一 表达式用函数处理与用宏处理两者的结果有可能是不同的。

【例 6.21】 用用户自定义函数求 n 

#include"stdio.h" 

void main() 

int i=1; 

while (i<=5) 

printf("%d\n",sq(i++)); 

int sq(int y) 

return((y)*(y)); 

}

运行结果如下:

16  25

【例 6.22】 用宏定义函数求 n 

#include"stdio.h" 

#define SQ(y) ((y)*(y))  void main() 

int i=1; 

while (i<=5) 

printf("%d\n",SQ(i++)); 

}

运行结果如下:

12  30

在例 6.18 中函数名为 sq,形参为 y,函数体表达式为((y)*(y))。在例 6.19 中宏名为 SQ,

形参也为  y,字符串表达式为((y)*(y))。例  6.18  的函数调用为  sq(i++),例  6.19  的宏调用为  SQ(i++),实参也是相同的。从输出结果来看,却大不相同。

分析:在例 6.18 中,函数调用是把实参 i 值传给形参 y 后自增 1,然后输出函数值。因而 要循环  5  次,输出  1~5  的平方值。而在例  6.19  中宏调用时,只作代换。SQ(i++)被代换为  ((i++)*(i++))。在第一次循环时,由于 i 等于 1,其计算过程为:表达式中前一个 i 初值为 1,

然后 i 自增 1 变为 2,因此表达式中第 2 个 i 初值为 2,相乘的结果也为 2,然后 i 值再自增 1 

然后 i 自增 1 变为 2,因此表达式中第 2 个 i 初值为 2,相乘的结果也为 2,然后 i 值再自增 1 

相關文件