C语言程序设计课件:函数_第1页
C语言程序设计课件:函数_第2页
C语言程序设计课件:函数_第3页
C语言程序设计课件:函数_第4页
C语言程序设计课件:函数_第5页
已阅读5页,还剩96页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

数5.1函数的定义、函数参数和函数值5.2函数的调用5.3函数的嵌套调用与递归调用5.4函数应用举例5.5变量的作用域和生存期单元小结5.1函数的定义、函数参数和函数值

5.1.1C语言对函数的规定

C语言对函数做出如下规定:

(1)一个C语言程序由一个或多个源程序文件组成。

(2)一个源程序文件由一个或多个函数组成。

(3) C语言程序的执行从main函数开始,到main函数结束。

(4)所有函数都是平行的,函数不能嵌套定义。

(5)函数按使用角度分为标准函数和自定义函数。

(6)函数按形式分为无参函数和有参函数。5.1.2函数的定义

1.函数定义的一般形式

类型说明符函数名(形参类型形参名,……,形参类型形参名)

声明部分

语句

[return语句]

函数由函数首部和函数体两部分组成。

(1)函数首部主要由三部分构成:

①类型说明符:函数的类型。

②函数名:命名遵循标识符命名规则。

③形参:在函数定义中,括号中的参数称之为形参,定义时应分别指明类型,形参与形参之间用逗号隔开。

(2)函数体主要由三部分构成:

①声明部分:定义若干变量,用以帮助形参实现该函数的功能。

②若干语句:实现该函数的功能。

return语句:带回一个返回值,返回值的类型应与函数类型一致。

2.函数定义的一些表现形式

(1)无参函数的定义形式。

类型说明符函数名()

声明部分

语句

[return语句]

由于是无参函数,函数首部括号内是空的,说明没有任何参数。

(2)有参函数的定义形式。

类型说明符函数名(形参列表)

{声明部分

语句

[return语句]

函数首部括号内是形参,可以有一个,也可以有多个。(3)空函数的定义形式。

类型说明符函数名()

函数首部括号内是空的,函数体也是空的。此函数没有任何实际功能,只是为将来扩充新功能提供方便。

说明:

(1)函数类型原则上要求与return后表达式类型一致,如不一致,表达式类型会强制转换成函数类型;如果缺省函数类型说明符,则系统一律认为函数的类型为整型;如果函数的确不需要返回值,则函数类型设为void型,如后面的printstar函数,函数首部可写为:

voidprintstar()

(2)return语句的作用是带返回值到函数调用处;将程序执行流程返回函数调用处。(如果该函数不需要返回值,那么可以省略return,函数体执行结束后也将程序执行流程返回调用处。)

(3)如何确定一个函数是否需要返回值,即是否需要return语句?首先要看函数功能,然后再看函数调用语句是否被主调函数所使用,如果函数调用语句是一个独立语句,就不需要返回值,函数类型为void型;如果函数调用语句在主调函数中是表达式或语句的一个成分,则需要返回值,返回值类型决定函数类型。

【例5-1】

求两个整数的最大公约数。

该函数的功能是求任意两个整数的最大公约数,定义两个形参x和y,均是整型,表示求x和y的最大公约数;求得的结果(最大公约数)也是整型,这是需要返回的结果,故函数类型为整型。函数名命名为gys,得到如下函数:

intgys(intx,inty)

{ intz; /*求得的最大公约数用z存放*/

实现函数功能的语句

return(z); /*return语句带回返回值*/

}

【例5-2】

判断某数是否为素数。

该函数的功能是判断任意一个整数是否为素数,定义一个形参n,类型为整型,表示判断n是否为素数;求得的结果用1表示“是”,用0表示“否”,也是整型,故函数类型为整型。函数名命名为prim,得到如下函数:

intprim(int)

{ intm;

实现判断素数功能,判断结果若是素数,m值为1;判断结果若不是素数,m值为0

return(m);

}

5.2函

5.2.1函数调用的一般形式

定义函数的目的在于通过调用函数实现其功能。函数调用的一般形式如下:

函数名(实际参数表)

函数名()

当实际参数(简称实参)多于一个时,各实参之间用逗号隔开。实参的个数和类型必须与所调用函数中的形参相同。对于没有形参的函数,采用无实参的调用形式。

一般情况下,函数调用有两种形式:

(1)调用的函数用于求出某一个值。这时,函数的调用可以作为表达式出现在允许表达式出现的地方。例如上节中例5-1的函数调用可以写成:k=gys(75,50)。

【例5-3】

读程序,理解有返回值的函数调用。图5-1例5-3程序运行结果

(2)调用的函数仅仅是完成某些操作而不返回函数值,这时函数的调用可以作为一条独立的语句。

【例5-4】

读程序,理解无返回值的函数调用。图5-2例5-4程序运行结果5.2.2函数的声明

在C语言中,除了main函数外,用户所定义的函数遵循“先定义、后使用”的规则。当把函数的定义放在调用之后,应该在调用之前对函数进行声明,即在所调用的函数之后定义,则在调用函数之前需要对被调函数进行声明。

函数声明的一般形式如下:

类型名函数名(参数类型1,参数类型2,…);

或者

类型名函数名(参数类型1参数名1,参数类型2参数名2,…);

这里的参数名可以是任意的标识符,即不必与函数首部中的形参相同,甚至可以省略。

例如,在例5-4中,如果function函数在main函数之后定义,则需在main函数之前加以说明,程序修改如下:#include<stdio.h>

voidfunction();

main()

{ function();

}

voidfunction()

{ printf("Hello,World!\n");

}

);

某些情况下也可省略对被调函数的声明:

(1)被调函数的定义在主调函数之前,即先定义后调用。

(2)被调函数的返回值类型是整型或字符型。5.2.3函数参数的传递方式

有参函数调用时,主调函数与被调函数之间存在着数据传递的关系,即主调函数调用被调函数时将实参的值传递给形参,而被调函数向主调函数传递数值则是利用return语句实现的。

在C语言中,函数的参数传递有两种方式:按值传递,按地址传递。

1.值传递

当形参定义为一般变量时,函数的参数传递就是按值传递方式,简称为值传递。它是指当一个函数被调用时,根据实参和形参的对应关系将实参的值一一传递给形参。

值传递是一种单向的数据传递,即实参向形参传递数据,但形参数据的改变不会影响到实参的值。【例5-5】

函数参数的值传递方式。

#include<stdio.h>

voidswap(inta,intb);

main()

{

intx=7,y=11;

printf("交换之前:");

printf("x=%d,y=%d\n",x,y);

swap(x,y);

printf("交换之后:");

printf("x=%d,y=%d\n",x,y);

}

voidswap(inta,intb)

{ inttemp;

temp=a;

a=b;

b=temp;

}

图5-3例5-5程序运行结果

该程序首先在main函数中定义了两个整型变量x和y,其初始值分别为7和11,然后调用swap自定义函数试图交换x、y的值,可结果是,调用函数swap以后,x和y的值并没有交换,x仍然是7,y仍然是11。为什么会出现这种情况呢?这是因为实参x和y对应的内存单元与形参a和b对应的内存单元是各不相同的,在函数调用时,将x的值7传递给形参a,y的值11传递给形参b,如图5-4所示。但在swap函数体内只是将a和b的值交换了,即a的值变为11,b的值变为7,当函数swap执行完毕返回时,a和b的内存单元就释放了,变量x和y所对应的内存单元并没有作任何的改变,如图5-5所示。

图5-4调用函数时将实参传给形参图5-5调用函数后,形参值不传给实参

2.地址传递

当形参定义为数组名或指针变量时,函数的参数传递是按地址传递方式,简称为地址传递。它是指当一个函数被调用时,把实参的值(地址值)传递给形参。

值传递与地址传递方式只是传递的数据类型不同,值传递方式传递一般的数值,而地址传递方式传递的是地址值。地址传递方式实际上是值传递方式的一个特例,本质还是传值,只是此时传递的是一个地址数据值。

地址传递方式形参与实参指向同样的内存单元,函数中对形参值的改变也会改变实参的值。因此,函数参数的地址传递方式可实现调用函数与被调函数之间的双向数据传递。注意,形参必须是指针(地址)变量,而实参可以是地址常量或指针(地址)变量。比较典型的地址传递方式就是用数组名作为函数的参数,在用数组名作函数的参数时,不是进行值的传递,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。由于数组名就是数组的首地址,因此在数组名作函数参数时所进行的传递只是地址的传递,也就是说,把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上,是形参数组和实参数组为同一数组,共同拥有同一段内存空间。

【例5-6】10个人的成绩存放在数组中,定义函数对成绩进行处理,将低于60分的成绩修改为60。

将10个人的成绩存放于数组score中,以score作为实参调用函数pass。函数pass处理形参数组array,将其中小于60的元素值修改为60。

#include<stdio.h>

voidpass(intarray[],intn);

main()

{

intscore[10]={70,45,90,65,53,85,55,75,66,77},i;

printf("处理之前的原始成绩如下:\n");

for(i=0;i<10;i++)

printf("%3d",score[i]);

printf("\n");

pass(score,10); printf("处理之后的成绩如下:\n");

for(i=0;i<10;i++)

printf("%3d",score[i]);

printf("\n");

}

voidpass(intarray[],intn)

{

inti;

for(i=0;i<n;i++)

if(array[i]<60)array[i]=60;

}图5-6例5-6程序运行结果

由于pass函数的参数为数组名,因此是地址传递方式。形参数组array获得了实参数组名score传来的数组首地址,则array与score数组共用一段存储空间。当形参数组元素的值被修改时,实参数组元素的值自然也同步发生了改变,如图5-7所示。图5-7地址传递时实参与形参的对应关系

5.3函数的嵌套调用与递归调用

5.3.1函数的嵌套调用

C语言的函数定义都是互相平行、互相独立的,也就是说在定义函数时,一个函数内不能包含另一个函数的定义,即不能嵌套定义函数。但是,C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用,即在被调函数中又调用其他函数。

如图5-8所示的是两层嵌套(包括main函数共3层函数),其执行过程如下:

①执行main函数的开头部分;

②遇调用a函数的语句,流程转去a函数;图5-8函数的嵌套调用

③执行a函数的开头部分;

④遇调用b函数的语句,流程转去b函数;

⑤执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作;

⑥返回调用b函数处,即返回a函数;

⑦继续执行a函数中尚未执行的部分,直到a函数结束;

⑧返回main函数中调用a函数处;

⑨继续执行main函数的剩余部分直到结束。【例5-7】

读程序,理解函数的嵌套调用。

#include<stdio.h>

intf2(intm)

{ intx;

x=2*m;

return(x);

}

intf1(intn)

{ inty;

if(n>5)y=f2(n);

else

y=2*n;

return(y);

}

main()

{ inta;

scanf("%d",&a);

printf("%d\n",f1(a));

}

图5-9例5-7程序运行结果分析程序的执行过程和运行结果如下:

①从main函数开始执行,执行到函数调用f1(a);

②实参a向形参n传递值;

③形参n获得值10,执行f1函数;

④由于n>5成立,执行函数调用语句:y=f2(n);

⑤实参n向形参m传递值;

⑥形参m获得值10,执行f2函数;

⑦f2函数的执行结果x为20,将20返回值带回主调函数f1;

⑧f1函数继续执行,y为20,将20返回值带回主调函数main;

⑨main函数继续执行,输出结果为20;

⑩碰到“}”,程序结束。5.3.2函数的递归调用

1.什么是递归调用

在调用一个函数的过程中,又出现直接或间接地调用该函数本身,称为函数的递归调用。

2.递归的方式

递归分直接递归和间接递归两种方式。

如图5-10所示是直接递归,f函数在执行的过程中又调用f本身;如图5-11所示是间接递归,f1函数在执行过程中调用f2函数,f2函数在执行过程中又调用f1函数。

在没有任何限制条件下,这样的递归调用会出现什么后果?显然,调用会无休止地进行下去,出现死循环,最后死机。怎样避免这种情况呢?方法是设置递归出口。

图5-10直接递归图5-11间接递归

【例5-8】5个小朋友排着队猜年龄,第1个小朋友10岁,其余的年龄后一个比前一个大2岁,请问第5个小朋友的年龄是多大?

算法思想:

设age(n)是求第n个人的年龄,那么,根据题意,可知:

age(5)=age(4)+2

age(4)=age(3)+2

age(3)=age(2)+2

age(2)=age(1)+2

age(1)=10

可以用数学公式表述如下:

图5-12求第5个人年龄的过程

递归分为两个阶段:第一阶段是“回推”,欲求第5个,“回推”到第4个,而第4个未知,“回推”到第3个,……,直到第1个,age(1)=10;第二阶段“递推”,从第1个推算第2个(12岁),从第2个推算出第3个(14岁),……,一直推算到第5个(18岁)。

该例题的递归结束条件:age(1)=10,这就是递归出口。图5-13例5-8程序运行结果

【例5-9】运用递归求1+2+3+…+n的和。

算法思想:采用递归方法求1+2+3+…+n的和。

假设sum(n)的功能是求前n项的和,若求出前n-1项的和,则前n-1项的和加第n项即为前n项的和。

该问题的递归公式:n为1时,函数值为1是递归出口。

#include<stdio.h>

intsum(intn)

{ inty;

if(n==1)y=1;

elsey=sum(n-1)+n;

return(y);

}

main()

{ intn;

scanf("%d",&n);

printf("%d\n",sum(n));

}

图5-14例5-9程序运行结果

5.4函数应用举例

【例5-10】

利用字符串库函数完成字符串的长度统计。

库函数strlen的函数原型为“intstrlen(char*str);”,返回字符串str的长度,该函数由头文件“string.h”来统一声明。

由于指针的知识尚未学习,在此使用字符数组来存储字符串,数组名本身也为指针。

#include<stdio.h>

#include<string.h> /*对于库函数我们使用include来完成库函数声明*/

main()

{ charstr[80];

intlength;

printf("输入一串字符\n");

gets(str);

length=strlen(str);

printf("字符串\"%s\"\n长度是%d\n",str,length);

}

图5-15例5-10程序运行结果

【例5-11】

利用字符串库函数完成字符串的复制。

库函数strcpy原型为“char*strcpy(char*dest,char*src);”,将字符串src复制至字符串dest中,该函数由头文件“string.h”来统一声明。

#include<stdio.h>

#include<string.h>

main()

{ chardest[80],src[80];

printf("输入一串字符\n");

gets(src);

strcpy(dest,src);

printf("输出复制结果\n");

puts(dest);

}图5-16例5-11程序运行结果

【例5-12】

利用字符串库函数完成字符串的连接。

库函数strcat原型为“char*strcat(char*dest,char*src);”,将字符串src拼接至字符串dest尾部,该函数由头文件“string.h”来统一声明。

图5-17例5-12程序运行结果

【例5-13】

利用字符串库函数完成字符串的大小比较。

库函数strcmp原型为“intstrcmp(char*str1,char*str2);”,函数返回值大于0,表明字符串str1大于字符串str2;返回值小于0,表明字符串str1小于字符串str2;返回值等于0,表明字符串str1与str2相等。该函数由头文件“string.h”来统一声明。图5-18例5-13程序运行结果【例5-14】

编写程序打印输出下面*号构成的三角形。

*

***

*****

*******

*********

观察发现每行的’*’和行数的关系是2*n-1,每行开头的空格数是4、3、2、1,0。在此定义一个函数print完成所有字符的输出。图5-19例5-14程序运行结果

【例5-15】

输出100~200之间的所有素数。

要输出100~200之间的素数,首先我们要判断一个数是不是一个素数,在此自定义函数isPrime来完成此功能。图5-20例5-15程序运行结果【例5-16】

用递归法编写乘方函数pow(x,n),n规定是整数。

此问题有如下递归公式:

从上面的递归公式看出,n=0时,有确定的解1;当n>0时,大规模能分解成小规模“pow(x,n-1)*x”求解;当n<0时,大规模能分解成小规模“pow(x,n+1)/x”求解,非常适合使用函数递归调用的方式求解本例。图5-21例5-16程序运行结果【例5-17】

用递归法输出斐波数列的前20个数。

斐波数列有下面的递归公式:首先,当n=1或n=2时,有确定的解1,其次,当n>2时,大规模分解成小规模“fib(n-2)+fib(n-1)”求解。图5-22例5-17程序运行结果

5.5变量的作用域和生存期

5.5.1变量的作用域

1.局部变量

在某个函数中定义的变量称为局部变量。局部变量的作用域是本函数,即局部变量只能在本函数中使用,在其他函数中不能使用。

关于局部变量的几点说明:

(1)不同的函数中允许有同名的变量,它们的作用域不同,互不干扰。

(2)函数的形参也是局部变量。

(3)在函数内部,允许在复合语句的声明部分定义变量,该变量的作用域为此复合语句。

图5-23例5-18程序运行结果

程序分析如下:

main主函数和fun1函数都有名为x的变量,虽然变量名相同,但它们是两个不同的变量,占据不同的存储空间,分别处于两个不同的作用域,两者之间没有任何关系。因此,在fun1函数中输出x的值为10,而在main函数中输出x的值为5。

fun2函数的形参y是fun2函数的局部变量,它只属于函数fun2;在main主函数中也说明了同样名字y的局部变量,它只属于函数main。函数调用时主调函数的实参值传递给被调函数的形参是单向的,被调函数处理完数据后,形参的值并不回传给实参。

2.全局变量

在函数之外定义的变量称为全局变量,它不属于某个函数,其作用域从定义全局变量的位置开始到本源文件结束。如果在全局变量定义位置之前的函数想使用该全局变量,则需要在该函数中用关键字extern对这个全局变量进行外部变量声明。【例5-19】

运行下面程序,分析运行结果。

#include<stdio.h>

inta=5; /*a是全局变量*/

voidfun1()

{ printf("%d\n",a);

a=15;

printf("%d\n",a);

}

main()

{ printf("%d\n",a);

a=10;

fun1();

printf("%d\n",a);

}

图5-24例5-19程序运行结果

程序分析如下:

变量a是全局变量,在main主函数和fun1函数中都可使用,都可改变a的值。

【例5-20】

运行下面程序,分析运行结果。图5-25例5-20程序运行结果5.5.2变量的生存期

定义变量时可以规定存储类型,存储类型规定了变量的生存期。程序中的数据存储可以简单地分为两种方式:静态存储方式和动态存储方式。

静态存储方式的变量,从程序开始执行时分配存储空间,直到程序执行结束才释放其存储空间。

动态存储方式的变量,在函数调用时分配动态存储空间,函数调用结束释放存储空间。

静态存储方式的变量在程序运行期间一直“存活”,而动态存储方式的变量则时而存在时而消失。这种由于变量存储方式不同而产生的特性称为变量的生存期。一个变量属于哪种存储方式,由存储类型决定。

C语言中有auto、static、register、extern四种存储类型,在变量定义时指定。其一般格式如下:

存储类型

类型说明符

变量名;

其中auto和register属于动态存储方式,static和extern属于静态存储方式。

1.auto类型

声明一个自动存储类型变量的方法是在变量类型说明符前加上关键字“auto”,例如:

autointi;

一般的局部变量说明前面没有规定存储类型,默认为auto自动存储类型。auto存储类型变量的生存期就是其作用域,也即auto自动存储类型的局部变量在其所在函数说明时分配存储单元并开始其生存期,所在函数运行完毕收回存储单元,结束生存期。

全局变量不能被说明为auto类型。

【例5-21】

运行下面程序,分析运行结果。

#include<stdio.h>

voidfsum(intx)

{ ints=0; /*本行等价于autoints=0;变量是auto自动存储类型*/

s=s+x*x;

printf("s=%d\n",s);

}

main()

{ fsum(5);

fsum(10);

}

图5-26例5-21程序运行结果

程序分析如下:

fsum函数的形参x默认是auto自动存储类型,从main主函数开始运行,第一次调用fsum函数时分配4个存储单元,开始x的生存期,将实参的值5传递给x。执行函数体,“ints=0;”变量s默认为auto自动存储类型,分配4个存储单元,开始s的生存期,赋初值为0;接着执行“s=s+x*x;”,变量s被赋值为25,屏幕输出为“s=25”,此次fsum函数运行完毕,释放s和x的存储单元,局部变量s和x生存期结束。

程序继续执行,第二次调用fsum函数,再一次给形参x分配4个存储单元,开始x的生存期,将实参的值10传递给x。执行“ints=0;”,定义auto类型变量s,为s分配4个存储单元,开始s的生存期,赋初值为0,执行语句“s=s+x*x;”,变量s被赋值为100,屏幕输出为“s=100”,fsum函数运行再次完毕,释放s和x的存储单元,局部变量s和x生存期结束。返回main函数,整个程序运行结束。

2.static类型

声明一个静态存储类型变量的方法是在变量类型说明符前加上关键字“static”,例如:

staticinti;

static类型变量被分配在内存的静态存储区中,其生存期是整个程序运行期。

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论