1
第八章 函 数本章内容:
1.函数的定义形式
2.函数的参数和函数的值
3.函数调用、函数的嵌套调用以及函数的递归调用
4.数组作为函数参数
5.变量的作用域
6.变量的存储类别
7.内部函数和外部函数
2
C源程序是由函数组成的 。 虽然在前面的程序中都只有一个主函数 main(),但实用程序往往由多个函数组成 。
函数是C源程序的基本模块,通过对函数模块的调用实现特定的功能 。 可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言 。
C语言不仅提供了极为丰富的库函数 (如 Turbo C提供了三百多个库函数 ),还允许用户建立自己定义的函数 。
用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数 。
概 述
3
函数的分类
( 1) 库函数由C系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用 。
在前面各章的例题中反复用到 printf,scanf,getchar,
putchar,gets,puts,strcat等函数均属此类 。
( 2) 用户定义函数由用户按需要写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
1,从函数定义的角度看,函数可分为库函数和用户定义函数
4
(1) 有返回值函数此类函数被调用执行完后将向调用者返回一个执行结果,
称为函数返回值 。 如数学函数即属于此类函数 。
由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型 。
(2) 无返回值函数此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值 。 这类函数类似于其它语言的过程 。
由于函数无须返回值,用户在定义此类函数时可指定它的返回为,空类型,,空类型的说明符为,void”。
2,C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数
5
(1) 无参函数函数定义,函数说明及函数调用中均不带参数 。
主调函数和被调函数之间不进行参数传送 。
此类函数通常用来完成一组指定的功能,可以返回或不返回函数值 。
(2) 有参函数 (带参函数 )
在函数定义及说明时都有参数,称为形式参数 (形参 )。
在函数调用时也必须给出参数,称为实际参数 (实参 )。
进行函数调用时,主调函数将把实参的值传送给形参,
供被调函数使用 。
3,从主调函数和被调函数之间数据传送的角度看可分为无参函数和有参函数两种
6
1,在C语言中所有的函数定义,包括主函数 main在内,都是平行的 。 也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义 。
2,函数之间允许相互调用,也允许嵌套调用 。 把调用者称为主调函数 。 函数还可以自己调用自己,称为递归调用 。
3,一个C源程序必须有,也只能有一个主函数 main。
4,main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用 。
5,C程序的执行总是从 main函数开始,完成对其它函数的调用后再返回到 main函数,最后由 main函数结束整个程序 。
说 明:
7
函数定义的一般形式类型说明符指明了本函数的类型,函数的类型实际上是函数返回值的类型 。
函数名是由用户定义的标识符,函数名后有一个空括号,
其中无参数,但括号不可少 。
{ } 中的内容称为函数体 。
函数头函数体
1.无参函数的一般形式类型说明符 函数名 ( )
{ 类型说明语句
}
8
例如:定义一个函数:
void Hello()
{ printf ("Hello,C System \n");
}
这里,Hello作为函数名,本函数是一个无参函数,当被其它函数调用时,输出 Hello,C System字符串 。
在函数体中的类型说明,是对函数体内部所用到的变量的类型说明。
很多情况下都不要求无参函数有返回值,此时函数类型符可以写为 void。
说 明:
9
有参函数比无参函数多了两个内容,其一是形式参数表,
其二是形式参数类型说明 。
在形参表中给出的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号间隔 。
在进行函数调用时,主调函数将赋予这些形式参数实际的值 。 形参既然是变量,当然必须给以类型说明 。
2.有参函数的一般形式类型说明符 函数名 (形式参数表列 )
{ 类型说明语句
}
10
例如,定义一个函数,用于求两个数中的大数,可写为:
int max(int a,int b)
{ int z;
if (a>b) z=a;
else z=b;
return ( z );
}
现代格式
int max(a,b)
int a,b;
{ int z;
if (a>b) z=a;
else z=b;
return ( z );
}
传统格式
11
1,形参出现在函数定义中,在函数体内都可以使用,函数未被调用时,系统不给它分配存储空间,当函数被调用时系统才给分配其储存空间,用来接受从主调函数传递过来的数据,当该函数调用结束后,则立即释放其储存空间。
2,实参出现在主调函数中,即调用时写入函数括号中的参数,
调用时将实参数据传递给对应的形参,进入被调函数后,实参变量不能使用。
3,形参与实参的的关系:形参和实参的功能是作数据传递。发生函数调用时(形、实结合),主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。
函数的参数和函数的值函数的参数
12
函数的形参和实参具有以下特点,
1,形参变量只有在被调用时才分配内存单元,在调用结束时,
即刻释放所分配的内存单元 。
因此,形参只有在函数内部有效 。 函数调用结束返回主调函数后则不能再使用该形参变量 。
例:
main()
{ int x,y,z;
scanf("%d%d",&x,&y);
z=max(x,y);
printf("max=%d\n",z);
}
int max(int a,int b)
{ int d;
d=a>b?a:b;
return (d);
}
13
2.实参可以是常量,变量,表达式,函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参 。 因此应预先用赋值,输入等办法使实参获得确定值 。
3,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生,类型不匹配,的错误 。
4,函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。
因此 在函数调用过程中,形参的值发生改变,而实参中的值不会变化 。
函数的形参和实参具有以下特点,
14
void main()
{ int s(int); int n;
printf("input number\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n);
}
int s(int n)
{ int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
return 0;
}
如果输入 10
输出结果是:
例:分析下面程序的输出结果:
15
函数的返回值是指函数被调用之后,执行函数体中的程序段所得的并返回给主调函数的值 。
C语言函数的值只能通过 return语句返回函数的值 。
函数的返回值
1,return语句返回函数的值
return 语句使用的一 般形式:
return 表达式;
或者为,return (表达式 );
16
2,函数值的类型和函数定义中函数的类型应保持一致 。 如果两者不一致,则以函数类型为准,自动进行类型转换 。
3,如函数值为整型,在函数定义时可以省去类型说明。
4,不返回函数值的函数,可以明确定义为,空类型,,类型说明符为,void”。
return该语句的功能是计算表达式的值,并返回给主调函数。 在函数中允许有多个 return语句,但每次调用只能有一个 return 语句被执行,因此只能返回一个函数值。
17
因此可定义为:
void s(int n)
{ ……
}
如上例中函数 s并不向主函数返函数值一旦函数被定义为空类型后,就不能在主调函数中使用被调函数的函数值了。
例如,在定义 s为空类型后,在主函数中写下述语句
sum=s(n); 就是错误的。
为了使程序有良好的可读性并减少出错,凡不要求返回值的函数都应定义为空类型。
int s(int n)
{ int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
return 0;
}
18
函数的调用函数调用的一般形式函数名 (实际参数表 )
对无参函数调用时则无实际参数表 。
实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式 。
各个实际参数之间用逗号分隔 。
19
1,函数表达式函数作表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。
例如,z=max(x,y) 是一个赋值表达式,把 max函数的返回值赋予变量 z。
调用函数的方法:
2,函数语句函数调用的一般形式加上分号即构成函数语句,即:
函数名 (实际参数表 );
例如,printf ("%d",a); scanf ("%d",&b);都是以函数语句的方式调用函数。
20
3.函数实参函数作为另一个函数调用的实际参数出现 。
这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的 。
例如,printf("%d",max(x,y)); 即是把 max调用的返回值又作为 printf函数的实参来使用的。
调用函数的方法:
21
1.被调用的函数必须是已经存在的函数 。
2.对库函数的调用,必须把该函数的头文件用 #include命令包含在源文件前部 。
#include " 头文件名 " 或,#include < 头文件名 >
3.对用户自定义函数,如果被调函数的定义在主调函数之后,
则必须在主调函数中的说明部分对该被调函数进行说明,
这与使用变量之前要先进行变量说明是一样的 。
在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理 。
对被调用函数的声明和函数原型函数调用的条件:
22
1,传统格式,其一般格式为:
类型说明符 被调函数名 ( );
这种格式只给出函数返回值类型,被调函数名及一个空括号 。
这种格式由于在括号中没有任何参数信息,因此不便于编译系统进行错误检查,易于发生错误 。
2,现代格式,其一般形式为:
类型说明符 被调函数名 (类型 形参,类型 形参 …);
或为,类型说明符 被调函数名 (类型,类型 …);
现代格式的括号内给出了形参的类型和形参名,或只给出形参类型 。 这便于编译系统检错,以防止可能出现的错误 。
对被调函数的声明(函数原型):
23
例 如:
main()
{ int x,y,z;
int max(int a,int b); //或者 int max(int,int)
scanf("%d%d",&x,&y);
z=max(x,y);
printf("max=%d\n",z);
}
int max(int a,int b)
{ int d;
d=a>b?a:b;
return (d);
}
24
C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。
1,如果被调函数的返回值是整型时,可以不对被调函数作说明,而直接调用。但是实际当中都是加上函数原型的。
2,当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作说明而直接调用。
3,如在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。
说 明:
25
char str(int a);
float fy(float b);
main()
{
……
}
char str(int a)
{
……
}
float fy(float b)
{
……
}
放在最前面说明其中第一,二行对 str函数和 fy函数预先作了说明。 因此在以后各函数中无须对 str和 f函数再作说明就可直接调用。
26
C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。
虽然C语言不允许在一个函数的定义中出现对另一个函数的定义; 但是C语言允许在一个函数的定义中出现对另一个函数的调用。
若被调函数中需要使用另外一个函数的功能时(即被调函数中又需要调用其它函数),即出现了函数的嵌套调用。
函数的嵌套调用
27
main()
{…
fa();
….
fb();
}
fa()
{….
fb();
….
}
fb();
{…
….
}
28
例 8.6 用弦截法方程 f(x) =x3+5x2+16x-80=0的根。
#include "math.h"
float f(float x)
{ float y;
y=((x-5.0)*x+16.0)*x-80;
return (y);
}
float xpoint(float x1,float x2)
{ float y;
y=(x1*f(x2)-x2*f(x1))/(f(x2)-f(x1));
return (y);
}
float root(float x1,float x2)
{ int i;
float x,y,y1;
y1=f(x1);
do
{ x=xpoint(x1,x2);
y=f(x);
if(y*y1>0 )
{y1=y; x1=x;}
else
x2=x;
}while(fabs(y)>=0.00001);
return(x);
}
void main()
{ float x1,x2,f1,f2,x;
do
{ printf("input x1,x2:\n");
scanf("%f,%f",&x1,&x2);
f1=f(x1); f2=f(x2);
} while(f1*f2>=0);
x=root(x1,x2);
printf("root is:%f",x);
}
29
函数在它的函数体内直接或间接调用它自身称为递归调用。
这种函数称为递归函数。
C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数,执行递归函数将反复调用其自身,每调用一次就进入新的一层。
例如有函数 f如下:
函数的递归调用
int f (int x)
{ int y;
z= f(y);
return z;
}
30
例:用递归法计算 n!
阶乘的定义:
31
long fac(int n)
{ long f;
if(n==0||n==1)
f=1;
else
f=fac(n-1)*n;
return(f);
}
main()
{ int n;
long y;
printf("input n=,\n");
scanf("%d",&n);
y=fac(n);
printf("%d!=%ld",n,y);
}
递推回归递推 递推回归回归
32
递归结束条件及结束时的值;
能用递归形式表示,并且递归向终止条件发展。
构成递归的条件:
33
数组可以作为函数的参数使用,进行数据传送。
数组用作函数参数一种是把数组元素作为实参使用;
数组用作函数参数另一种是把数组名作为函数的形参和实参使用。
数组作为函数参数一、数组元素作函数实参数数组元素作为函数的实参与普通变量作实参数并无区别,函数实参的使用与普通变量是完全相同的。
在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。
34
用数组名作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组 ( 或指针变量 ),都必须有明确的数组说明 。
当形参和实参二者不一致时,即会发生错误 。
二、数组名作为函数参数在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。
注 意:
35
因为实际上形参数组并不存在,编译系统不为形参数组分配内存。
C语言中规定,数组名代表数组的首地址。
因此在数组名作函数参数时所进行的传送是 地址的传送,
也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。
实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。
数据的传递成为双向的。
注 意:
36
float aver(float a[10])
{ int i;
float av,s=a[0];
for(i=1;i<10;i++)
s=s+a[i];
av=s/10;
return av;
}
void main()
{ float aver(float a[]);
float sco[10],av;
int i;
printf("input 10 scores:\n");
for(i=0;i<10;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average is,%5.2f",av);
}
例 8.11 数组 a中存放了 10个学生的成绩,求平均成绩。
37
形参数组和实参数组的类型必须一致,否则将引起错误 。
形参数组和实参数组的长度可以不相同,因为在调用时只传送首地址而不检查形参数组的长度 。
当形参数组的长度与实参数组不一致时,虽不至于出现语法错误 (编译能通过 ),但程序执行结果将与实际不符,这是应予以注意的 。
用数组名作为函数参数时还应注意以下几点:
38
float aver(float a[10])
{ int i;
float av,s=a[0];
for(i=1;i<10;i++)
s=s+a[i];
av=s/10;
return av;
}
void main()
{
float aver(float a[]);
float sco[15],av;
int i;
printf("input 15 scores:\n");
for(i=0;i<15;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average is,%5.2f",av);
}
例 如:
39
3,在函数形参表中,可以不给出形参数组的长度,或用一个变量来表示数组元素的个数 。
即:可以写为:
void aver(float a[ ])
或写为
void aver(float a[ ],int n)
其中形参数组 a没有给出长度,而由 n值动态地表示数组的长度 。
n 的值由主调函数的实参进行传送 。
用数组名作为函数参数时还应注意以下几点:
40
float aver(float a[],int n)
{ int i;
float av,s=a[0];
for(i=1;i<n;i++)
s=s+a[i];
av=s/n;
return av;
}
void main()
{
float aver(float a[],int n);
float sco[15],av;
int i;
printf("input scores:\n");
for(i=0;i<15;i++)
scanf("%f",&sco[i]);
av=aver(sco,15);
printf("average is,%5.2f",av);
}
例 如:
41
例 8.13 写一用选择法排序的函数,调用该函数排序。
/*选择法排序函数 */
void sort(int a[ ],int n)
{
int i,j,p;
for(i=0;i<n-1;i++)
{ p=i;
for(j=i+1;j<n;j++)
if(a[p]<a[j]) p=j;
if(i!=p)
{ s=a[i];
a[i]=a[p];
a[p]=s;
}
}
}
/*主函数调用排序函数,*/
main()
{
void sort(int a[],int n) ;
int i,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
sort(a,10);
for(i=0;i<10;i++)
printf("%d ",a[i]);
}
42
变量的有效性范围称变量的作用域。
在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。
这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。
C语言中所有的量都有自己的作用域。
变量说明的方式不同,其作用域也不同。
变量的作用域
43
局部变量也称为内部变量。
局部变量是在函数内作定义说明的。
局部变量的作用域仅限于函数内,离开该函数后再使用这种变量是非法的。
C语言中的变量,按作用域范围可局部变量和全局变量一、局部变量
44
例如,int f1(int a) /*函数 f1*/
{ int b,c;
……
} a,b,c作用域
int f2(int x) /*函数 f2*/
{ int y,z;
} x,y,z作用域
main() /*主函数 main*/
{ int m,n;
} m,n 作用域在函数 f1内定义了三个变量,a为形参,b,c为一般变量。
在 f1的范围内 a,b,c有效,即 a,b,c变量的作用域限于 f1内。
x,y,z的作用域限于 f2内。 m,n的作用域限于 main函数内。
45
主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用 。
主函数中也不能使用其它函数中定义的变量 。 因为主函数也是一个函数,它与其它函数是平行关系 。
形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量 。
允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆 。
关于局部变量作用域的说明:
46
复合语句中也可定义变量,其作用域只在复合语句范围内。
例,main()
{ int x,y;
……
{ int z;
…
}
… …
}
47
全局变量也称为外部变量,它是在函数外部定义的变量 。
全局变量不属于哪一个函数,它属于一个源程序文件 。
全局变量的作用域是整个源程序 。
全局变量的说明符为 extern。
在一个函数之前定义的全局变量,在该函数内使用可不再加以说明 。
二、全局变量
48
例 如:
int a,b; /*全局变量 */
void f1() /*函数 f1*/
{ ……
}
float x,y; /*全局变量 */
int fz() /*函数 fz*/
{ ……
}
main() /*主函数 */
{ ……
} /*全局变量 x,y作用域 全局变量 a,b作用域 */
49
int s1,s2,s3; /*声明全局变量,用来传递数据 */
int vs( int a,int b,int c)
{ int v;
v=a*b*c; s1=a*b; s2=b*c; s3=a*c;
return v; }
void main()
{ int v,l,w,h;
printf("\ninput length,width and height\n");
scanf("%d%d%d",&l,&w,&h);
v=vs(l,w,h);
printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
}
例:输入长方体的长宽高 l,w,h。
求体积及三个面 x*y,x*z,y*z的面积。
50
对于全局变量的说明:
全局变量可以作为函数间数据联系的渠道。
由于C语言规定函数返回值只有一个,当需要增加函数的返回数据时,用外部变量是一种方法。
非必要时不要使用全局变量,因为全局变量在全部程序执行时占用存储单元并且降低了程序的通用性和清晰性。
若在同一个源文件中,全局变量与局部变量同名,则局部变量的作用范围内全局变量被“屏蔽”。
int a,b; /*全局变量 */
main() /*主函数 */
{ int a,b;
……
}
51
int a=5,b=8;
void main()
{ int a=10,z;
int max(int a,int b);
z=max(a,b);
printf("max=%d\n",z);
}
int max(int a,int b)
{ int d;
d=a>b?a:b;
return (d);
}
例 如:
问:程序运行的结果
52
各种变量作用域不同,就本质来说是变量的存储类型不同。
存储类型是指变量占用内存空间的方式,也称存储方式。
变量的存储方式可分为“静态存储”和“动态存储”两种。
静态存储变量通常是在变量定义时就分定存储单元并一直保持不变,直至整个程序结束。
动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放。
变量的存储类型
53
动态存储变量的典型是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时才予以分配,调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、
释放形参变量的存储单元。
静态存储变量则是一直存在的,而动态存储变量则时而存在时而消失。
由于变量存储方式不同而产生的这种特性称变量的生存期。
生存期表示了变量存在的时间。生存期和作用域是从时间和空间两个不同角度来描述变量的特性,既有联系又有区别。
一个变量究竟属于哪一种存储方式,并不能仅从其作用域来判断,还应有明确的存储类型说明。
变量的存储类型
54
自动变量和寄存器变量属于动态存储方式,外部变量和静态变量属于静态存储方式 。
变量的说明不仅应说明其数据类型,还应说明存储类型 。
变量说明的完整形式为:
存储类型说明符 数据类型说明符 变量名,变量名 …;
例如,static int a,b; 说明 a,b为静态类型变量
auto char c1,c2; 说明 c1,c2为自动字符变量 (省略 )
static int a[5]={1,2,3,4,5}; 说明 a为静整型数组
extern int x,y; 说明 x,y为外部整型变量
C语言中的存储类型
auto 自动变量 register 寄存器变量
extern 外部变量 static 静态变量
55
auto存储类型是C语言程序中使用最广泛的一种类型 。
C语言规定,函数内凡未加存储类型说明的变量均视为自动变量,也就是自动变量可省去说明符 auto。
{ auto int i,j,k;
auto char c;
……
}
{ int i,j,k;
char c;
……
} 等价于:
例如:
自动变量类型 auto
56
自动变量的作用域仅限于定义该变量的个体内 。 在函数中定义的自动变量,只在该函数内有效 。
自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。
函数调用结束,释放存储单元,结束生存期。
因此函数调用结束之后,自动变量的值不能保留。
由于自动变量的作用域和生存期都局限于定义它的个体内
( 函数或复合语句内 ),因此不同的个体中允许使用同名的变量而不会混淆。
自动变量具有以下特点:
57
外部变量的类型说明符为 extern。
外部变量和全局变量是对同一类变量两种不同角度的提法 。
全局变量是从它的作用域提出的,表示了它的作用范围 。
外部变量从它的存储方式提出的,表示了它的生存期 。
当一个源程序由若干个源文件组成时,在一个源文件中定义的外部变量在其它的源文件中也有效 。
外部变量
58
F1.C
int a,b; /*外部变量定义 */
char c; /*外部变量定义 */
main()
{
……
} F2.C
extern int a,b; /*外部变量说明 */
extern char c; /*外部变量说明 */
func (int x,y)
{
……
}
例如有一个源程序由源文件 F1.C和 F2.C组成:
59
静态变量的类型说明符是 static。
静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量 。
例如外部变量虽属于静态存储方式,但不一定是静态变量,
必须由 static加以定义后才能成为静态外部变量,或称静态全局变量 。
自动变量属于动态存储方式,但也可以用 static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式 。
由此看来,变量可由 static进行再说明,并改变其原有的存储方式 。
静态变量
60
在局部变量说明前加上 static说明符就构成静态局部变量 。
1,静态局部变量例如:
static int a,b;
static float array[5]={1,2,3,4,5};
静态局部变量属于静态存储方式,它具有以下特点:
(1) 静态局部变量在函数内定义,但不象自动变量那样,
当调用时就存在,退出函数时就消失。
静态局部变量始终存在着,它的生存期为整个源程序。
61
(2) 静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量 。 退出该函数后,尽管该变量还继续存在,但不能使用它 。
(3) 基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予 0值 。 而对自动变量不赋初值,则其值是不定的 。
1,静态局部变量
62
例 如:
int f( )
{ static int c;
c=c++;
return c;
}
void main()
{
int i;
for(i=1;i<=5;i++)
printf("c=%d\n",f( ) );
}
63
全局变量的说明之前再冠以 static就构成了静态全局变量 。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式 。 这两者在存储方式上并无不同 。
这两者的区别,
当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的 。
而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它 。
2.静态全局变量
64
由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误 。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。
把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
因此 static 这个说明符在不同的地方所起的作用是不同的。
应予以注意。
65
寄存器变量的说明符是 register。
之前的各类变量都是存放在存储器内的,当对一个变量频繁读写时,必须反复访问内存储器,从而花费大量存取时间 。
寄存器变量存放在 CPU的寄存器中,使用时不需要访问内存,而直接从寄存器中读写,这样可提高效率 。
对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量 。
寄存器变量
66
内部函数在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用,称为内部函数 。
定义内部函数的一般形式是:
static 类型说明符 函数名 (形参表 )
例如,static int f(int a,int b)
内部函数也称为静态函数 。 但此处 static的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件 。
因此在不同的源文件中定义同名的静态函数不会引起混淆 。
内部函数和外部函数
67
外部函数在整个源程序中都有效 。
外部函数的定义的一般形式为:
extern 类型说明符 函数名 (形参表 )
例如,extern int f(int a,int b)
如在函数定义中没有说明 extern或 static则隐含为 extern。
在一个源文件的函数中调用其它源文件中定义的外部函数时,应用 extern说明被调函数为外部函数 。
外部函数
68
F1.C (源文件一 )
main()
{
extern int f1(int i); /*外部函数说明,表示 f1函数在其它源文件中 */
……
}
F2.C (源文件二 )
extern int f1(int i); /*外部函数定义 */
{
……
}
例如:
69
1,函数的分类
(1)库函数:由 C系统提供的函数;
(2)用户定义函数:由用户自己定义的函数;
(3)有返回值的函数向调用者返回函数值,应说明函数类型
( 即返回值的类型 );
(4)无返回值的函数:不返回函数值,说明为空 (void)类型;
(5)有参函数:主调函数向被调函数传送数据;
(6)无参函数:主调函数与被调函数间无数据传送;
(7)内部函数:只能在本源文件中使用的函数;
(8)外部函数:可在整个源程序中使用的函数 。
本章小结
70
2,函数定义的一般形式
[extern/static] 类型说明符 函数名 ([形参表 ])
3,函数说明的一般形式
[extern] 类型说明符 函数名 ([形参表 ])
4,函数调用的一般形式,函数名 ([实参表 ]);
5,函数的参数分形参和实参,形参在函数定义中,实参在函数调用中,发生函数调用时,将把实参的值传送给形参 。
6,函数的值指函数的返回值,在函数中由 return语句返回 。
7,数组名作为函数参数时不进行值传送而进行地址传送 。 形参和实参实际上为同一数组的两个名称 。 因此形参数组的值发生变化,实参数组的值当然也变化 。
本章小结
71
8,C语言中,允许函数的嵌套调用和函数的递归调用 。
9,可从三个方面对变量分类,即变量的数据类型,变量作用域和变量的存储类型 。 在第二章中主要介绍变量的数据类型,
本章中介绍了变量的作用域和变量的存储类型 。
10.变量的作用域是指变量在程序中的有效范围,分为局部变量和全局变量 。
11.变量的存储类型是指变量在内存中的存储方式,分为静态存储和动态存储,表示了变量的生存期 。
本章小结
72
实验七:
实验指导书上机实验七
实验第 2,3题源文件分别命名为 sy7-1.cpp
和 sy7-2.cpp上传;
其余实验题自行上机调试。
作 业:
教材 Pg.186 8.2 8.3 8.13 8.14
第八章 函 数本章内容:
1.函数的定义形式
2.函数的参数和函数的值
3.函数调用、函数的嵌套调用以及函数的递归调用
4.数组作为函数参数
5.变量的作用域
6.变量的存储类别
7.内部函数和外部函数
2
C源程序是由函数组成的 。 虽然在前面的程序中都只有一个主函数 main(),但实用程序往往由多个函数组成 。
函数是C源程序的基本模块,通过对函数模块的调用实现特定的功能 。 可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言 。
C语言不仅提供了极为丰富的库函数 (如 Turbo C提供了三百多个库函数 ),还允许用户建立自己定义的函数 。
用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数 。
概 述
3
函数的分类
( 1) 库函数由C系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用 。
在前面各章的例题中反复用到 printf,scanf,getchar,
putchar,gets,puts,strcat等函数均属此类 。
( 2) 用户定义函数由用户按需要写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
1,从函数定义的角度看,函数可分为库函数和用户定义函数
4
(1) 有返回值函数此类函数被调用执行完后将向调用者返回一个执行结果,
称为函数返回值 。 如数学函数即属于此类函数 。
由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型 。
(2) 无返回值函数此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值 。 这类函数类似于其它语言的过程 。
由于函数无须返回值,用户在定义此类函数时可指定它的返回为,空类型,,空类型的说明符为,void”。
2,C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数
5
(1) 无参函数函数定义,函数说明及函数调用中均不带参数 。
主调函数和被调函数之间不进行参数传送 。
此类函数通常用来完成一组指定的功能,可以返回或不返回函数值 。
(2) 有参函数 (带参函数 )
在函数定义及说明时都有参数,称为形式参数 (形参 )。
在函数调用时也必须给出参数,称为实际参数 (实参 )。
进行函数调用时,主调函数将把实参的值传送给形参,
供被调函数使用 。
3,从主调函数和被调函数之间数据传送的角度看可分为无参函数和有参函数两种
6
1,在C语言中所有的函数定义,包括主函数 main在内,都是平行的 。 也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义 。
2,函数之间允许相互调用,也允许嵌套调用 。 把调用者称为主调函数 。 函数还可以自己调用自己,称为递归调用 。
3,一个C源程序必须有,也只能有一个主函数 main。
4,main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用 。
5,C程序的执行总是从 main函数开始,完成对其它函数的调用后再返回到 main函数,最后由 main函数结束整个程序 。
说 明:
7
函数定义的一般形式类型说明符指明了本函数的类型,函数的类型实际上是函数返回值的类型 。
函数名是由用户定义的标识符,函数名后有一个空括号,
其中无参数,但括号不可少 。
{ } 中的内容称为函数体 。
函数头函数体
1.无参函数的一般形式类型说明符 函数名 ( )
{ 类型说明语句
}
8
例如:定义一个函数:
void Hello()
{ printf ("Hello,C System \n");
}
这里,Hello作为函数名,本函数是一个无参函数,当被其它函数调用时,输出 Hello,C System字符串 。
在函数体中的类型说明,是对函数体内部所用到的变量的类型说明。
很多情况下都不要求无参函数有返回值,此时函数类型符可以写为 void。
说 明:
9
有参函数比无参函数多了两个内容,其一是形式参数表,
其二是形式参数类型说明 。
在形参表中给出的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号间隔 。
在进行函数调用时,主调函数将赋予这些形式参数实际的值 。 形参既然是变量,当然必须给以类型说明 。
2.有参函数的一般形式类型说明符 函数名 (形式参数表列 )
{ 类型说明语句
}
10
例如,定义一个函数,用于求两个数中的大数,可写为:
int max(int a,int b)
{ int z;
if (a>b) z=a;
else z=b;
return ( z );
}
现代格式
int max(a,b)
int a,b;
{ int z;
if (a>b) z=a;
else z=b;
return ( z );
}
传统格式
11
1,形参出现在函数定义中,在函数体内都可以使用,函数未被调用时,系统不给它分配存储空间,当函数被调用时系统才给分配其储存空间,用来接受从主调函数传递过来的数据,当该函数调用结束后,则立即释放其储存空间。
2,实参出现在主调函数中,即调用时写入函数括号中的参数,
调用时将实参数据传递给对应的形参,进入被调函数后,实参变量不能使用。
3,形参与实参的的关系:形参和实参的功能是作数据传递。发生函数调用时(形、实结合),主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。
函数的参数和函数的值函数的参数
12
函数的形参和实参具有以下特点,
1,形参变量只有在被调用时才分配内存单元,在调用结束时,
即刻释放所分配的内存单元 。
因此,形参只有在函数内部有效 。 函数调用结束返回主调函数后则不能再使用该形参变量 。
例:
main()
{ int x,y,z;
scanf("%d%d",&x,&y);
z=max(x,y);
printf("max=%d\n",z);
}
int max(int a,int b)
{ int d;
d=a>b?a:b;
return (d);
}
13
2.实参可以是常量,变量,表达式,函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参 。 因此应预先用赋值,输入等办法使实参获得确定值 。
3,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生,类型不匹配,的错误 。
4,函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。
因此 在函数调用过程中,形参的值发生改变,而实参中的值不会变化 。
函数的形参和实参具有以下特点,
14
void main()
{ int s(int); int n;
printf("input number\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n);
}
int s(int n)
{ int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
return 0;
}
如果输入 10
输出结果是:
例:分析下面程序的输出结果:
15
函数的返回值是指函数被调用之后,执行函数体中的程序段所得的并返回给主调函数的值 。
C语言函数的值只能通过 return语句返回函数的值 。
函数的返回值
1,return语句返回函数的值
return 语句使用的一 般形式:
return 表达式;
或者为,return (表达式 );
16
2,函数值的类型和函数定义中函数的类型应保持一致 。 如果两者不一致,则以函数类型为准,自动进行类型转换 。
3,如函数值为整型,在函数定义时可以省去类型说明。
4,不返回函数值的函数,可以明确定义为,空类型,,类型说明符为,void”。
return该语句的功能是计算表达式的值,并返回给主调函数。 在函数中允许有多个 return语句,但每次调用只能有一个 return 语句被执行,因此只能返回一个函数值。
17
因此可定义为:
void s(int n)
{ ……
}
如上例中函数 s并不向主函数返函数值一旦函数被定义为空类型后,就不能在主调函数中使用被调函数的函数值了。
例如,在定义 s为空类型后,在主函数中写下述语句
sum=s(n); 就是错误的。
为了使程序有良好的可读性并减少出错,凡不要求返回值的函数都应定义为空类型。
int s(int n)
{ int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
return 0;
}
18
函数的调用函数调用的一般形式函数名 (实际参数表 )
对无参函数调用时则无实际参数表 。
实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式 。
各个实际参数之间用逗号分隔 。
19
1,函数表达式函数作表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。
例如,z=max(x,y) 是一个赋值表达式,把 max函数的返回值赋予变量 z。
调用函数的方法:
2,函数语句函数调用的一般形式加上分号即构成函数语句,即:
函数名 (实际参数表 );
例如,printf ("%d",a); scanf ("%d",&b);都是以函数语句的方式调用函数。
20
3.函数实参函数作为另一个函数调用的实际参数出现 。
这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的 。
例如,printf("%d",max(x,y)); 即是把 max调用的返回值又作为 printf函数的实参来使用的。
调用函数的方法:
21
1.被调用的函数必须是已经存在的函数 。
2.对库函数的调用,必须把该函数的头文件用 #include命令包含在源文件前部 。
#include " 头文件名 " 或,#include < 头文件名 >
3.对用户自定义函数,如果被调函数的定义在主调函数之后,
则必须在主调函数中的说明部分对该被调函数进行说明,
这与使用变量之前要先进行变量说明是一样的 。
在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理 。
对被调用函数的声明和函数原型函数调用的条件:
22
1,传统格式,其一般格式为:
类型说明符 被调函数名 ( );
这种格式只给出函数返回值类型,被调函数名及一个空括号 。
这种格式由于在括号中没有任何参数信息,因此不便于编译系统进行错误检查,易于发生错误 。
2,现代格式,其一般形式为:
类型说明符 被调函数名 (类型 形参,类型 形参 …);
或为,类型说明符 被调函数名 (类型,类型 …);
现代格式的括号内给出了形参的类型和形参名,或只给出形参类型 。 这便于编译系统检错,以防止可能出现的错误 。
对被调函数的声明(函数原型):
23
例 如:
main()
{ int x,y,z;
int max(int a,int b); //或者 int max(int,int)
scanf("%d%d",&x,&y);
z=max(x,y);
printf("max=%d\n",z);
}
int max(int a,int b)
{ int d;
d=a>b?a:b;
return (d);
}
24
C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。
1,如果被调函数的返回值是整型时,可以不对被调函数作说明,而直接调用。但是实际当中都是加上函数原型的。
2,当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作说明而直接调用。
3,如在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。
说 明:
25
char str(int a);
float fy(float b);
main()
{
……
}
char str(int a)
{
……
}
float fy(float b)
{
……
}
放在最前面说明其中第一,二行对 str函数和 fy函数预先作了说明。 因此在以后各函数中无须对 str和 f函数再作说明就可直接调用。
26
C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。
虽然C语言不允许在一个函数的定义中出现对另一个函数的定义; 但是C语言允许在一个函数的定义中出现对另一个函数的调用。
若被调函数中需要使用另外一个函数的功能时(即被调函数中又需要调用其它函数),即出现了函数的嵌套调用。
函数的嵌套调用
27
main()
{…
fa();
….
fb();
}
fa()
{….
fb();
….
}
fb();
{…
….
}
28
例 8.6 用弦截法方程 f(x) =x3+5x2+16x-80=0的根。
#include "math.h"
float f(float x)
{ float y;
y=((x-5.0)*x+16.0)*x-80;
return (y);
}
float xpoint(float x1,float x2)
{ float y;
y=(x1*f(x2)-x2*f(x1))/(f(x2)-f(x1));
return (y);
}
float root(float x1,float x2)
{ int i;
float x,y,y1;
y1=f(x1);
do
{ x=xpoint(x1,x2);
y=f(x);
if(y*y1>0 )
{y1=y; x1=x;}
else
x2=x;
}while(fabs(y)>=0.00001);
return(x);
}
void main()
{ float x1,x2,f1,f2,x;
do
{ printf("input x1,x2:\n");
scanf("%f,%f",&x1,&x2);
f1=f(x1); f2=f(x2);
} while(f1*f2>=0);
x=root(x1,x2);
printf("root is:%f",x);
}
29
函数在它的函数体内直接或间接调用它自身称为递归调用。
这种函数称为递归函数。
C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数,执行递归函数将反复调用其自身,每调用一次就进入新的一层。
例如有函数 f如下:
函数的递归调用
int f (int x)
{ int y;
z= f(y);
return z;
}
30
例:用递归法计算 n!
阶乘的定义:
31
long fac(int n)
{ long f;
if(n==0||n==1)
f=1;
else
f=fac(n-1)*n;
return(f);
}
main()
{ int n;
long y;
printf("input n=,\n");
scanf("%d",&n);
y=fac(n);
printf("%d!=%ld",n,y);
}
递推回归递推 递推回归回归
32
递归结束条件及结束时的值;
能用递归形式表示,并且递归向终止条件发展。
构成递归的条件:
33
数组可以作为函数的参数使用,进行数据传送。
数组用作函数参数一种是把数组元素作为实参使用;
数组用作函数参数另一种是把数组名作为函数的形参和实参使用。
数组作为函数参数一、数组元素作函数实参数数组元素作为函数的实参与普通变量作实参数并无区别,函数实参的使用与普通变量是完全相同的。
在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。
34
用数组名作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组 ( 或指针变量 ),都必须有明确的数组说明 。
当形参和实参二者不一致时,即会发生错误 。
二、数组名作为函数参数在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。
注 意:
35
因为实际上形参数组并不存在,编译系统不为形参数组分配内存。
C语言中规定,数组名代表数组的首地址。
因此在数组名作函数参数时所进行的传送是 地址的传送,
也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。
实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。
数据的传递成为双向的。
注 意:
36
float aver(float a[10])
{ int i;
float av,s=a[0];
for(i=1;i<10;i++)
s=s+a[i];
av=s/10;
return av;
}
void main()
{ float aver(float a[]);
float sco[10],av;
int i;
printf("input 10 scores:\n");
for(i=0;i<10;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average is,%5.2f",av);
}
例 8.11 数组 a中存放了 10个学生的成绩,求平均成绩。
37
形参数组和实参数组的类型必须一致,否则将引起错误 。
形参数组和实参数组的长度可以不相同,因为在调用时只传送首地址而不检查形参数组的长度 。
当形参数组的长度与实参数组不一致时,虽不至于出现语法错误 (编译能通过 ),但程序执行结果将与实际不符,这是应予以注意的 。
用数组名作为函数参数时还应注意以下几点:
38
float aver(float a[10])
{ int i;
float av,s=a[0];
for(i=1;i<10;i++)
s=s+a[i];
av=s/10;
return av;
}
void main()
{
float aver(float a[]);
float sco[15],av;
int i;
printf("input 15 scores:\n");
for(i=0;i<15;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average is,%5.2f",av);
}
例 如:
39
3,在函数形参表中,可以不给出形参数组的长度,或用一个变量来表示数组元素的个数 。
即:可以写为:
void aver(float a[ ])
或写为
void aver(float a[ ],int n)
其中形参数组 a没有给出长度,而由 n值动态地表示数组的长度 。
n 的值由主调函数的实参进行传送 。
用数组名作为函数参数时还应注意以下几点:
40
float aver(float a[],int n)
{ int i;
float av,s=a[0];
for(i=1;i<n;i++)
s=s+a[i];
av=s/n;
return av;
}
void main()
{
float aver(float a[],int n);
float sco[15],av;
int i;
printf("input scores:\n");
for(i=0;i<15;i++)
scanf("%f",&sco[i]);
av=aver(sco,15);
printf("average is,%5.2f",av);
}
例 如:
41
例 8.13 写一用选择法排序的函数,调用该函数排序。
/*选择法排序函数 */
void sort(int a[ ],int n)
{
int i,j,p;
for(i=0;i<n-1;i++)
{ p=i;
for(j=i+1;j<n;j++)
if(a[p]<a[j]) p=j;
if(i!=p)
{ s=a[i];
a[i]=a[p];
a[p]=s;
}
}
}
/*主函数调用排序函数,*/
main()
{
void sort(int a[],int n) ;
int i,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
sort(a,10);
for(i=0;i<10;i++)
printf("%d ",a[i]);
}
42
变量的有效性范围称变量的作用域。
在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。
这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。
C语言中所有的量都有自己的作用域。
变量说明的方式不同,其作用域也不同。
变量的作用域
43
局部变量也称为内部变量。
局部变量是在函数内作定义说明的。
局部变量的作用域仅限于函数内,离开该函数后再使用这种变量是非法的。
C语言中的变量,按作用域范围可局部变量和全局变量一、局部变量
44
例如,int f1(int a) /*函数 f1*/
{ int b,c;
……
} a,b,c作用域
int f2(int x) /*函数 f2*/
{ int y,z;
} x,y,z作用域
main() /*主函数 main*/
{ int m,n;
} m,n 作用域在函数 f1内定义了三个变量,a为形参,b,c为一般变量。
在 f1的范围内 a,b,c有效,即 a,b,c变量的作用域限于 f1内。
x,y,z的作用域限于 f2内。 m,n的作用域限于 main函数内。
45
主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用 。
主函数中也不能使用其它函数中定义的变量 。 因为主函数也是一个函数,它与其它函数是平行关系 。
形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量 。
允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆 。
关于局部变量作用域的说明:
46
复合语句中也可定义变量,其作用域只在复合语句范围内。
例,main()
{ int x,y;
……
{ int z;
…
}
… …
}
47
全局变量也称为外部变量,它是在函数外部定义的变量 。
全局变量不属于哪一个函数,它属于一个源程序文件 。
全局变量的作用域是整个源程序 。
全局变量的说明符为 extern。
在一个函数之前定义的全局变量,在该函数内使用可不再加以说明 。
二、全局变量
48
例 如:
int a,b; /*全局变量 */
void f1() /*函数 f1*/
{ ……
}
float x,y; /*全局变量 */
int fz() /*函数 fz*/
{ ……
}
main() /*主函数 */
{ ……
} /*全局变量 x,y作用域 全局变量 a,b作用域 */
49
int s1,s2,s3; /*声明全局变量,用来传递数据 */
int vs( int a,int b,int c)
{ int v;
v=a*b*c; s1=a*b; s2=b*c; s3=a*c;
return v; }
void main()
{ int v,l,w,h;
printf("\ninput length,width and height\n");
scanf("%d%d%d",&l,&w,&h);
v=vs(l,w,h);
printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
}
例:输入长方体的长宽高 l,w,h。
求体积及三个面 x*y,x*z,y*z的面积。
50
对于全局变量的说明:
全局变量可以作为函数间数据联系的渠道。
由于C语言规定函数返回值只有一个,当需要增加函数的返回数据时,用外部变量是一种方法。
非必要时不要使用全局变量,因为全局变量在全部程序执行时占用存储单元并且降低了程序的通用性和清晰性。
若在同一个源文件中,全局变量与局部变量同名,则局部变量的作用范围内全局变量被“屏蔽”。
int a,b; /*全局变量 */
main() /*主函数 */
{ int a,b;
……
}
51
int a=5,b=8;
void main()
{ int a=10,z;
int max(int a,int b);
z=max(a,b);
printf("max=%d\n",z);
}
int max(int a,int b)
{ int d;
d=a>b?a:b;
return (d);
}
例 如:
问:程序运行的结果
52
各种变量作用域不同,就本质来说是变量的存储类型不同。
存储类型是指变量占用内存空间的方式,也称存储方式。
变量的存储方式可分为“静态存储”和“动态存储”两种。
静态存储变量通常是在变量定义时就分定存储单元并一直保持不变,直至整个程序结束。
动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放。
变量的存储类型
53
动态存储变量的典型是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时才予以分配,调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、
释放形参变量的存储单元。
静态存储变量则是一直存在的,而动态存储变量则时而存在时而消失。
由于变量存储方式不同而产生的这种特性称变量的生存期。
生存期表示了变量存在的时间。生存期和作用域是从时间和空间两个不同角度来描述变量的特性,既有联系又有区别。
一个变量究竟属于哪一种存储方式,并不能仅从其作用域来判断,还应有明确的存储类型说明。
变量的存储类型
54
自动变量和寄存器变量属于动态存储方式,外部变量和静态变量属于静态存储方式 。
变量的说明不仅应说明其数据类型,还应说明存储类型 。
变量说明的完整形式为:
存储类型说明符 数据类型说明符 变量名,变量名 …;
例如,static int a,b; 说明 a,b为静态类型变量
auto char c1,c2; 说明 c1,c2为自动字符变量 (省略 )
static int a[5]={1,2,3,4,5}; 说明 a为静整型数组
extern int x,y; 说明 x,y为外部整型变量
C语言中的存储类型
auto 自动变量 register 寄存器变量
extern 外部变量 static 静态变量
55
auto存储类型是C语言程序中使用最广泛的一种类型 。
C语言规定,函数内凡未加存储类型说明的变量均视为自动变量,也就是自动变量可省去说明符 auto。
{ auto int i,j,k;
auto char c;
……
}
{ int i,j,k;
char c;
……
} 等价于:
例如:
自动变量类型 auto
56
自动变量的作用域仅限于定义该变量的个体内 。 在函数中定义的自动变量,只在该函数内有效 。
自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。
函数调用结束,释放存储单元,结束生存期。
因此函数调用结束之后,自动变量的值不能保留。
由于自动变量的作用域和生存期都局限于定义它的个体内
( 函数或复合语句内 ),因此不同的个体中允许使用同名的变量而不会混淆。
自动变量具有以下特点:
57
外部变量的类型说明符为 extern。
外部变量和全局变量是对同一类变量两种不同角度的提法 。
全局变量是从它的作用域提出的,表示了它的作用范围 。
外部变量从它的存储方式提出的,表示了它的生存期 。
当一个源程序由若干个源文件组成时,在一个源文件中定义的外部变量在其它的源文件中也有效 。
外部变量
58
F1.C
int a,b; /*外部变量定义 */
char c; /*外部变量定义 */
main()
{
……
} F2.C
extern int a,b; /*外部变量说明 */
extern char c; /*外部变量说明 */
func (int x,y)
{
……
}
例如有一个源程序由源文件 F1.C和 F2.C组成:
59
静态变量的类型说明符是 static。
静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量 。
例如外部变量虽属于静态存储方式,但不一定是静态变量,
必须由 static加以定义后才能成为静态外部变量,或称静态全局变量 。
自动变量属于动态存储方式,但也可以用 static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式 。
由此看来,变量可由 static进行再说明,并改变其原有的存储方式 。
静态变量
60
在局部变量说明前加上 static说明符就构成静态局部变量 。
1,静态局部变量例如:
static int a,b;
static float array[5]={1,2,3,4,5};
静态局部变量属于静态存储方式,它具有以下特点:
(1) 静态局部变量在函数内定义,但不象自动变量那样,
当调用时就存在,退出函数时就消失。
静态局部变量始终存在着,它的生存期为整个源程序。
61
(2) 静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量 。 退出该函数后,尽管该变量还继续存在,但不能使用它 。
(3) 基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予 0值 。 而对自动变量不赋初值,则其值是不定的 。
1,静态局部变量
62
例 如:
int f( )
{ static int c;
c=c++;
return c;
}
void main()
{
int i;
for(i=1;i<=5;i++)
printf("c=%d\n",f( ) );
}
63
全局变量的说明之前再冠以 static就构成了静态全局变量 。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式 。 这两者在存储方式上并无不同 。
这两者的区别,
当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的 。
而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它 。
2.静态全局变量
64
由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误 。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。
把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
因此 static 这个说明符在不同的地方所起的作用是不同的。
应予以注意。
65
寄存器变量的说明符是 register。
之前的各类变量都是存放在存储器内的,当对一个变量频繁读写时,必须反复访问内存储器,从而花费大量存取时间 。
寄存器变量存放在 CPU的寄存器中,使用时不需要访问内存,而直接从寄存器中读写,这样可提高效率 。
对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量 。
寄存器变量
66
内部函数在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用,称为内部函数 。
定义内部函数的一般形式是:
static 类型说明符 函数名 (形参表 )
例如,static int f(int a,int b)
内部函数也称为静态函数 。 但此处 static的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件 。
因此在不同的源文件中定义同名的静态函数不会引起混淆 。
内部函数和外部函数
67
外部函数在整个源程序中都有效 。
外部函数的定义的一般形式为:
extern 类型说明符 函数名 (形参表 )
例如,extern int f(int a,int b)
如在函数定义中没有说明 extern或 static则隐含为 extern。
在一个源文件的函数中调用其它源文件中定义的外部函数时,应用 extern说明被调函数为外部函数 。
外部函数
68
F1.C (源文件一 )
main()
{
extern int f1(int i); /*外部函数说明,表示 f1函数在其它源文件中 */
……
}
F2.C (源文件二 )
extern int f1(int i); /*外部函数定义 */
{
……
}
例如:
69
1,函数的分类
(1)库函数:由 C系统提供的函数;
(2)用户定义函数:由用户自己定义的函数;
(3)有返回值的函数向调用者返回函数值,应说明函数类型
( 即返回值的类型 );
(4)无返回值的函数:不返回函数值,说明为空 (void)类型;
(5)有参函数:主调函数向被调函数传送数据;
(6)无参函数:主调函数与被调函数间无数据传送;
(7)内部函数:只能在本源文件中使用的函数;
(8)外部函数:可在整个源程序中使用的函数 。
本章小结
70
2,函数定义的一般形式
[extern/static] 类型说明符 函数名 ([形参表 ])
3,函数说明的一般形式
[extern] 类型说明符 函数名 ([形参表 ])
4,函数调用的一般形式,函数名 ([实参表 ]);
5,函数的参数分形参和实参,形参在函数定义中,实参在函数调用中,发生函数调用时,将把实参的值传送给形参 。
6,函数的值指函数的返回值,在函数中由 return语句返回 。
7,数组名作为函数参数时不进行值传送而进行地址传送 。 形参和实参实际上为同一数组的两个名称 。 因此形参数组的值发生变化,实参数组的值当然也变化 。
本章小结
71
8,C语言中,允许函数的嵌套调用和函数的递归调用 。
9,可从三个方面对变量分类,即变量的数据类型,变量作用域和变量的存储类型 。 在第二章中主要介绍变量的数据类型,
本章中介绍了变量的作用域和变量的存储类型 。
10.变量的作用域是指变量在程序中的有效范围,分为局部变量和全局变量 。
11.变量的存储类型是指变量在内存中的存储方式,分为静态存储和动态存储,表示了变量的生存期 。
本章小结
72
实验七:
实验指导书上机实验七
实验第 2,3题源文件分别命名为 sy7-1.cpp
和 sy7-2.cpp上传;
其余实验题自行上机调试。
作 业:
教材 Pg.186 8.2 8.3 8.13 8.14