1
第 7章 函数与预处理命令北京科技大学 计算机系
C 语言程序设计
2005年 3月
2第 7章 函数与预处理命令
7.1 概述
7.2 函数的定义与调用
7.3 数组作函数参数
7.4 函数的嵌套调用和递归调用
7.5 局部变量和全局变量及其作用域
7.6 变量的存储类别及变量的生存期
7.7 函数的存储分类
7.8 编译预处理
7.9 多文件程序的调试方法
2005年 3月
37.1 概述
① 程序结构清晰,可读性好。
② 减少重复编码的工作量。
③ 可多人共同编制一个大程序,缩短程序设计周期,提高程序设计和调试的效率。
使用函数的好处
… …
C程序源程序文件 n
函数 1 函数 m
… …源程序文件 1
函数 1 函数 n
2005年 3月
4【 例 7.1】 求一个整数的立方。
int cube (int x) /* 函数定义 */
{ return (x * x * x); }
main( )
{ int f,a;
printf("\nEnter an integer number,");
scanf("%d",&a);
f = cube (a);
printf("%d * %d * %d = %d\n",a,a,a,f);
} 程序运行情况如下:
Enter an integer number,2?
2 * 2 * 2 = 8
函数调用程序的执行总是从 main函数开始
2005年 3月
5
⑴ 一个 C源程序可以由一个或多个源程序文件组成。 C编译系统在对 C源程序进行编译时是以文件为单位进行的。
⑵ 一个 C源程序文件可以由一个或多个函数组成。
所有函数都是独立的。主函数可以调用其它函数,
其它函数可以相互调用。
⑶ 在一个 C程序中,有且仅有一个主函数 main。
C程序的执行总是从 main函数开始,调用其它函数后最终回到 main函数,在 main函数中结束整个程序的运行。
说明
2005年 3月
6⑷ 函数的种类从函数定义形式分:
① 有参函数:
在主调(用)函数和被调
(用)函数之间通过参数进行数据传递,如:
int cube (int x) { … }
② 无参函数:
如,getchar( )
在调用无参函数时,主调函数不需要将数据传递给无参函数。
从使用的角度看:
① 标准函数(库函数)
库函数是由系统提供的。
如,getchar( ),sin(x)等。
在程序中可以直接调用它们。附录 A列出了 C的部分库函数。
② 用户自定义函数。
如,例 7.1中的 cube函数。
2005年 3月
7【 例 7.2】 无参函数的定义与调用。
void welcome ( )
{ printf("*********************************\n");
printf(" Welcome to China \n");
printf("*********************************\n");
}
main( )
{ welcome( );}
程序的输出结果如下:
*********************************
Welcome to China
*********************************
2005年 3月
87.2.1 函数的定义函数定义的一般形式函数类型 函数名 (类型名 形式参数 1,… )
{ 说明语句执行语句
}
例如:求两个数的最大值。
int max(int x,int y)
{ int z;
z = x > y? x,y;
return( z );
}
类型省略时默认为 int
类型 没有形式参数为 无参函数
2005年 3月
9
int max(x,y)
int x,y;
{ int z;
z = x > y? x,y;
return( z );
}
int max(x,y)
{ int x,y;
……
}

int max(int x,y)
{ …… }

int max(x,y)
int x,y,z;
{ z = x > y? x,y;
return( z );
}
花括号中也可以为空,这种函数叫 空函数 。
不能在函数体内定义其他函数,即函数 不能嵌套定义 。
形参也可以这样定义 如下定义都是错误的

2005年 3月
10
函数名(实参表列)
在 C语言中,把函数调用也作为一个表达式。
因此凡是表达式可以出现的地方都可以出现函数调用。例如:
① welcome( );
② if (iabs (a)>max) max=iabs(a);
③ m=max(c,max(a,b));
7.2.2 函数的调用函数调用的一般形式:
2005年 3月
11
int sum100( )
{ int i,t=0;
for (i=1; i<=100; i++)
t+=i;
return (t);}
main( )
{ int s;
s=sum100( );
printf("%d\n",s);
}
程序输出结果:
5050
int sum ( int x )
{ int i,t=0;
for (i=1; i<=x; i++)
t+=i;
return (t);
}
main( )
{ int s;
s=sum (100);
printf("%d\n",s);
}
【 例 7.3】 求 1~ 100的累加和。
思考:
两个程序有何不同程序输出结果:
5050
2005年 3月
12
void swap(int x,int y)
{ int z;
z=x; x=y; y=z;
printf("\nx=%d,y=%d",x,y);
}
main( )
{ int a= 10,b=20;
swap(a,b);
printf("\na=%d,b=%d\n",a,b);
}
7.2.3 函数参数与函数的返回值
1.函数的形式参数与实际参数程序输出结果:
x=20,y=10
a=10,b=20
形式参数(形参)
实际参数(实参)
【 例 7.4】 编一程序,将主函数中的两个变量的值传递给 swap函数中的两个形参,交换两个形参的值。
单向值传递
2005年 3月
13有关形参和实参的说明:
① 当函数被调用时才给形参分配内存单元。调用结束,所占内存被释放。
② 实参可以是常量、变量或表达式,但要求它们有确定的值。
③ 实参与形参类型要一致,字符型与整型可以兼容

④ 实参与形参的个数必须相等。在函数调用时,实参的值赋给与之相对应的形参。,单向值传递”

注意:在 TC中,实参的求值顺序是从右到左。
2005年 3月
14【 例 7.5】 函数调用中实参的求值顺序。
void fun(int a,int b)
{ printf("a=%d,b=%d\n",a,b); }
main( )
{ int m=5;
fun(3+m,m++);
}
程序输出结果:
a=9,b=5
2005年 3月
152.函数的类型与函数的返回值说明:
①函数的类型决定了函数返回值的类型。
若省略函数的类型,系统默认其为 int型。
②无返回值的函数应将其类型定义为 void
(空)类型。
⑴ 函数的类型 【 例 7.6】 输出两个数中的大数。
max(int x,int y)
{ int z;
z=x>y?x:y;
return (z); /* 返回 z的值 */
}
main( )
{ int a,b,c;
scanf("%d,%d",&a,&b);
c=max(a,b);
printf("max is %d\n",c);
}
2005年 3月
16
函数的返回值是通过 return语句带回到主调函数的功能,终止函数的运行,返回主调函数,若有返回值,将返回值带回主调函数。
说明:
① 若函数没有返回值,return语句可以省略。
② return语句中的表达式类型一般应和函数的类型一致,如果不一致,系统自动将表达式类型转换为函数类型 。
⑵ 函数的返回值
return 语句格式:
return (表达式 ); 或 return 表达式 ;
或 return;
2005年 3月
17【 例 7.8】 计算并输出圆的面积。
s(int r)
{ return 3.14*r*r;}
main( )
{ int r,area;
scanf("%d",&r);
printf("%d\n",s(r));
}
自动转换为 int型思考:
若要得到单精度实型的圆面积,程序应如何修改程序运行情况如下:
2?
12
2005年 3月
187.2.4 对被调函数的声明和函数原型变量要 先定义后使用,
函数也如此 。即 被调函数的定义要出现在主调函数的定义之前 。如 swap函数,
允许整型函数(且参数也是整型)的定义出现在主调函数之后。如 max函数,
如果非整型函数在主调函数之后定义,则应在主调函数中或主调函数之前对 被调函数进行声明。
void swap(int x,int y)
{ …}
main( )
{ …
swap(a,b);
}
main( )
{ …
c=max(a,b);
}
max(int x,int y)
{ …}
2005年 3月
19对被调函数进行声明的一般形式函数类型 函数名( 参数类型 1 参数名 1,… ) ;
或 函数类型 函数名( 参数类型 1,参数类型 2,… ) ;
思考,
以下哪种情况需要 在主调函数中 对被调函数 声明
被调函数定义在前,主调函数定义在后 。
主调函数定义在前,被调函数定义在后,且被调函数的类型不是整型的。
被调函数定义在后,但被调函数的类型是 整型 。
第二种形式省略了参数名,此种形式也称为 函数的原型 。
2005年 3月
20
main( )
{ void calc(float x,float y,char opr);
float a,b; char opr;
printf("\nInput expression:");
scanf("%f%c%f",&a,&opr,&b);
calc(a,b,opr);
}
void calc(float x,float y,char opr)
{ switch(opr)
{ case '+':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x+y);return;
case '-':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x-y);return;
case '*':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x*y);return;
case '/':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x/y);return;
default,printf("Operator err! \n"); }
}
对被调函数的声明
【 例 7.9】 计算并输出两个数的和、差、积、商。
2005年 3月
21
图 7.2 验证哥德巴赫猜想输入 n的值
for (a=6; a<=n; a+=2)
for(b=3;b<=a/2;b+=2)
b是素数?
T F
c=a-b
c是素数?
T F
输出:
a,b,c的值
break;
【 例 7.10】 哥德巴赫猜想之一是任何一个大于 5的偶数都可以表示为两个素数之和。验证这一论断。
2005年 3月
22
#include "math.h"
int prime(int n);
main( )
{ int a,b,c,n;
scanf("%d",&n);
for (a=6; a<=n; a+=2)
for (b=3; b<=a/2; b+=2)
if (prime(b))
{ c=a-b;
if (prime(c))
{ printf("%d=%d+%d\n",a,b,c);
break; }
}
}
/* 穷举法判断素数 */
int prime(int n)
{ int i;
for (i=2; i<=sqrt(n); i++)
if (n%i==0) return 0;
return 1;
}
可以在 main函数的前面对 prime
函数进行声明。实际上,该声明可以省略,为什么?
程序如下:
2005年 3月
237.3 数组作函数参数
7.3.1 一维数组元素作函数参数
main( )
{ int a[5],i,m ;
for (i=0; i<5; i++)
scanf("%d",&a[i]);
m=a[0];
for (i=1; i<5; i++)
m=min(m,a[i]);
printf("%d\n",m);
}
【 例 7.11】 求 5个数中的最小值。
int min(int x,int y)
{ return (x<y?x:y);
}
用打擂台方法求最小值。 m相当于擂主
2005年 3月
247.3.2 一维数组名作函数参数
数组名 表示数组在内存中的 起始地址 。
例如,数组 a在内存中从 2000地址开始存放,则 a的值为 2000。 2000是地址值,是指针类型的数据(第 8
中将介绍指针类型),不能把它看成是整型或其他类型数据。
实参是数组名,形参也应定义为 数组形式,形参数组的长度可以省略,但 [ ]不能省,否则就不是数组形式了。
【 例 7.12】 用冒泡法将 10个整数排序。
2005年 3月
25void sort(int b[ ],int n);
void printarr(int b[ ]);
main( )
{ int a[10] =
{11,22,63,97,58,80,45,
32,73,36};
printf("Before
sort:\n");
printarr(a);
sort(a,10);
printf("After sort:\n");
printarr(a);
}
void printarr(int b[10])
{ int i;
for (i=0; i<10; i++)
printf("%5d",b[i]);
printf("\n");}
void sort(int b[ ],int n)
{ int i,j,t;
for (i=1; i<n; i++)
for (j=0; j<n-i; j++ )
if (b[j]>b[j+1])
{ t=b[j];b[j]=b[j+1];b[j+1]=t; }
}
2005年 3月
26
a[0] a[1] a[2] a[3] a[4 ] a[5] a[6] a[7] a[8] a[9]
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
11 22 63 97 58 80 45 32 73 36
(a) 排序前
a[0] a[1] a[2] a[3] a[4 ] a[5] a[6] a[7] a[8] a[9]
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
11 22 32 36 45 58 63 73 80 97
(b) 排序后图 7.3 调用 sort函数
2000b 形参 b 实际是一个可以存放地址的变量a:2000
实参赋给形参首地址:
2000
首地址:
2005年 3月
27
#include "stdio.h"
main( )
{ void scat(char str1[ ],char str2[ ]);
char s1[50],s2[50]; int i,k;
printf("Input s1:");
gets(s1);
printf("Input s2:");
gets(s2);
scat(s1,s2);
printf("Output s1:%s\n",s1);
printf("Output s2:%s\n",s2);
}
void scat(char str1[ ],char str2[ ])
{ int i=0,k=0;
while (str1[i]!='\0') i++;
while (str2[k]!='\0')
{ str1[i]=str2[k];
i++; k++;
}
str1[i]='\0';
}
scat函数还可简化为:
void scat(char str1[ ],char str2[ ])
{ int i=0,k=0;
while (str1[i]) i++;
while (str1[i++]=str2[k++]);
}
【 例 7.13】 编程序,实现字符串连接。
2005年 3月
28
以二维数组为例。二维数组名作实参时,对应的形参也应该定义为一个二维数组形式。对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。
【 例 7.14】 编程序,将矩阵转置。设转置前为 a
矩阵,转置后为 b矩阵,如下所示:
a=
1 2 3 4
5 6 7 8
9 10 11 12
1 5 9
2 6 10
3 7 11
4 8 12
b=
思路:将 a[0][0]?b[0][0],a[0][1]?b[1][0],a[0][2]?b[2][0],
a[1][0]?b[0][1],…,a[i][j]?b[j][i],… 。
7.3.3 多维数组作函数参数
2005年 3月
29void turn( );
main( )
{ int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int i,j,b[4][3];

turn(a,b);
printf("array b:\n");
for (i=0; i<4; i++)
{ for (j=0; j<3; j++)
printf("%5d",b[i][j]);
printf("\n");
}
}
/* 矩阵转置函数 */
void turn(int arra[ ][4],int
arrb[ ][3])
{ int r,c;
for (r=0; r<3;r++)
for (c=0; c<4; c++)
arrb[c][r]=arra[r][c];
}
2005年 3月
307.4 函数的嵌套调用和递归调用
main函数
{ ……
调用函数 A;
……
}
函数 A
{ ……
调用函数 B;
……
}
函数 B
{ ……
……
……
}
7.4.1 函数的嵌套调用
2005年 3月
31【 例 7.15】 函数的嵌套调用
main( )
{ int n=3;
printf ("%d\n",sub1(n));
}
sub1(int n)
{ int i,a=0;
for (i=n; i>0; i--)
a+=sub2(i);
return a ;
}
sub2(int n)
{
return n+1;
}程序输出结果:9
2005年 3月
327.4.2 函数的递归调用
1.递归的基本概念递归调用,一个函数直接或间接地调用了它本身
,就称为函数的递归调用。
递归函数,在函数体内调用该函数本身。
int sub(int x)
{ int y,z;
……
if( …… ) z= sub(y);
else { …… }
return ;
}
例如:
直接调用 sub
函数本身
2005年 3月
332.递归函数的执行过程
【 例 7.16】 编一递归函数求 n!。
思路,以求 4的阶乘为例,
4!=4*3!,3!=3*2!,2!=2*1!,1!=1,0!=1。
递归结束条件,当 n=1或 n=0时,n!=1。
递归公式:
n! = 1 ( n=0,1)n× (n-1)! ( n>1)
2005年 3月
34程序如下:
float fact (int n)
{ float f=0;
if(n<0)
printf("n<0,error!");
else if (n==0 || n==1)
f=1;
else f=fact(n-1)*n;
return (f);
}
main( )
{ int n; float y;
printf("\nInput n:");
scanf("%d",&n);
y=fact(n);
printf("%d!=%-10.0f\n",n,y);
}
运行情况如下:
Input a integer number:4?
4!=24
2005年 3月
35递归调用过程回 推
main( ) fact(4) fact(3) fact(2) fact(1)
{ { { { {
… … … … …
y=fact(4); f=4*fact(3); f=3*fact(2); f=2*fact(1); f=1;
… … … … …
return 24 return 6 return 2 return 1
} } } } }
递 推
2005年 3月
363.编制递归函数的方法
⑴ 数值型问题递归函数的编程方法对于数值型问题,首先要找出解题的数学公式,
这个公式必须是 递归定义 的,且所处理的 对象要有规律地递增或递减,然后确定 递归结束条件 。
【 例 7.17】 编一递归函数求 xn 。
思路:首先把 xn转化成递归定义的公式
xn = 1 ( n=0)x × xn - 1 ( n>0)
再找出递归结束条件:当 n=0时,xn=1。
2005年 3月
37程序如下:
long xn(int x,int n)
{ long f=0;
if (n<0) printf("n<0,data error!\n");
else if (n==0) f=1;
else f=x*xn(x,n-1);
return (f);
}
main( )
{ int n,x; long y;
scanf("%d,%d",&x,&n);
y=xn(x,n);
printf("%ld\n",y);
}
程序运行情况如下:
2,10?
1024
2005年 3月
38⑵ 非数值型问题递归函数的编程方法有些问题不能直接用数学公式求解。非数值型问题比数值型问题更难找出递归的算法。它不能用一个递归公式表示。解决这类问题首先要把问题将大化小,将繁化简。将一个复杂的问题化解成若干个相对简单的小问题,而某个小问题的解法与原问题解法相同,并且越来越简单直至有确定的解。
【 例 7.18】 编制一递归函数,将一个十进制正整数(如,15613)转换成八进制数形式输出。
2005年 3月
39
思路:十进制整数转换成八进制整数的方法是 除 8
逆向取余 。如图 7.5所示。
余数,商:
15613%8=5 15613/8=1951
1951%8=7 1951/8=243
243%8=3 243/8=30
30%8=6 30/8=3
3%8=3 3/8=0
结果,36375
图 7.5 十进制转换成八进制
⑵ 非数值型问题递归函数的编程方法 (续 )
2005年 3月
40
该题实际上是要把一个十进制数除以 8得到的余数逆向输出。就是先得到的余数后输出,最后得到的余数最先输出。
我们先由大化小:求八进制数变成求一系列余数的问题。求第一个余数是将 15613除以 8取余,
因为先得到的余数后输出,所以把这个余数存在一个变量 m中,接下去求下一个余数。和求第一个余数的方法相同,只是被除数变成了 15613
除以 8的整数商 1951。因此,这是一个递归调用的问题。定义变量 m存放余数,x存放被除数。
递归算法描述如下:
⑵ 非数值型问题递归函数的编程方法 (续 )
2005年 3月
41
① 先求出余数 m,m=x%8;
② 求 x除以 8取余后的整数商,x=x/8;
③ 如果 x不等于 0,递归调用该函数,否则执行④。
④ 输出余数 m。
⑤ 返回调用点。
⑵ 非数值型问题递归函数的编程方法 (续 )
2005年 3月
42程序如下:
#include "stdio.h"
void dtoo(int x)
{ int m;
m=x%8;
x=x/8;
if (x!=0) dtoo(x);
printf("%d",m);
}
main( )
{ int n;
scanf("%d",&n);
printf("%d=(",n);
dtoo(n);
printf(")8\n");
}
程序运行情况如下:
15613?
15613=(36375)8
2005年 3月
437.5 局部变量和全局变量及其作用域
7.5.1 变量的作用域
7.5.2 局部变量及其作用域变量的作用域,变量在程序中可以被使用的范围。
根据变量的作用域可以将变量分为 局部变量和全局变量。
局部变量( 内部变量 ),在 函数内 或 复合语句内 定义的变量以及 形参 。
作用域,函数内或复合语句内。
【 例 7.19】 分析下面程序的运行结果及变量的作用域。
问题,一个变量在程序的哪个函数中都能使用吗?
2005年 3月
44void sub(int a,int b)
{ int c;
a=a+b; b=b+a; c=b-a;
printf("sub:\ta=%d b= %d c= %d\n",a,b,c); }
局部变量
main( )
{ int a=1,b=1,c=1;
printf("main:\ta=%d b= %d c= %d\n",a,b,c);
sub(a,b);
printf("main:\ta=%d b= %d c= %d\n",a,b,c);
{ int a=2,b=2;
printf("comp:\ta=%d b= %d c= %d\n",a,b,c); }
printf("main:\ta=%d b= %d c= %d\n",a,b,c); }
局部变量局部变量

分程序



程序块

程序输出结果:
main,a=1 b= 1 c= 1
sub,a=2 b= 3 c= 1
main,a=1 b= 1 c= 1
comp,a=2 b= 2 c= 1
main,a=1 b= 1 c= 1
2005年 3月
457.5.3 全局变量及其作用域全局变量 ( 外部变量 ):在 函数外部 定义的变量。
作用域,从定义变量的位置开始到本源文件结束 。如在其作用域内的函数或分程序中定义了同名局部变量,则在局部变量的作用域内,同名全局变量暂时不起作用。
【 例 7.20】 全局变量和局部变量的作用域。
2005年 3月
46int a = 5;
void f(int x,int y)
{ int b,c;
b=a+x;
c=a-y;
printf("%d\t%d\t%d\n",a,b,c);
}
局部变量
main( )
{ int b=6,c=7;
f(b,c);
printf("%d\t%d\t%d\n",a,b,c);
{ int a=9,b=8;
printf("%d\t%d\t%d\n",a,b,c);
{ c=10;
printf("%d\t%d\t%d\n",a,b,c);
}
printf("%d\t%d\t%d\n",a,b,c);
}
printf("%d\t%d\t%d\n",a,b,c);
}
局部变量局部变量程序输出结果:
5 11 -2
5 6 7
9 8 7
9 8 10
9 8 10
5 6 10
全局变量
2005年 3月
477.6 变量的存储类别及变量的生存期
7.6.1 变量的生存期与变量的存储分类变量的生存期,变量在内存中占据存储空间的时间。
思考,1,何时 为变量分配内存单元?
2,将变量分配在内存的什么 区域?
3,变量占据内存的 时间 ( 生存期 )?
程序代码区静态存储区动态存储区存储分配动态存储变量静态存储变量
2005年 3月
487.6.2 变量的存储类别变量的属性数据类型:
决定为变量分配内存单元的长度,
数据的存放形式,
数的范围。
存储类别:
决定了变量的生存期,
给它分配在哪个存储区。
2005年 3月
49变量定义语句的一般形式存储类别 数据类型 变量名 1,…,变量名 n ;
auto(自动的) register(寄存器的)
static(静态的) extern(外部的)
1.自动变量( auto类别)
局部变量可以定义为自动变量。
main()
{int x,y;

}
main()
{auto int x,y;

}
自动变量等价可省
2005年 3月
50
⑴ 内存分配调用函数或执行分程序时在 动态存储区 为其分配存储单元,函数或分程序执行结束,所占内存空间即刻释放。
⑵ 变量的初值定义变量时若没赋初值,变量的 初值不确定 ;如果赋初值则每次函数被调用时执行一次赋值操作。
⑶ 生存期在函数或分程序执行期间。
⑷ 作用域自动变量所在的函数内或分程序内。
自动变量
2005年 3月
512.静态变量( static类别)
除形参外,局部变量和全局变量都可以定义为静态变量。
局部静态变量(或称内部静态变量)
全局静态变量(或称外部静态变量)
静态变量
static int a;
main( )
{ float x,y;
… }
f( )
{ static int b=1;
……
}
全局静态变量局部静态变量自动变量不能省
2005年 3月
52
⑴ 内存分配编译时,将其分配在内存的静态存储区中,程序运行结束释放该单元。
⑵ 静态变量的初值若定义时未赋初值,在编译时,系统自动赋初值为
0;若定义时赋初值,则仅在编译时赋初值一次,
程序运行后不再给变量赋初值 。
⑶ 生存期整个程序的执行期间。
⑷ 作用域局部静态变量的作用域是它所在的函数或分程序。
全局静态变量的作用域是从定义处开始到本源文件结束。
静态变量
2005年 3月
53
int c;
static int a;
main( )
{ float x,y;
… }
char s;
f( )
{ static int b=1;
……
}
3.外部变量( extern类别)
在函数外定义的变量若没有用 static说明,则是外部变量。外部变量只能隐式定义为 extern类别,不能显式定义。
全局静态变量自动变量局部静态变量外部变量
2005年 3月
54
⑴ 内存分配编译时,将其分配在静态存储区,程序运行结束释放该单元。
⑵ 变量的初值若定义变量时未赋初值,在编译时,系统自动赋初值为 0。
⑶ 生存期整个程序的执行期间。
⑷ 作用域从定义处开始到本源文件结束。
此外,还可以用 extern进行声明,以使其作用域扩大到该程序的其它文件中。
外部变量问题:
全局静态变量的作用域可以扩展到本程序的其它文件吗?
2005年 3月
55外部变量声明的一般格式
extern 数据类型 变量名 1,…,变量名 n;

extern 变量名 1,…,变量名 n;
注意:
①外部变量声明用关键字 extern,而外部变量的定义不能用 extern,只能隐式定义。
②定义外部变量时,系统要给变量分配存储空间,
而对外部变量声明时,系统不分配存储空间,
只是让编译系统知道该变量是一个已经定义过的外部变量,与函数声明的作用类似。
2005年 3月
56
int p=1,q=5;
float f1(int a)
{ extern char c1,c2;
……
}
char c1,c2;
char f2(int x,int y)
{ ……
}
main( )
{ ……
}
外部变量声明定义外部变量定义外部变量思考,在 f1函数中声明 c1、
c2的作用是什么?如何修改程序使所有函数都可以使用外部变量而又不需要声明?
【 例 7.24】 在一个文件内声明外部变量。
2005年 3月
57【 例 7.25】 在多文件的程序中声明外部变量。
file1.c文件中程序如下:
int i;
main( )
{ void f1( ),f2( ),f3( );
i=1;
f1( );
printf("\tmain,i=%d",i);
f2( );
printf("\tmain,i=%d",i);
f3( );
printf("\tmain,i=%d\n",i);
}
void f1( )
{ i++;
printf("\nf1,i=%d",i);
}
file2.c文件中程序如下:
extern int i;
void f2( )
{ int i=3;
printf("\nf2,i=%d",i);
}
void f3( )
{ i=3;
printf("\nf3,i=%d",i);
}
程序输出结果:
f1,i=2 main,i=2
f2,i=3 main,i=2
f3,i=3 main,i=3
声明外部变量定义外部变量
2005年 3月
584.寄存器变量( register类别)
只有 函数内定义的变量或形参 可以定义为寄存器变量。寄存器变量的值保存在 CPU的寄存器中。
受寄存器长度的限制,寄存器变量只能是 char、
int和指针类型的变量。
【 例 7.26】 寄存器变量的使用。
main( )
{ long int sum=0;
register int i;
for (i=1; i<=1000; i++)
sum+=i;
printf("sum=%ld\n ",sum);
}
程序输出结果:
sum=500500
2005年 3月
597.6.3 归纳变量的分类
1.按照变量的作用域对变量分类
⑴ 局部变量
⑵ 全局变量
2.按照变量的生存期对变量分类
⑴ 静态存储变量包括:局部静态变量和全局静态变量
⑵ 动态存储变量包括:自动变量
2005年 3月
607.7 函数的存储分类外部函数:
extern int fan(char a,char b)
{ …… }
静态函数:
static int func( )
{ …… }
外部函数 和 静态函数 区别:
外部函数允许本程序其他文件中的函数调用
(与外部变量类似 )。
静态函数 禁止 本程序其他文件中的函数调用
(与外部静态变量类似 )。
extern可以省略
2005年 3月
61
源文件
*.c
运行文件
*.exe编译 编译 编译目标文件
*.obj 连接编译 连接连接编译预处理 编 译编译预处理包括:
宏定义文件包含条件编译
7.8 编译预处理
2005年 3月
627.8 编译预处理(续)
源文件
*.c
运行文件
*.exe
目标文件
*.obj编译 连接
main()
{ float r,s,c;
scanf(“%f”,&r);
s = r * r * 3.14;
c = 2 * r * 3.14;
printf(“s=%f,c=%f”,s,c);
}
如何修改圆周率
2005年 3月
63
#define 宏名 字符串宏定义的功能:
在进行编译前,用 字符串 原样替换程序中的宏名 。
这个替换过程称为,宏替换,或,宏展开,,
字符串 也称为替换文本。
命令的一般格式:
7.8.1 不带参数的宏定义
7.8 编译预处理(续)
2005年 3月
647.8 编译预处理(续)
例如,#define PI 3.14
main()
{ float r,s,c;
scanf(“%f”,&r);
s = r * r * PI;
c = 2 * r * PI;
printf(“s=%f,c=%f”,s,c);
}
替换
3.14
3.14
编 译
2005年 3月
657.8 编译预处理(续)
② 为了增加程序的可读性,建议宏名用大写字母,其他的标识符用小写字母。
③双引号中有与宏名相同的字符串不进行替换。
④已经定义的宏名可以被后定义的宏名引用。在预处理时将层层进行替换。
说明,
① 宏定义的作用域是从定义处开始到源文件结束,
但根据需要可用 undef命令终止其作用域。形式为,
#undef 宏名
2005年 3月
66【 例 7.29】 不带参数的宏定义。
源程序:
#define PI 3.14
#define S PI*r*r
#define V 4*S*r/3
main( )
{float r;
printf("\nInput r:");
scanf("%f",&r);
printf("S=%.2f V=%.2f\n",S,V);
}
编译预处理后的程序:
main( )
{ float r;
printf("\nInput r:");
scanf("%f",&r);
printf("S=%.2f V=%.2f\n",
3.14*r*r,4* 3.14*r*r *r/3);
}
S V
进入编译
2005年 3月
67
命令的一般形式
7.8.2 带参数的宏定义
#define 宏名 (形参表 ) 字符串功能,
在编译预处理时,把源程序中所有 带参数的宏名用宏定义中的 字符串 替换,并且用宏名后圆括号中的 实参替换 字符串中的 形参 。
例如,#define MAX(X,Y) ((X)>(Y)?(X):(Y))
7.8 编译预处理(续)
2005年 3月
687.8 编译预处理(续)
【 例 7.30】 带参数的宏定义。
#define MAX(x,y) ((x)>(y)?(x):(y))
main( )
{ …
printf("%d\n",a,b,MAX(a,b));
printf("%d\n",MAX(a+m,b+n));
}
分两次替换:
①将宏名 MAX(a,b)替换成字符串 ((x)>(y)?(x):(y))。
②用实参 a替换形参 x,实参 b替换形参 y。
程序中的两个 printf语句被展开为:
printf("%d\n",((a)>(b)?(a):(b)));
printf("%d\n",((a+m)>(b+n)?( a+m):( b+n)));
2005年 3月
697.8 编译预处理(续)
【 例 7.31】 分析下面程序运行后的输出结果。
#define MA(x) x*(x-1)
main( )
{ int a=1,b=2;
printf("%d\n",MA(1+a+b));
}
分两次替换:
① MA(1+a+b) 用 x*(x-1) 替换。
②用 1+a+b替换 x。
printf语句被展开为:
printf("%d\n",1+a+b*(1+a+b-1));
特别注意:
由于替换文本中的 x没有用括号括起,因此,
1+a+b也不能用括号括起。
程序输出结果,8
2005年 3月
707.8 编译预处理(续)
1.命令的一般形式格式 1,#include <文件名 >
格式 2,#include "文件名 "
7.8.3 文件包含文件包含是将指定的某个源文件的内容全部包含到当前文件中。用 include命令实现。
用 格式 1,预处理程序仅在 \TC\INCLUDE目录下查找指定文件。
用 格式 2,预处理程序首先在 当前目录中 查找指定文件,若找不到再到 \TC\INCLUDE目录中查找。
2005年 3月
717.8 编译预处理(续)
例如:调用 sin(x) 函数时,要在程序的开头使用如下命令:
#include <math.h>
在预处理时,用 math.h文件内容替换
#include <math.h>命令行。
2.功能在预处理时,将 include命令后指定文件的内容替换该命令行。
2005年 3月
727.9 多文件程序的调试方法操作步骤如下:
① 在 TC 环境下建立一个工程文件( 扩展名为 prj),
设文件名为 test.prj,文件内容如下:
file1.c
file2.c
fiile3.c
若这3个文件不在当前盘或当前路径下,需要在文件名前加盘符和路径。如果已经将源文件编译成目标文件,可在工程文件中直接写目标文件名。
1,使用工程文件将多个文件连接成一个可执行文件的方法例如:将 file1.c,file2.c和 file3.c合并成一个可执行文件 。
2005年 3月
737.9 多文件程序的调试方法(续)
② 在 TC主菜单的,Project”下拉菜单中选择
,Project name”项,输入文件名 test.prj(文件名前也可以加盘符和路径)。
③ 在 TC环境下,按 Ctrl+F9,系统首先找到工程文件 test.prj,然后将 file1.c,file2.c和 file3.c分别进行编译,若编译没通过,根据 出错信息 检查、
修改源程序,然后再按 Ctrl+F9重新编译。若编译通过,系统自动生成三个对应的目标文件:
file1.obj,file2.obj 和 fiile3.obj。接着系统自动进行 连接,连接 成功则生成 test.exe并自动执行该程序。
2005年 3月
747.9 多文件程序的调试方法(续)
2,使用文件包含命令将多个文件连接成一个可执行文件的方法以 例 7.27为例,分析文件包含后源程序的情况。
2005年 3月
757.9 多文件程序的调试方法(续)
file3.c
#include "file2.c"
#include "file3.c"
file1.c
A
file2.c
B
C
B
C
A
file1.c
预处理前 预处理后
2005年 3月
76