版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第7章函数7.1函数概述7.2函数的定义7.3函数的参数和函数的返回值7.4函数的调用与声明7.5函数的参数传送7.6函数的嵌套调用和递归调用7.7内部变量和外部变量7.8变量的存储类别7.9内部函数和外部函数7.10模块化程序设计举例模块化程序设计是进行大型程序设计的一种有效方法,其基本思想是把一个复杂问题按功能或层次分成若干模块,即将一个大任务分成若干个子任务,对应每一个子任务编制一个子程序。由于模块相互独立,在设计其中任一模块时不会受到其它模块的干扰,因而可将原来较为复杂的问题简化为一系列简单模块的设计。
在C语言中,子程序的功能是由函数来完成的。C语言的源程序是由函数组成的,且至少要有一个主函数main()。一般情况下,一个C程序可以由一个主函数和若干个其它函数构成。主函数调用其它函数,其它函数之间也可以相互调用。函数是C语言源程序的基本模块,C语言通过对函数模块的调用实现特定的功能。
可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言。由于采用了函数模块式的结构,C语言易于实现结构化程序设计,使程序的层次结构清晰,便于程序的编写、阅读和调试。
C语言源程序是由函数组成的。虽然在前面各章节的介绍中C源程序都只有一个主函数main(),但在实际应用中,程序往往由多个函数组成。函数是C源程序的基本模块,通过对函数模块的调用实现特定的功能。
C语言不仅提供了极为丰富的库函数(如TurboC、MSC都提供了300多个库函数),还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。7.1函数概述
1.从函数定义的角度分类
从函数定义的角度分类,函数可分为库函数和用户自定义函数两种。
1)库函数
库函数是由C系统提供的,用户无须定义,也不必在程序中做类型说明,只需在程序前包含有该函数原型声明的头文件便可在程序中直接调用。在前面各章的例题中反复用到的printf()、scanf()、getchar()、putchar()、gets()、puts()等函数均属此类。
2)用户自定义函数
用户自定义函数指由用户自己按需要编写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且需要时在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
2.从函数是否有返回值的角度分类
C语言的函数兼有其它语言中的函数和过程两种功能,从函数是否有返回值的角度分类,可分为有返回值函数和无返回值函数两种。
1)有返回值函数
此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。如数学函数即属于此类函数。由用户定义的这种要返回函数值的函数,必须在函数定义和函数声明中明确返回值的类型。
2)无返回值函数
此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。由于函数无须返回值,用户在定义此类函数时可指定它的返回值为“空类型”,空类型的说明符为“void”。
3.从函数调用是否有参数的角度分类
从函数调用是否有参数的角度分类,可分为无参函数和有参函数两种。
1)无参函数
无参函数在函数定义、函数声明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。
2)有参函数
有参函数也称为带参函数。在函数定义及函数声明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)。进行函数调用时,主调函数把实参的值传送给形参,供被调函数使用。
4.从函数的功能角度分类
C语言提供了极为丰富的库函数,从函数的功能角度分类,可分为字符类型分类函数、转换函数、目录路径函数、诊断函数、图形函数、输入/输出函数、接口函数、字符串函数、内存管理函数、数学函数、日期和时间函数、进程控制函数、其它函数等。
(1)字符类型分类函数:用于对字符按ASCII码分类,可分为字母、数字、控制字符、分隔符、大小写字母等。
(2)转换函数:用于字符或字符串的转换。在字符量和各类数字量(整型、实型等)之间进行转换,在大、小写之间进行转换。
(3)目录路径函数:用于文件目录和路径操作。
(4)诊断函数:用于内部错误检测。
(5)图形函数:用于屏幕管理和各种图形功能。
(6)输入/输出函数:用于完成输入/输出功能。
(7)接口函数:用于与DOS、BIOS和硬件的接口。
(8)字符串函数:用于字符串操作和处理。
(9)内存管理函数:用于内存管理。
(10)数学函数:用于数学计算。
(11)日期和时间函数:用于日期、时间转换操作。
(12)进程控制函数:用于进程管理和控制。
(13)其它函数:用于其它各种功能。以上各类函数不仅数量多,而且有的还需要掌握硬件知识才能使用,因此要想全部掌握势必需要一个较长的学习过程。初学者应首先掌握一些最基本、最常用的函数,再逐步深入。有关库函数的内容可查阅相关资料。
7.2.1函数的定义形式
在C语言中,函数定义就是编写完成函数功能的程序块。一个用户自定义函数由两部分组成:函数首部和函数体。根据函数有无参数,其定义形式分为以下两种。
1.无参函数的定义形式
类型标识符函数名() /*函数首部*/
{7.2函 数 的 定 义说明部分
执行部分
}/*函数体*/
2.有参函数的定义形式
类型标识符函数名(形式参数表)/*函数首部*/
{
说明部分
执行部分
}/*函数体*/说明:
(1)类型标识符和函数名构成函数首部,函数类型标识符指明了函数的类型,也就是函数返回值的数据类型;函数名是由用户定义的标识符,命名要符合标识符的命名规则,同一程序中的函数不能重名,函数名用来唯一标识一个函数。
(2)花括号{}中的内容为函数体,说明部分对函数体内部所用到的各种变量的类型进行定义和声明,对被调用的函数进行声明,执行部分是实现函数功能的语句序列。
(3)当函数体为空时,称此函数为空函数。调用空函数时,空函数什么也不做。
(4)在C语言中,函数之间是并列关系,因此函数定义中不能包含另一个函数的定义,即函数定义不能嵌套。7.2.2函数参数的说明
函数参数的说明有两种方式:一种是现代方式,一种是传统方式。现代方式在现在的C语言版本中使用,传统方式在老的C语言版本中使用,不过现在的C语言版本也支持传统方式。
1.现代方式
函数参数说明的现代方式是将函数参数及其类型的说明都放在函数首部的形式参数表中,例如:
intmax(intx,inty)
2.传统方式
函数参数说明的传统方式是将函数参数放在函数首部的形式参数表中,而其类型的说明另起一行来说明,例如:
intmax(x,y)
intx,y;7.2.3函数定义举例
【例7.1】
编写函数,在屏幕上显示“Hello,World!”。
voidhello()
{
printf("Hello,World!\n");
}
【例7.2】
编写函数,求两个整数的最大值。
intmax(intx,inty)/*定义函数类型、函数名、形参*/
{
intm;
if(x>y)/*求两个数的最大值并赋给变量m*/
m=x;
else
m=y;
return(m);/*返回结果*/
}
【例7.3】
编写函数,求xn。
doublepower(doublex,intn)
{
doublep;
if(n>0)
for(p=1.0;n>0;n--)
p=p*x;
else
p=1.0;
return(p);
}
以上这些函数只有被主调函数调用后才能实现其功能。
7.3.1函数的形式参数和实际参数
函数定义时的参数称为形式参数,简称为形参。形参在函数未被调用时是没有确定的值的,只是形式上的参数。函数被调用时的参数称为实际参数,简称实参。实参可以是常量、变量或表达式,有确定的值。函数定义的形参可接受实参传来的值。7.3函数的参数和函数的返回值在函数定义时,如果函数有参数,必须定义形参的类型。在函数调用时,函数的实参与形参要求在个数上相等,对应的形参和实参类型相同或赋值兼容。
形参和实参可以同名,也可以不同名。形参是该被调函数内部的变量,实参是由主调函数提供的,如果实参是变量,即使形参变量和实参变量同名,也是两个不同的量,占用不同的内存单元。
【例7.4】
编写函数 ,然后调用它来求 。
#include<stdio.h>
ints(intn)/*n是形参*/
{
inti,sum=0;
for(i=1;i<=n;i++)
sum=sum+i;
return(sum);
}
voidmain()
{
intn,sum;
printf("inputn:");
scanf("%d",&n);
sum=s(n);/*n是实参*/
printf("sum(1..%d)=%d\n",n,sum);
}程序运行结果:
Inputn:100↙
sum(1..100)=5050
该程序由两个函数main()和s()组成,main()函数调用s()函数。s()函数有一个整型形参n。
当main()函数调用s()函数时,s()函数将实参n的值传送给形参n,计算出 。虽然形参和实参同名,但它们是不同的量,初学者可以先使用不同名的形参和实参。7.3.2函数的返回值
模块化程序设计思想中的子程序一般分为两种,一种是带返回值的,称为函数;另一种是不带返回值的,称为过程。在C语言中并不区分子程序是函数还是过程,而是统称为函数,但将函数分为带返回值的函数和不带返回值的函数两种。
如果一个函数带返回值,此类函数的功能主要是完成某种计算,得到计算结果,因此它必须使用显式的返回语句向调用者返回计算结果,这个计算结果就称为函数的返回值。用户定义此类函数时,必须在函数定义中明确指定函数返回值的数据类型,函数的返回值通过return语句返回。例7.2中的函数max(),第一行intmax(intx,inty)中开始的int为该函数返回值的数据类型,最后的return(m);语句用于向调用者返回函数的计算结果。
如果一个函数无返回值,此类函数用于完成某项特定的处理任务,函数的任务完成后不需要向调用者返回任何结果。用户在定义此类函数时应指定它的返回值为空类型,空类型的类型说明符为void,也无需return语句返回任何结果。
例7.1中的函数hello(),第一行voidhello()中开始的void表明该函数的返回值为空类型,即无返回值,最后也无需return语句向调用者返回任何结果。说明:
(1)函数的返回值是通过被调用函数中的return语句实现的,其使用格式为
return(表达式);
或
return表达式;
return语句的执行过程是首先计算表达式的值,然后将计算结果返回给调用者。
(2)带返回值的函数只能返回一个值。
(3)函数返回值的类型由定义函数时的函数首部的函数类型决定。如果函数值类型与return语句中表达式值的类型不一致,则以函数定义时函数首部的类型为准。
(4)
return语句的另一项功能是结束被调用的函数,返回到主调函数中继续执行被调用函数后面的语句。其使用格式为
return;
(5)在函数定义时,函数中允许使用多个return语句,但只要一个起作用后,其它的就失去作用了。
C语言程序从主函数main()开始执行,一直到主函数main()的函数体结束为止。而函数的执行是通过对函数的调用来执行的,调用者称为主调函数,被调用者称为被调函数。当被调函数被调用结束时,从被调函数结束的位置再返回到主调函数中被调函数后面的语句继续执行,直到主函数main()结束。7.4函数的调用与声明7.4.1函数调用的形式
有参函数调用的形式如下:
函数名(实参表列)
无参函数调用的形式如下:
函数名(
)
对于有参函数,如果实参表列包含多个实参,则各实参间用逗号隔开,实参与形参的个数相等,对应的类型应相同或赋值兼容。7.4.2函数调用的方式
可以用两种方式调用函数。
1.函数语句
C语言中的函数可以仅进行某些操作而不返回值,这时函数的调用可作为一条独立的语句,如:
函数名(实参表列);
或
函数名(
);
注意:函数后面有一个分号“;”。
2.函数表达式
当所调用的函数有返回值且需要使用返回值时,函数的调用可作为表达式出现在允许表达式出现的任何地方。例如,例7.3中的power()函数在调用时就可作为函数表达式(y=power(x,n);)。
实际上,对于有返回值的函数,如果主调函数不使用其返回值,也可以以函数语句的调用方式来调用。例如,程序中可以使用
getch();
主调函数并不关心其返回值(即键入的字符)是什么,它只需要用户敲一个键即可。7.4.3函数的声明
调用一个函数,首先要求该函数已经被定义,但有时仅有定义仍然不能正确调用该函数,这时需要增加对被调用函数的声明。
函数声明的目的是使编译系统在编译阶段对函数的调用进行合法性检查,判断形参与实参的类型及个数是否匹配。
函数声明采用函数原型的方法。函数原型就是已定义函数的首部。
对于有参函数,函数声明的形式如下:
类型标识符函数名(形参表列);
对于无参函数,函数声明的形式如下:
类型标识符函数名();
注意:函数声明除了函数的首部,最后还有一个分号“;”。函数声明可以放在开始,即所有函数的前面,也可以放在主调函数内调用被调函数前。事实上,在下面3种情形下可以省略对被调函数的声明:
(1)被调函数的定义出现在主调函数之前。
(2)被调函数的返回值类型是整型或字符型。整型是系统默认的类型。
(3)被调函数是C语言提供的库函数。虽然库函数的调用不需要作函数声明,但必须把该库函数的头文件用
#include命令包含在源程序的最前面。例如,像getchar()、putchar()这样的函数定义是放在stdio.h头文件中的,只要在程序最前面加上#include<stdio.h>就可以了。
【例7.5】
编写函数xn,然后调用它来求213。
#include<stdio.h>
voidmain()
{
doublepower(doublex,intn);/*对函数power()进行声明*/
doublex=2.0,y;
intn=13;
y=power(x,n);/*调用函数power()*/
printf("y=%lf",y);
}
doublepower(doublex,intn)/*对函数power()进行定义*/
{
doublep;
if(n>0)
for(p=1.0;n>0;n--)
p=p*x;
else
p=1.0;
return(p);
}程序运行结果:
y=8192.000000
模块化程序设计中,参数传送是既重要又复杂的概念,如果不能掌握函数的参数传送,就无法编写出正确的函数。本节将进一步介绍函数的形参和实参的特点以及两者之间的关系。7.5函数的参数传送7.5.1实参与形参之间的数据传送
形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。函数的实参和形参进行数据传送时具有以下特点:
(1)函数调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向传送给实参,这称为按值传送,也称传值调用。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
(2)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
(3)实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参,因此应预先用赋值、输入等方法使实参获得确定值。
(4)实参和形参在数量上、类型上、顺序上应严格一致,否则会发生“类型不匹配”的错误。
【例7.6】实参与形参之间的数据单向传送。
#include<stdio.h>
voidp(intx,inty)
{
x=x+3;
y=y+7;
printf("x=%d,y=%d\n",x,y);
}
voidmain()
{
inta=8,b=10;
printf("a=%d,b=%d\n",a,b);
p(a,b);
printf("a=%d,b=%d\n",a,b);
}
程序运行结果:
a=8,b=10
x=11,y=17
a=8,b=10
本程序中定义了一个函数p(),该函数的功能仅是对传送进来的参数做了一个简单的运算,即对第一个参数x加3,对第二个参数y加7。在主函数中说明定义了两个变量a和b,分别初始化为8和10。当主函数调用函数p()时,系统给形参开辟两个存储单元x和y,将实参a和b的值传送给函数p()的形参x和y。传送过程如图7-1所示。
图7-1实参与形参之间的数据单向传送在主函数中第一次用printf()输出a和b的值,分别为8和10。调用函数p(),将实参a和b的值分别传送给形参x和y后,函数p()内部对形参x和y进行了改变,用printf()输出x和y的值,分别为11和17。当调用函数p()结束返回主函数后,再用printf()输出a和b的值,仍然分别为8和10。可见函数内部对形参的改变并没有改变实参的值,即实参的值不随形参的变化而变化。7.5.2数组作参数
数组也可以作为函数参数使用。数组作为函数的参数有两种形式:一种是数组元素作实参使用;另一种是数组名作为函数的形参和实参使用。
1.数组元素作函数实参
数组元素就是下标变量,它与普通变量并无区别,因此它作为函数实参使用时与普通变量是完全相同的,在进行函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。
【例7.7】
编写程序,判别一个整数数组中各元素的值,若大于0则输出该值,否则输出0。
#include<stdio.h>
voidprint(intn)
{
if(n>0)
printf("%5d",n);
else
printf("%5d",0);
}
voidmain()
{
inti,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(i=0;i<10;i++)
print(a[i]);
}
程序运行结果:
2354-1390-64891234095-1↙
235409008912340950
2.数组名作函数实参
数组名实际上表示的是整个数组的首地址。如果调用函数的实参是数组名,则被调用函数的形参也应该是数组类型。尤为重要的是,如果实参是数组名,形参是数组类型,则调用函数和被调用函数存取的将是相同的一组空间,原因是实参传送给形参的是一组空间的首地址。
【例7.8】
编写程序,输入10个学生的成绩,对这些成绩进行排序,输出排序前后的结果。
#include<stdio.h>
voidsort(floata[],intn) /*排序函数*/
{
inti,j,k;
floatt;
for(i=0;i<n-1;i++){
k=i;
for(j=i+1;j<n;j++)
if(a[k]<a[j])
k=j;
t=a[i];
a[i]=a[k];
a[k]=t;
}
}
voidprint(floata[],intn) /*输出函数*/
{
inti;
for(i=0;i<n;i++)
printf("%5.1f",a[i]);
printf("\n");
}
voidmain() /*主函数*/
{
inti;
floata[10];
for(i=0;i<10;i++)
scanf("%f",&a[i]);
printf("Beforesort:\n");
print(a,10);
sort(a,10);
printf("Aftersort:\n");
print(a,10);
}
程序运行结果:
65897856958869748623↙
Beforesort:
65.089.078.056.095.088.069.074.086.023.0
Aftersort:
95.089.088.086.078.074.069.065.056.023.0
用数组名做函数的实参,应分别在主调函数和被调函数中定义数组,实参数组与形参数组要一一对应,而且应该是同一类型。实参数组与形参数组大小可以一致也可以不一致,因为C编译系统对形参数组大小不作检查,只是将实参数组的首地址传送给形参数组。实际上,形参数组可以不指定大小,在定义形参数组时后面跟一个空的方括号即可。
多维数组名也可以作为函数的实参,在定义形参数组时可以指定每一维的长度,但可以不指定第一维的长度。
【例7.9】
编写程序,将一个3行4列的矩阵的每一列的最小值存入另一个一维数
组中。
#include<stdio.h>
voidmin(inta[][4],intb[])
{
inti,j,k;
for(j=0;j<4;j++){
k=a[0][j];
for(i=1;i<3;i++)
if(k>a[i][j])
k=a[i][j];
b[j]=k;
}
}
voidprint_a2(inta[][4],intm)
{
inti,j;
for(i=0;i<m;i++){
for(j=0;j<4;j++)
printf("%6d",a[i][j]);
printf("\n");
}
}
voidprint_a1(inta[],intm)
{
inti;
for(i=0;i<m;i++)
printf("%6d",a[i]);
printf("\n");
}
voidmain()
{
inta[3][4]={73,87,13,68,90,23,65,45,46,76,93,80},b[4];
printf("Arrayis:\n");
print_a2(a,3);
min(a,b);
printf("Minis:\n");
print_a1(b,4);
}程序运行结果:
Arrayis:
73871368
90236545
46769380
Minis:
46231345
7.6.1函数的嵌套调用
在C语言中,虽然函数之间的关系是并列的,在函数定义时不能嵌套定义,但是允许函数嵌套调用,例如函数A调用函数B,函数B又调用函数C。
7.6函数的嵌套调用和递归调用
【例7.10】
函数的嵌套调用。
#include<stdio.h>
voidprint();/*函数声明*/
voidprnline();/*函数声明*/
voidmain()
{
inti,j;
putchar('\n');
for(i=0;i<2;i++){
for(j=0;j<3;j++)
print();/*函数调用*/
putchar('\n');
}
}
voidprint()/*函数定义*/
{
putchar('*');
prnline();/*函数调用*/
}
voidprnline()/*函数定义*/
{
putchar('_');
}
程序运行结果:
*_*_*_
*_*_*_
以上程序中的函数调用关系为:main()函数在执行过程中调用print()函数,print()函数在执行过程中又调用prnline()函数,形成了函数之间的嵌套调用。7.6.2函数的递归调用
函数的递归调用是指在函数调用的过程中,函数直接或间接地调用了函数自身。含有直接或间接调用自身的函数称为递归函数。C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。一个问题要采用递归方法来解决时,必须符合以下三个条件:
(1)要解决的问题可以转化为一个新的问题,而这个新的问题的解法仍与原来的解法相同,只是所处理的对象有规律地递减。
(2)可以应用这个转化过程使问题得到解决。
(3)必定要有一个结束递归的条件。
【例7.11】
用递归方法求。
问题分析:
求可以用以下数学关系表示:
从以上表达式可以看到,当n>0时,求n!
的问题可以转化为求n(n-1)!
的新问题,而求(n-1)!
的解法与原来求n!
的解法相同,只是运算对象由n变成了n-1,因此求
(n-1)!
的问题又可以转化为求
(n-1)(n-2)!
的新问题,……,每次转化为新问题时,运算对象就递减1,直到运算对象的值减至0时,阶乘的值为1,递归不再进行下去,这就是求n!
这个递归算法的结束条件。程序如下:
#include<stdio.h>
intfac(intn)
{
intt;
if(n==0)
return(1);
else{
t=n*fac(n-1);
return(t);
}
}
voidmain()
{
intn,y;
printf("Inputn:");
scanf("%d",&n);
if(n<0)
printf("Dataerror!\n");
else{
y=fac(n);
printf("%d!=%d\n",n,y);
}
}
程序运行结果:
Inputn:5↙
5!=120
【例7.12】
用递归方法求Fibonacci数列。
Fibonacci数列的递推公式如下:
程序如下:
#include<stdio.h>
longfibonacci(intn)
{
longt;
if(n==1||n==2)
t=1;
else
t=fibonacci(n-1)+fibonacci(n-2);
return(t);
}
voidmain()
{
intn;
longy;
printf("Inputn:");
scanf("%d",&n);
y=fibonacci(n);
printf("Fibonacci(%d)=%ld\n",n,y);
}程序运行结果:
Inputn:10↙
Fibonacci(10)=55
【例7.13】
Hanoi塔问题。
这是一个古典的数学问题。问题是这样的:
古代有一个梵塔,塔内有A、B、C3个座,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个老和尚想把这64个盘子从A座移到C座,要求是每次只允许移动一个盘子,可以借助B座,且在移动的过程中都要保证三个座上的盘子大的在下,小的在上。编写程序显示移动的步骤。问题分析:
将n阶问题转化成n-1阶问题:
第一步,把A座上的n-1个盘子借助C座移到B座上;
第二步,把A座上剩下的一个盘子移到C座上;
第三步,把B座上的n-1个盘子借助A座移到C座。
其中,第一步和第三步的方法是相同的。
递归出口:n=1,此时A座上只有一个盘子,直接将其移到C座上即可。程序如下:
#include<stdio.h>
voidmove(charx,chary)
{
printf("%c-->%c\n",x,y);
}
voidhanoi(intn,chara,charb,charc)
{
if(n==1)
move(a,c);
else{
hanoi(n-1,a,c,b);
move(a,c);
hanoi(n-1,b,a,c);
}
}
voidmain()
{
intn;
printf("Inputn:");
scanf("%d",&n);
hanoi(n,'A','B','C');
}
程序运行结果:
Inputn:3↙
A-->C
A-->B
C-->B
A-->C
B-->A
B-->C
A-->C
递归作为一种算法在程序设计语言中广泛应用,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。递归的缺点:
递归算法解题的运行效率较低。在递归调用的过程中,系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
变量是程序运行过程中值可以改变的量。编译系统为变量分配内存单元,用来存放程序运行过程中的输入数据、中间结果和最终结果等。
变量的一个特性是变量的数据类型,它用来说明该变量在内存中所占的字节数,以及变量的运算规则。变量的另一个特性就是属性,变量的属性包含两个方面,即变量的作用域和变量的存储类别。7.7内部变量和外部变量变量的作用域是指变量的合法使用范围。变量的作用域与定义变量的位置密切相关,变量只能在它的作用域内使用。
变量的存储类别是指变量在内存中的存储位置。变量的存储类别决定着变量的生存期,变量的生存期是指变量在内存或寄存器中存在的时间段。
变量从作用域来区分可分为内部变量和外部变量。7.7.1内部变量
在函数内部或复合语句内部定义的变量称为内部变量。
内部变量的作用域是指从定义该变量的位置开始到它所在函数或复合语句结束。在它的作用域外,内部变量是不可见的。换言之,函数或复合语句内定义的内部变量是不能被其它的函数或复合语句所引用的。
内部变量的生存期是指定义该变量到函数结束或复合语句结束这段时间。
内部变量包含自动类型内部变量、寄存器类型内部变量和静态类型内部变量。另外,函数的形参也属于内部变量,因为函数形参的作用域只在该函数内。使用内部变量有助于实现信息隐藏,即使不同的函数定义了同名的内部变量,也不会相互影响。
例如:
intf1(inta)
{
intb,c;
...
}
intf2(floatx,floaty)
{
floatz;
...
}
voidmain()
{
charc1,c2;
...
}
在函数f1()内定义了三个内部变量a、b、c,其中a为形参,在函数f1()的范围内a、b、c有效,也即a、b、c的作用域仅限于函数f1()内。同理,x、y、z的作用域仅限于函数f2()内,c1、c2的作用域仅限于主函数main()内。说明:
(1)主函数中定义的内部变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的内部变量,因为主函数也是一个函数。它和其它函数是平等关系。
(2)形参变量属于被调函数的内部变量,实参变量属于主调函数的内部变量。允许在不同的函数中使用相同的内部变量名,它们代表不同的对象,分配不同的存储单元,互不干扰,也不会发生混淆。7.7.2外部变量
在函数外任意位置定义的变量称为外部变量。
外部变量的作用域是指从定义它的位置开始,直至它所在的源程序文件结束。对于不在作用范围的程序段,可以通过在段内对外部变量进行声明的方式来拓展外部变量的作用域。外部变量包含非静态类型外部变量和静态类型外部变量。例如:
在同一个源程序文件中,如果外部变量与内部变量同名,则在内部变量的作用域内,外部变量将被屏蔽,即不起作用。
外部变量的使用增加了函数之间传送数据的途径。在外部变量的作用域内,任何函数都可以引用该外部变量。一个函数对外部变量的修改,能影响到其它引用该外部变量的函数,因此,外部变量使用不当,会产生意外的错误。外部变量的使用也会使得函数的通用性降低,从结构化程序设计的角度看,函数应视为完成单一功能的程序段,过多使用外部变量,会使函数之间的依赖性增加,增加函数的耦合性。一般情况下,除非性能的特别要求,建议尽量避免使用外部变量。
【例7.14】外部变量与内部变量同名。
#include<stdio.h>
inta=3,b=5;/*a、b是外部变量*/
intf()
{
printf("f:a=%d,b=%d\n",a,b);/*输出外部变量a、b的值*/
}
voidmain()
{
inta=12,b=25;/*a、b是内部变量*/
printf("main:a=%d,b=%d\n",a,b);/*输出内部变量a、b的值*/
f();
}
程序运行结果:
main:a=12,b=25
f:a=3,b=5
7.8.1变量的存储类别概述
C程序运行时占用的内存空间分为三部分,如图7-2所示。程序运行期间的数据分别存放在静态存储区和动态存储区。静态存储区用来存放程序运行期间所需占用固定存储单元的变量,如外部变量和静态类型的内部变量。动态存储区用来存放不需要长期占用内存单元的变量,如函数的形参和函数调用时的返回值等。7.8变量的存储类别
图7-2C程序运行时占用的内存空间变量的存储类别分为两类:动态存储类别和静态存储类别。
对于动态存储类别的变量,当程序运行进入定义它的函数或复合语句时才被分配存储空间,当程序运行结束离开此函数或复合语句时,所占用的内存空间被释放。动态存储类别是一种节省内存空间的存储方式,它是在需要时分配内存空间,不需要时释放内存空间的变量。动态存储类别的变量又分为两种:自动类型变量和寄存器类型变量。自动类型变量被存放于内存的动态存储区,用auto说明符来定义;寄存器类型变量被存放于寄存器中,用register说明符来定义。对于静态存储类别的变量,在程序运行整个过程中,始终占用固定的内存空间,直至程序运行结束,才释放占用的内存空间。静态存储类别的变量被存放于内存空间的静态存储区,可用static和extern来定义和声明变量。
变量的存储类别决定着变量的生存期。变量的作用域和生存期是两个不同的概念。作用域指的是可见性,生存期指的是存在性。可见是指变量在它的作用域内有效,可对其进行存取、引用等;不可见是指变量在它的作用域外无效,不能对其实施有效操作。存在是指变量占用内存;不存在是指变量所占用内存已经被释放。7.8.2内部变量的存储类别
内部变量的作用域是指定义该变量的函数或复合语句的范围。内部变量的存储类别是指它在内存中的存储方式。内部变量可存放于内存的动态区、内存的静态区或寄存器。但无论内部变量存放于何处,其作用域都是不变的。
1.自动存储类型内部变量
自动存储类型内部变量的存储单元被分配在内存的动态存储区。自动存储类型变量的定义形式为
auto类型变量名
在函数内部,自动存储类型变量是系统默认的变量类型,关键字“auto”可以省略,因此,以下两种定义变量的方式是等价的:
inta;
autointa;
函数内部不做特别说明定义的变量、函数的形参都是自动存储类型内部变量,它们都是在进入函数或复合语句时被分配内存单元的,在该函数或复合语句运行期间一直存在,在函数或复合语句运行结束时自动释放这些内存单元。自动存储类型内部变量的作用域和生存期是一致的,在它的生存期内都是有效的、可见的。函数内部的自动存储类型内部变量在每次函数调用时,系统都会在内存的动态存储区为它们重新分配内存单元,随着函数的频繁调用,某个变量的存储位置随着程序的运行是不断变化的,所以未赋初值的自动存储类型内部变量的值是不确定的。
2.寄存器存储类型内部变量
寄存器存储类型内部变量的存储单元被分配在寄存器中。其定义形式为
register类型变量名;
如:
registerintj;
寄存器存储类型内部变量的作用域、生存期与自动存类型内部变量相同。因为寄存器的存取速度比内存快得多,通常将频繁使用变量放在寄存器中(如循环体中涉及的内部变量),以提高程序的执行速度。
计算机中寄存器的个数是有限的,寄存器的数据位数也是有限的,所以定义寄存器存储类型内部变量的个数不能太多,并且只有整型变量和字符型变量可以定义为寄存器存储类型内部变量。
寄存器存储类型内部变量的定义通常是不必要的,现在优化的编译系统能够识别频繁使用的变量,并能够在不需要编程人员作出寄存器存储类型定义的情况下,就把这些变量存放在寄存器中。
3.静态存储类型内部变量
静态存储类型内部变量的存储单元被分配在内存空间的静态存储区中。静态存储类型内部变量的定义形式为
static类型变量名
静态存储类型内部变量在编译的时候被分配内存、赋初值,并且只被赋初值一次,对未赋初值的静态存储类型内部变量,系统自动给它赋初值0(或
'\0')。在整个程序运行期间,静态存储类型内部变量在内存的静态存储区占用固定的内存单元,即使它所在的函数调用结束,也不释放存储单元,其值也会继续保留,下次再调用该函数时,静态存储类型变量继续使用原来的存储单元,仍使用原来存储单元中的值。可以利用静态存储类型变量的这个特点,编写需要在被调用结束后仍保存内部变量值的函数。
静态存储类型变量的作用域仍然是定义该变量的函数或复合语句内部。虽然静态存储类型变量在整个程序运行期间都是存在的,但在它的作用域外,它是不可见的,即不能被其它函数引用。
【例7.15】
静态存储类型变量举例。
#include<stdio.h>
intsum(intx)
{
staticints=0;/*定义静态存储类型内部变量*/
s=s+x;
return(s);
}
main()
{
inti,k;
for(i=1;i<=10;i++)
k=sum(i);
printf("\n0+1+...+%2d=%2d\n",i-1,k);
}
程序运行结果:
1+...+10=55
说明:程序从main()函数开始运行,此时,函数sum()内的静态存储类型内部变量s在静态存储区已被分配存储单元并初始化为0。main()函数调用函数sum()10次,第一次为“k=sum(1);”,s是静态存储类型内部变量,初始化为0,执行“s=s+x;”,s内保存的值是1(0+1);第二次为“k=sum(2);”,s不再初始化,执行“s=s+x;”,s内保存的是3(1+2);第三次为“k=sum(3);”,执行“s=s+x;”,s内保存的是6(3+3);……;第十次为“k=sum(10);”,执行“s=s+x;”,s内保存的是55(45+10)。7.8.3外部变量的存储类别
外部变量只能存放在内存的静态存储区。外部变量的生存期是整个程序的运行期。外部变量的作用域局限于定义它的程序文件,经过声明后,则可被程序的其它程序文件中的函数所引用。
外部变量分为外部存储类型外部变量和静态存储类型外部变量。
1.外部存储类型外部变量
外部存储类型外部变量是在所有函数外定义的外部变量,其定义形式为
类型变量名
外部存储类型外部变量的作用域是整个程序,只需加上声明,就可以被所在程序的其它程序文件所引用。
外部存储类型外部变量也称为程序级外部变量。
2.静态存储类型外部变量
静态存储类型外部变量也是在所有函数外定义的外部变量,其定义形式为
static类型变量名
静态存储类型外部变量的作用域是其所在的程序文件。虽然它在程序运行期间一直存在,但它不能被其它程序文件引用。静态外部变量能够限制它的作用域的扩展,达到信息隐蔽的目的。
静态存储类型外部变量也称为文件级外部变量。
3.外部变量的声明
外部变量的定义是在所有函数的外部,它的使用却是在函数的内部。如果外部变量不在程序文件的开头定义,则其作用域只限于从定义处到程序文件的结束。如果在定义处之前或其它程序文件中引用(外部存储类型外部变量),则应该在引用之前对其进行声明,表示该变量是一个已经定义的外部变量。外部变量一经声明,就可以从声明处起,合法地引用该外部变量。外部变量的声明形式为
extern类型变量名;
在外部变量的作用域内(即从定义处到程序文件结束)可以省略对外部变量的声明。
【例7.16】
外部变量声明举例。
/*file1.c*/
intx=0;
main()
{
…
}
/*file2.c*/
externintx;/*外部变量声明*/
intfunc()
{
…
}
说明:该程序由两个程序文件file1.c和file2.c组成。在file1.c中定义了外部整型变量x,在file2.c中对file1.c中定义的外部整型变量x进行了声明,这样file2.c中的函数就可以引用file1.c中定义的外部整型变量x了。
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024至2030年中国粒芯梅菜数据监测研究报告
- 2024至2030年中国矩型波纹补偿器行业投资前景及策略咨询研究报告
- 2024至2030年中国特殊油封数据监测研究报告
- 2024至2030年中国开关接插键数据监测研究报告
- 2024至2030年中国塑料断路器行业投资前景及策略咨询研究报告
- 《恶意代码基础与防范(微课版)》 课件 第8章 勒索型恶意代码
- 浙江省宁波市镇海区部分学校2024-2025学年二年级上学期期中语文试卷
- 医改护理培训
- 产品工厂直供合同范例
- 木制钓鱼艇转让合同模板
- 2024年6月2日《证券投资顾问》真题卷(79题)
- 金融知识进万家
- 招商专员培训资料
- 2025年中考语文复习之文言文阅读
- 福建省厦门市2024-2025学年新人教版九年级语文上学期期末质量检测试题
- 冬季道路行车安全
- 2024统编版(2024)道德与法治小学一年级上册教学设计(附目录)
- 2024版《中医基础理论经络》课件完整版
- 2024年全球 二次元移动游戏市场研究报告-点点数据
- 医师定期考核人文医学考试题库500题(含参考答案)
- 2024年秋季新统编版七年级上册道德与法治全册教案
评论
0/150
提交评论