第九章 函数.docx
《第九章 函数.docx》由会员分享,可在线阅读,更多相关《第九章 函数.docx(22页珍藏版)》请在冰豆网上搜索。
第九章函数
第九章函数
本章介绍C语言中程序、文件、函数的区别和联系,大型程序的管理和装配;库函数的使用,用户函数的定义、调用方法、函数的声明;函数原型的概念。
变量的存储属性,变量的生存期和作用域,内部函数和外部函数,函数的嵌套调用和递归调用。
最后是相关程序举例。
上一页 下一页
9.1C程序的模块化结构
9.1.1概述
一个用C语言开发的软件往往由许多功能组成,包含的程序语句很多,从组成上看,各个功能模块彼此有一定的联系,功能上各自独立,从开发过程上看,可能不同的模块由不同的程序员开发,怎样将不同的功能模块连接在一起,成为一个程序,怎样保证不同的开发者的工作既不重复,又能彼此衔接,这就需要模块化设计。
支持这种设计方法的语言称为模块化程序设计语言,C语言提供了模块化设计的功能。
所谓模块化设计是将一个大的程序自上向下进行功能分解,分成若干个子模块,模块对应了一个功能,有自己的界面,有相关的操作,完成独立的功能。
各个模块可以分别由不同的人员编写和调试,最后,将不同的模块组装成一个完整的程序。
在C语言中,用函数实现功能模块的定义,C的程序的功能可以通过函数之间的调用实现,一个完整的C程序可以由多个源程序文件组成,一个文件中可以包含多个函数。
图9-1给出了一般的C语言程序的结构。
C语言是一种现代程序设计语言,它具有以下特点:
(1)C语言允许将一个程序写入不同的源文件,每一个源文件可以独立编译,一个源文件可以被不同的程序使用。
(2)一个源文件由多个函数组成,函数是最小的功能单位,一个函数可以被不同的源文件的其他函数调用。
(3)一个C程序有且仅有一个主函数main(),主函数可以放在任何一个源文件中,程序的执行从主函数开始,主函数是系统定义的。
(4)不同源文件的组装可以通过工程文件实现。
上一页 下一页
9.2函数的定义
9.2.1函数定义的一般形式
函数的定义通常包含以下内容:
函数返回值类型函数名(形参表说明)/*函数首部*/
{说明语句/*函数体*/
执行语句
}
说明:
(1)数的定义中的类型,是指函数返回值的类型。
函数返回值不能是数组,也不能是函数,除此之外任何合法的数据类型都可以是函数的类型,如:
int、long、float、char等,或是后面讲到的指针、结构等。
函数的类型是可以省略的,当不指明函数类型时,系统默认的是整类型。
(2)函数名是用户自定义的标识符,是C语言函数定义中唯一不可省略的部分,需符合C语言对标识符的规定,即由字母,数字或下划线组成,用于标识函数,并用该标识符调用函数。
另外,函数名本身也有值,它代表了该函数的入口地址,使用指针调用该函数时,将用到此功能。
(3)形参也成为"形式参数"。
形参表是用逗号分隔的一组变量说明,包括形参的类型和形参标识符,其作用是指出每一个形参的类型和形参的名称,当调用函数时,接受来自主调函数的数据,确定各参数的值。
形参表说明可以有两种表示形式:
intfunc(intx,inty)
{……}
或:
intfunc(x,y)
intx,y;
{……}
通常,调用函数需要多个原始数据,就必须定义多个形式参数。
注意,在")"后面不能加分号";"。
(4)用{}括起来的部分是函数的主体,称为函数体。
函数体是一段程序,确定该函数应完成的规定的运算,应执行的规定的动作,集中体现了函数的功能。
函数内部应有自己的说明语句和执行语句,但函数内定义的变量不可以与形参同名。
花括号{}是不可省略的。
根据函数定义的一般形式,我们可以得到一个C语言中最简单的函数:
dumy()
{}
这是C语言中一个合法的函数,函数名为dumy。
它没有函数类型说明,也没有形参表,同时函数体内也没有语句。
实际上函数dumy不执行任何操作和运算,在一般情况下是没有用途的,但在程序开发的过程中有时是需要的,常用来代替尚未开发完毕的函数。
上一页 下一页
9.3数据在函数之间的传递
在程序中,如果仅仅用函数代替一个语句序列,那么函数的作用就不大了。
一般情况下,常常要求同一个函数可以根据不同的数据,进行相同的处理之后得到不同的结果,这样,函数与函数之间通常要传递数据和计算结果。
C语言中采用参数、返回值和全局变量三种方式进行数据传递。
主调函数与被调函数之间是双向传递数据,当调用函数时,通过函数的参数,主调函数为形参提供数据,调用结束时,被调函数通过返回语句将函数的运行结果(称为返回值)带回主调函数中,函数之间还可以通过使用全局变量,在一个函数内使用其他函数中的某些变量的结果,本节仅讨论函数的参数传递和函数的返回值,全局变量将在后续内容中介绍。
9.3.1形参与实参
形参是函数定义时由用户定义的形式上的变量,实参是函数调用时,主调函数为被调函数提供的原始数据。
在C语言中,实参向形参传送数据的方式是"值传递"。
C语言函数参数采用"值传递"的方法,其含义是:
在调用函数时,将实参变量的值取出来,复制给形参变量,使形参变量在数值上与实参变量相等。
在函数内部使用从实参中复制来的值进行处理。
C语言中的实参可以是一个表达式,调用时先计算表达式的值,再将结果(值)复制到形参对应的存储单元中,一旦函数执行完毕,这些存储单元所保存的值不再保留。
形式参数是函数的局部变量,仅在函数内部才有意义,不能用它来传递函数的结果。
函数间形参变量与实参变量的值的传递过程类似于日常生活中的"复印"操作:
甲方请乙方工作,拿着原件为乙方复印了一份复印件,乙方凭复印件工作,将结果汇报给甲方。
在乙方工作过程中可能在复印件上进行涂改、增删、加注释等操作,但乙方对复印件的任何修改都不会影响到甲方的的原件。
值传递的优点在于被调用的函数不可能改变主调函数中变量的值,而只能改变它的局部的临时副本。
这样就可以避免被调用函数的操作对调用函数中的变量可能产生的副作用。
C语言中,在"值传递"方式下,我们既可以在函数之间传递"变量的值",也可以在函数之间传递"变量的地址"。
例9-3:
编写一个函数求两个的最大值。
main()
{inta,b,c;
scanf("%d%d",&a,&b);
c=max(a,b);/*主函数内调用功能函数max,实参为a和b*/
printf("%d,%d,%d\n",a,b,c);
}
intmax(intx,inty)/*x和y为形参,接受来自主调函数的原始数据*/
{intz;
z=x>y?
x:
y;
return(z);/*将函数的结果返回主调函数*/
}
我们从以下几点总结函数参数:
(1)形参在被调函数中定义,实参在主调函数中定义。
(2)形参是形式上的,定义时编译系统并不为其分配存储空间,也无初值,只有在函数调用时,临时分配存储空间,接受来自实参的值,函数调用结束,内存空间释放,值消失。
(3)实参可以是变量名或表达式,但必须在函数调用之间有确定的值。
main()
{inta,b,c,d;
scanf("%d,%d,%d,%d",&a,&b,&c,&d);
x=max(a+b,c+d);/*表达式做实参*/
……}
(4)实参与形参之间是单向的值传递,即实参的值传给形参,因此,实参与形参必须类型相同,个数相等,一一对应。
例9-4:
分析下列程序的执行过程。
main()
{intx=2,y=3,z=0;
printf("*x=%d,y=%d,z=%d\n",x,y,z);
try(x,y,z);/*函数调用x、y、z为实参*/
printf("****x=%d,y=%d,z=%d\n",x,y,z);
}
try(intx,inty,intz)
{printf("**x=%d,y=%d,z=%d\n",x,y,z);
z=x+y;
x=x*x;
y=y*y;
printf("***x=%d,y=%d,z=%d\n",x,y,z);
}
运行结果:
*x=2,y=3,z=0
**x=2,y=3,z=0
***x=4,y=9,z=5
****x=2,y=3,z=0
程序中变量的传递过程如图9-2所示。
程序的运行结果表明,当调用函数时,实参的值传给形参,在被调函数内部,形参的变化不会影响实参的值。
(5)当实参之间有联系时,实参的求值顺序在不同的编译系统下是不同的,TurboC是从右向左。
例:
func3(intx,inty)
调用此函数时,若func3(k,++k),当k的初值为3,调用时,实参的求值顺序是从右向左,因此调用形式实质上是:
func(4,4)。
类似的,对库函数 printf("%d,%d\n",k,++k);也是如此,当k=3时,其结果为4,4,而不是3,4。
例9-5:
若在主函数中变量a=5,b=10,编写一个函数交换主函数中两个变量的值,使变量a=10,b=5。
main()
{inta,b;
a=5;b=10;/*说明两个变量并赋初值*/
printf("beforeswapa=%d,b=%d\n",a,b);
swap(a,b);/*用变量a和b作为实际参数调用函数*/
printf("afterswapa=%d,b=%d\n",a,b);
}
swap(x,y)
intx,y;
{inttemp;
/*借助临时变量交换两个形参变量x和y的值*/
temp=x;/*①*/
x=y;/*②*/
y=temp;/*③*/
printf("inswapx=%d,y=%d\n",x,y);
}
程序中调用swap时,将需要交换的两个变量a和b作为实参,目的是通过调用函数swap后使变量a和b的数值得到交换。
在调用swap之前其各个变量的状态和相互关系可用图9-3描述。
在程序中调用swap时,实参变量a和b的值传递给了形参变量x和y,并且在函数的内部完成了x和y值的交换,但是x和y与a和b各自使用自己的内存区域,他们之间仅仅在参数传递时进行了数值的传递,所以变量x和y的变化并不影响变量a和b。
在这个过程中各个变量的变化和相互关系可用图9-4描述。
程序的实际运行结果为:
beforeswapa=5,b=10
inswapx=10,y=5
afterswapa=5,b=10
这个看似十分正确的程序根本无法满足要求,这是由函数间参数的传递方式决定的。
以上程序没有完成预期的任务,如何实现题目的要求,请参见第10章中相关的内容。
在值传递方式下,每个形式参数仅能传递一个数据,当需要在函数之间传递大量数据时,值传递方式显然不适用,应采用另一种地址传递方式,有关内容将在以后介绍。
上一页 下一页
9.4函数的调用
主调函数使用被调函数的功能,称为对被调函数的调用。
函数调用的基本形式是通过函数名和函数的参数。
9.4.1函数的调用方式
按照函数在主调函数中的作用,函数的调用方式可以有以下三种形式:
1、函数语句
被调函数在主调函数中,以语句的方式出现。
通常只完成一种操作,不带回返回值。
例9-10:
分析程序的执行过程。
#include
func5()
{printf("Thisisaprogramm!
\n");
}
main()
{func5();
}
2、函数表达式
将函数的调用结果作运算符的运算分量,这种函数是有返回值的。
例9-11:
库函数pow(a,b)的功能是求ab,在主函数中调用该函数的程序为:
#include
main()
{inta,b,i,j,c;
c=pow(a,i)+pow(b,j);
}
将函数pow(a,i)和pow(b,j)作为"+"运算符的运算分量。
3、函数参数
函数的调用结果进一步做其他函数的实参,这种函数也是有返回值的。
上例中,若c=pow(a,pow(b,i));
此时,pow(b,i)作为下一次调用pow函数的实参。
比如:
max(x,y)
d=max(c,max(a,b);
上一页 下一页
9.5数组作函数的参数
数组是相同数据类型的有限个数据元素的集合,数组可以作函数的参数的方式可以有两种,一种是数组中的元素作函数的参数,另一种是数组名作函数的参数。
9.5.1数组元素作函数的参数
数组定义、赋值之后,数组中的元素可以逐一使用,与普通变量相同。
由于形参是在函数定义时定义,并无具体的值,因此数组元素只能在函数调用时,作函数的实参。
例9-14:
分析程序的执行过程。
#include
main()
{inta[10],b,i;/*在主调函数内定义数组*/
for(i=0;i<10;i++)
scanf("%d",&a[i]);/*为数组元素赋值*/
b=0;
for(i=0;i<10;i++)
b=max(b,a[i]);/*循环调用函数,依次用数组中的每一个元素做实参*/
printf("%d",b);
}
当用数组中的元素作函数的实参时,必须在主调函数内定义数组,并使之有值,这是,实参与形参之间仍然是"值传递"的方式,函数调用之前,数组已有初值,调用函数时,将该数组元素的值,传递给对应的形参,两者的类型应当相同。
9.5.2数组名做函数的参数
数组名作函数的参数,必须遵循以下原则:
(1)如果形参是数组形式,则实参必须是实际的数组名,如果实参是数组名,则形参可以是同样维数的数组名或指针。
(2)要在主调函数和被调函数中分别定义数组。
(3)实参数组和形参数组必须类型相同,形参数组可以不指明长度。
(4)在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址,因此,当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递",实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元,编译系统不再为形参数组分配存储单元。
例9-15:
分析程序的执行过程。
#include
func6(charstr[])
{printf("%s",str);
}
main()
{chara[10]="TurboC";
func6(a);/*数组名做函数的实参*/
}
内存空间存储状态如图9-5所示。
调用时,实参数组将首地址a赋值给形参数组str,两个数组共同占用相同的内存单元,共享数组中的数据,a[0]与str[0]代表同一个元素,a[1]与str[1]代表同一个元素。
因此,当数组名做函数参数时,形参数组的长度与实参数组的长度可以不相同,当形参数组长度小与实参数组长度时,形参数组只取部分实参数组中的数据,实参中的其余部分可以不起作用,形参数组也可以不指明长度。
9.5.3多维数组做函数的参数
当多维数组中元素做函数参数时,与一维数组元素做函数实参是相同的,这里讨论多维数组名做函数的参数。
以二维数组为例:
二维数组名做函数参数时,形参的语法形式是:
类型说明符形参名[][常量表达式M]
形参数组可以省略一维的长度。
例如:
intarray[][10]
由于实参代表了数组名,是"地址传递",二维数组在内存中是按行优先存储,并不真正区分行与列,在形参中,就必须指明列的个数,才能保证实参数组与形参数组中的数据一一对应,因此,形参数组中第二维的长度是不能省略的。
调用函数时,与形参数组相对应的实参数组必须也是一个二维数组,而且它的第二维的长度与形参数组的第二维的长度必须相等。
上一页 下一页
9.6函数的嵌套调用
在C语言中,函数的定义是平行的,不允许进行函数的嵌套定义,即在一个函数体中再定义一个新的函数。
而函数之间的调用可以是任意的,即允许在一个函数体内再调用其他函数,这种在函数体中再调用其它函数称为函数的嵌套调用。
在C语言中,函数的嵌套调用是很常见的,下面给出一个函数嵌套调用的例子。
例9-16:
分析程序的执行过程。
#include
voidfunc7()
voidfunc8()
voidfunc9()
main()
{printf("Iaminmain\n");
func7();
printf("Iamfinallybackinmain\n");
}
voidfunc7()
{printf("Iaminthefirstfunction\n");
func8();
printf("Iaminbackinthefirstfunction\n");
}
voidfunc8()
{printf("NowIaminthesectionfunction\n");
func9();
printf("NowIambackinthesectionfunction\n");
}
voidfunc9()
{printf("NowIaminthethirdfunction\n");
}
程序的运行结果如下:
Iamfinallybackinmain
Iaminthefirstfunction
NowIaminthesectionfunction
NowIaminthethirdfunction
NowIambackinthesectionfunction
Iaminbackinthefirstfunction
Iamfinallybackinmain
在这个简单的例子中,main函数首先调用了func7,函数func7中又调用了函数func8,函数func8中又调用了函数func9。
整个程序中函数的嵌套调用过程如图9-6所示。
上一页 下一页
9.7变量的存储属性
在C语言中,变量是对程序中数据所占用内存空间的一种抽象,定义变量时,用户定义变量的名,变量的类型,这是变量的操作属性。
不仅可以通过变量名访问该变量,系统还通过该标识符确定该变量在内存空间的位置。
在计算机中,保存变量当前值的存储单元有两类,一类是内存,另一类是CPU中的寄存器,变量的存储属性就是讨论变量的存储位置的,C语言中定义了四种存储属性,即自动变量、外部变量、静态变量和寄存器变量,它关系到变量在内存中的存放位置,由此决定了变量的值保留的时间和变量的作用范围,这就是生存期和作用域的概念。
9.7.1变量的生存期和作用域
1、变量的生存期
变量的生存期是指变量值保留的期限,可分为两种情况:
(1)静态存储:
变量存储在内存中的静态存储区,在编译时就分配了存储空间,在整个程序运行期间,该变量占有固定的存储单元,变量的值都始终存在,程序结束后,这部分空间才释放。
这类变量的生存期为整个程序。
(2)动态存储:
变量存储在内存中的动态存储区,在程序运行过程中,只有当变量所在函数被调用时,编译系统临时为该变量分配一段内存单元,该变量有值,函数调用结束,变量值消失,这部分空间释放。
我们说这类变量的生存期仅在函数调用期间。
2、变量的作用域
变量的作用域也称为可见性,指变量的有效范围,可分为局部与全局两种情况:
(1)局部变量:
在一个函数或复合语句内定义的变量,称为局部变量,局部变量仅在定义它的函数或复合语句内有效。
例如函数的形参是局部变量。
编译时,编译系统不为局部变量分配内存单元,而是在程序的运行中,当局部变量所在的函数被调用时,编译系统根据需要临时分配内存,调用结束,空间释放。
例9-17:
分析程序的运行结果。
func11()
{intx=3;
{intx=2;/*第一个复合语句中的局部变量*/
{intx=1;/*第二个复合语句中的局部变量*/
printf("*x=%d\n",x);
}
printf("**x=%d\n",x);
}
printf("***x=%d\n",x);
}
main()
{intx=10;
printf("1:
x=%d\n",x);
func11();
printf("2:
x=%d\n",x);
}
程序的运行结果是:
1:
x=10/*输出主函数内的变量x*/
*x=1/*输出第一个复合语句中的变量x*/
**x=2/*输出第二个复合语句中的变量x*/
***x=3/*输出函数func11中的变量x*/
2:
x=10/*输出主函数内的变量x*/
(2)全局变量:
变量在所有函数之外定义称为全局变量,其作用范围为从定义开始,到本文件结束。
全程变量一经定义,编译系统为其分配固定的内存单元,在程序运行的自始至终都占用固定单元。
如果在定义之前使用该全局变量,用extern加以说明,则可扩展全局变量的作用域。
(3)使用全局变量与局部变量,应注意以下几点:
①不同函数内的局部变量可以重名,互不影响。
②全局变量与局部变量可以同名,在局部变量起作用的范围内,全局变量不起作用。
③全局变量的初始化只能有一次,是在对全局变量说明的时候。
例9-18:
分析以下程序的运行结果。
intx=100;/*全局变量的定义*/
func12()
{intx=10;/*局部变量,函数内有效*/
f();
ff();
printf("%d\n",x);
}
f()
{intx=500;/*局部变量,函数内有效*/
x+=100;
printf("%d\n",x);
}
ff()
{x+=100;/*使用全局变量*/
printf("%d\n",x);
}
程序运行结果:
600/*调用函数f()的结果*/
2