2009-7-31
1
第 8章 函数与预处理命令西亚斯国际学院 自动化
C 语言程序设计
2009-7-31
2第 8章 函数与预处理命令
8.1 概述
8.2 函数的定义与调用
8.3 数组作函数参数
8.4 函数的嵌套调用和递归调用
8.5 局部变量和全局变量及其作用域
8.6 变量的存储类别及变量的生存期
8.7 函数的存储分类
8.8 编译预处理
8.9 多文件程序的调试方法
2009-7-31
38.1 概述
① 程序结构清晰,可读性好。
② 减少重复编码的工作量。
③ 可多人共同编制一个大程序,缩短程序设计周期,提高程序设计和调试的效率。
使用函数的好处
… …
C程序源程序文件 n
函数 1 函数 m
… …源程序文件 1
函数 1 函数 n
2009-7-31
4【 例 8.1】 求一个整数的立方。
int cube (int x) /* 函数定义 */
{ return (x * x * x); }
#include<stdio.h>
void 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函数开始
2009-7-31
5
⑴ 一个 C源程序可以由一个或多个源程序文件组成。 C编译系统在对 C源程序进行编译时是以文件为单位进行的。
⑵ 一个 C源程序文件可以由一个或多个函数组成。
所有函数都是独立的。主函数可以调用其它函数,
其它函数可以相互调用。
⑶ 在一个 C程序中,有且仅有一个主函数 main。
C程序的执行总是从 main函数开始,调用其它函数后最终回到 main函数,在 main函数中结束整个程序的运行。
说明
2009-7-31
6⑷ 函数的种类从函数定义形式分:
① 有参函数:
在主调(用)函数和被调
(用)函数之间通过参数进行数据传递,如:
int cube (int x) { … }
② 无参函数:
如,getchar( )
在调用无参函数时,主调函数不需要将数据传递给无参函数。
从使用的角度看:
① 标准函数(库函数)
库函数是由系统提供的。
如,getchar( ),sin(x)等。
在程序中可以直接调用它们。附录 A列出了 C的部分库函数。
② 用户自定义函数。
如,例 8.1中的 cube函数。
2009-7-31
78.2.1 函数的定义函数定义的一般形式函数类型 函数名 (类型名 形式参数 1,… )
{ 说明语句执行语句
}
例如:求两个数的最大值。
int max(int x,int y)
{ int z;
z = x > y? x,y;
return( z );
}
类型省略时默认为 int
类型 没有形式参数为 无参函数
2009-7-31
8
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 );
}
花括号中也可以为空,这种函数叫 空函数 。
不能在函数体内定义其他函数,即函数 不能嵌套定义 。
形参也可以这样定义 如下定义都是错误的

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

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

注意:在 TC中,实参的求值顺序是从右到左。
2009-7-31
13【 例 8.5】 计算并输出圆的面积。
s(int r)
{ return 3.14*r*r;}
#include<stdio.h>
void main( )
{ int r,area;
scanf("%d",&r);
area=s(r);
printf("%d\n",area);
}
自动转换为 int型思考:
若要得到单精度实型的圆面积,程序应如何修改程序运行情况如下:
2?
12
2009-7-31
148.2.4 对被调函数的声明和函数原型变量要 先定义后使用,
函数也如此 。即 被调函数的定义要出现在主调函数的定义之前 。如 swap函数,
允许整型函数(且参数也是整型)的定义出现在主调函数之后。如 max函数,
如果非整型函数在主调函数之后定义,则应在主调函数中或主调函数之前对 被调函数进行声明。
void swap(int x,int y)
{ …}
main( )
{ …
swap(a,b);
}
main( )
{ …
c=max(a,b);
}
max(int x,int y)
{ …}
2009-7-31
15
图 8.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;
【 例 8.6】 哥德巴赫猜想之一是任何一个大于 5的偶数都可以表示为两个素数之和。验证这一论断。
2009-7-31
16
#include "math.h"
int prime(int n);
#include<stdio.h>
void 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
函数进行声明。实际上,该声明可以省略,为什么?
程序如下:
2009-7-31
178.3.2 一维数组名作函数参数
数组名 表示数组在内存中的 起始地址 。
例如,数组 a在内存中从 2000地址开始存放,则 a的值为 2000。 2000是地址值,是指针类型的数据(第 8
中将介绍指针类型),不能把它看成是整型或其他类型数据。
实参是数组名,形参也应定义为 数组形式,形参数组的长度可以省略,但 [ ]不能省,否则就不是数组形式了。
【 例 8.7】 用冒泡法将 10个整数排序。
2009-7-31
18void sort(int b[ ],int n);
void printarr(int b[ ]);
#include<stdio.h>
void main( )
{ int a[10] =
{11,22,63,98,58,80,45,
32,83,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; }
}
2009-7-31
19
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
首地址:
2009-7-31
20
以二维数组为例。二维数组名作实参时,对应的形参也应该定义为一个二维数组形式。对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。
【 例 8.7】 编程序,将矩阵转置。设转置前为 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],… 。
8.3.3 多维数组作函数参数
2009-7-31
218.4.2 函数的递归调用不要求 !
2009-7-31
228.5.3 全局变量及其作用域全局变量 ( 外部变量 ):在 函数外部 定义的变量。
作用域,从定义变量的位置开始到本源文件结束 。如在其作用域内的函数或分程序中定义了同名局部变量,则在局部变量的作用域内,同名全局变量暂时不起作用。
【 例 8.8】 全局变量和局部变量的作用域。
2009-7-31
238.6.2 变量的存储类别变量的属性数据类型:
决定为变量分配内存单元的长度,
数据的存放形式,
数的范围。
存储类别:
决定了变量的生存期,
给它分配在哪个存储区。
2009-7-31
24变量定义语句的一般形式存储类别 数据类型 变量名 1,…,变量名 n ;
auto(自动的) register(寄存器的)
static(静态的) extern(外部的)
1.自动变量( auto类别)
局部变量可以定义为自动变量。
void void
main()
{int x,y;

}
void void
main()
{auto int x,y;

}
自动变量等价可省
2009-7-31
25
void main()
{ int a,b,c;
printf(“Enter a,b:\n”);
scanf(“%d%d”,&a,&b);
c=sum(a,b);
printf(“Sum=%d\n”,c);
}
sum(int a,int b)
{ int c=0;
c=a+b;
return(c);
}
rintf( ter,b:\,);
sc f( );
c=su (a,b);
printf(“Su =%d\n”,c);
void ain()
Enter a,b:
内存用户区静态存储区
am 不定
bm 不定
cm 不定
1 2 <回车 >
as=1
bs=2
cs=0
bm=2
am=1
s (int a,int b)
Sum=3
程 序程序区动态存储区观察下列程序运行时变量的存储情况单击开始运行
bs=2
cs=3
运行结束
cm =3
2009-7-31
26
⑴ 内存分配调用函数或执行分程序时在 动态存储区 为其分配存储单元,函数或分程序执行结束,所占内存空间即刻释放。
⑵ 变量的初值定义变量时若没赋初值,变量的 初值不确定 ;如果赋初值则每次函数被调用时执行一次赋值操作。
⑶ 生存期在函数或分程序执行期间。
⑷ 作用域自动变量所在的函数内或分程序内。
自动变量
2009-7-31
272.静态变量( static类别)
除形参外,局部变量和全局变量都可以定义为静态变量。
局部静态变量(或称内部静态变量)
全局静态变量(或称外部静态变量)
静态变量
static int a;
main( )
{ float x,y;
… }
f( )
{ static int b=1;
……
}
全局静态变量局部静态变量自动变量不能省
2009-7-31
28
static int b=0;
main( )
{ int a=2,i;
for (i=0; i<2; i++)
printf ("%4d",f(a));
printf("\n");
}
int f(int a)
{ static int c=3;
b++; c++;
return (a+b+c);
}
内存用户区静态存储区程序区动态存储区
【 例 8.9】 静态变量的使用。
b=0
c=3
程 序
am=2
i不定
for (i=0; i<2; i+ )
i=0
printf (" 4d",f(a));
af =2
4
c
1
( 7
1
2
5
9
2
intf( \ );
单击开始运行运行结束
2009-7-31
29
⑴ 内存分配编译时,将其分配在内存的静态存储区中,程序运行结束释放该单元。
⑵ 静态变量的初值若定义时未赋初值,在编译时,系统自动赋初值为
0;若定义时赋初值,则仅在编译时赋初值一次,
程序运行后不再给变量赋初值 。
⑶ 生存期整个程序的执行期间。
⑷ 作用域局部静态变量的作用域是它所在的函数或分程序。
全局静态变量的作用域是从定义处开始到本源文件结束。
静态变量
2009-7-31
30
int c;
static int a;
main( )
{ float x,y;
… }
char s;
f( )
{ static int b=1;
……
}
3.外部变量( extern类别)
在函数外定义的变量若没有用 static说明,则是外部变量。外部变量只能隐式定义为 extern类别,不能显式定义。
全局静态变量自动变量局部静态变量外部变量
2009-7-31
31
⑴ 内存分配编译时,将其分配在静态存储区,程序运行结束释放该单元。
⑵ 变量的初值若定义变量时未赋初值,在编译时,系统自动赋初值为 0。
⑶ 生存期整个程序的执行期间。
⑷ 作用域从定义处开始到本源文件结束。
此外,还可以用 extern进行声明,以使其作用域扩大到该程序的其它文件中。
外部变量问题:
全局静态变量的作用域可以扩展到本程序的其它文件吗?
2009-7-31
32外部变量声明的一般格式
extern 数据类型 变量名 1,…,变量名 n;

extern 变量名 1,…,变量名 n;
注意:
①外部变量声明用关键字 extern,而外部变量的定义不能用 extern,只能隐式定义。
②定义外部变量时,系统要给变量分配存储空间,
而对外部变量声明时,系统不分配存储空间,
只是让编译系统知道该变量是一个已经定义过的外部变量,与函数声明的作用类似。
2009-7-31
334.寄存器变量( register类别)
只有 函数内定义的变量或形参 可以定义为寄存器变量。寄存器变量的值保存在 CPU的寄存器中。
受寄存器长度的限制,寄存器变量只能是 char、
int和指针类型的变量。
【 例 8.10】 寄存器变量的使用。
#include<stdio.h>
void main( )
{ long int sum=0;
register int i;
for (i=1; i<=1000; i++)
sum+=i;
printf("sum=%ld\n ",sum);
}
程序输出结果:
sum=500500
2009-7-31
348.6.3 归纳变量的分类
1.按照变量的作用域对变量分类
⑴ 局部变量
⑵ 全局变量
2.按照变量的生存期对变量分类
⑴ 静态存储变量包括:局部静态变量和全局静态变量
⑵ 动态存储变量包括:自动变量
2009-7-31
358.8 函数的存储分类 (自学 )
外部函数:
extern int fan(char a,char b)
{ …… }
静态函数:
static int func( )
{ …… }
外部函数 和 静态函数 区别:
外部函数允许本程序其他文件中的函数调用
(与外部变量类似 )。
静态函数 禁止 本程序其他文件中的函数调用
(与外部静态变量类似 )。
extern可以省略
2009-7-31
36
源文件
*.c
运行文件
*.exe编译 编译 编译目标文件
*.obj 连接编译 连接连接编译预处理 编 译编译预处理包括:
宏定义文件包含条件编译
8.8 编译预处理自学不要求
2009-7-31
378.9 多文件程序的调试方法不要求
2009-7-31
38