版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
函数4.1概述4.2函数的定义、调用和声明4.3函数的嵌套和递归调用4.4变量的作用域与生存期4.5编译预处理命令本章小结
本章要点
函数是指完成一个特定工作的独立程序模块,它是C语言程序的基本组成单元。每一个C语言程序都是由一个或者一个以上的函数组成的,如每个程序必须包括的main()函数。函数可以把大的计算任务分解成若干个较小的任务,使编程更为简单。本章详细讲解C语言程序设计中函数的相关知识。
本章主要内容包括函数的定义和声明;函数的调用和参数传递;函数的嵌套调用和递归调用;变量的作用域和生存期;变量的存储类别;编译预处理。
4.1概述
4.1.1函数的分类C语言程序是由函数组成的,程序员可以直接调用系统定义好的函数,也可以自己定义函数,进行调用
从用户使用的角度看,函数分为两种:
(1)标准函数:即库函数。我们前面用过的函数除了每个程序必须有的main()函数以外,还有格式输出函数printf(),格式输入函数scanf(),求平方根函数sqrt()等,这些函数不需要用户编写,可以直接调用。这些由C语言系统提供定义的函数叫库函数。
(2)自定义函数:用户也可以根据程序需要自己定义新的函数。这些由用户自己定义的函数叫自定义函数。本章重点讲述的就是自定义函数的定义和调用。
根据函数的调用关系,可以把函数分为两种:
(1)主调函数:调用其他函数的函数。
(2)被调函数:被其他函数调用的函数。
从函数的形式看,函数分两类:
(1)无参函数:在调用函数时,主调函数不需要复制数据到被调函数,一般用来执行特定的操作。
(2)有参函数:在调用函数时,主调函数和被调函数需要进行数据传递。
4.1.2程序解析
【例4-1】输入n,求1!+2!+…+n!,并输出结果。
分析:我们可以先不计算阶乘,把i! 作为一个累加项,这样程序编写就非常简单了。通过上一章的学习,我们可以通过一个for循环来计算累加和,程序可写为
问题:i! 怎么求?
方法:可以通过两种方法来对i! 进行求解。
(1)可以使用循环来求i!,即在主函数中使用循环或者二重循环来求i!(代码请读者自己编写)。
(2)也可以编写阶乘函数,在主函数中调用阶乘函数求出i!,使程序更为简单。首先要定义阶乘函数intfactorial(intm),然后用函数调用代替上述代码中的“i!”。
程序分析:输入3后,程序中n赋值为3,则main()函数中,for循环执行3次,每次执行调用一次factorial()函数计算i!并保存在变量f中,计算完成后由returnf;语句带回到main()函数中,将i!值累加到变量sum上。经过3次循环后,就可以计算出sum=1!+2!+3!,并将sum输出。
说明:
(1)一个简单的C程序可由一个main()函数组成。
(2)一般来说,一个C语言程序由一个main()函数和若干个用户定义的函数组成。
(3)程序执行时,从main()函数开始,通过主函数调用其他函数。其他函数执行结束后,还要回到main()函数,在main()函数中结束整个程序的运行。main()函数是系统定义的程序的入口点。注意:一个C语言程序只能有一个main()函数。
(4)用户自定义函数可以相互调用,但其他函数不能调用main()函数。
4.2函数的定义、调用和声明
对用户自定义函数的使用主要分为三个步骤:首先要定义函数,即编写函数的主体,实现函数的功能;定义完函数以后,在 main()函数或其他函数中调用这个函数执行相应计算或实现特定的功能;如果被调函数在主调函数之后,则调用之前还要对函数进行声明。
4.2.1函数的定义
1.函数定义的一般形式
函数定义的一般形式如下:
函数类型函数名(形参表) /*函数首部*/
{ /*函数体*/
声明部分
执行部分
return语句
}
说明:
(1)函数类型指函数返回值类型,应与return语句中表达式的类型一致。当两者不一致时,以函数类型为准。若函数不需要返回值,函数类型为void,函数体中不需要写return语句。
(2)函数类型默认为int型,即当函数类型不写时,返回值类型为int型,而不是没有返回值,请注意区分。
(3)形参表中格式为
类型1参数1,类型2参数2,…,类型n参数n
参数之间用逗号分隔,每个参数前面的类型都必须分别写明。
(4) return语句只能返回一个值。
(5)注意函数首部后面不能加分号。
【例4-2】编写函数fun,统计一个十进制数中数码为0的个数,以及数码为1的个数。
程序分析:fun()函数中每次循环判断最低位是否为0或1,是的话,计数变量+1,然后将已经判断完的最低位去掉,直到判断完所有的位数。fun()函数的计算结果在函数体中输出,结果不需要带回main()函数,函数没有返回值。
2.无参函数的定义
无参函数定义的一般形式如下:
函数类型函数名() /*函数首部*/
{
声明部分
执行部分 /*函数体*/
return语句
}
无参函数其实就是函数的一种特殊情况。在使用函数的过程中,有时主调函数不需要传递值给被调函数,这时函数定义时就没有参数。无参函数可以带回返回值,也可以不带回返回值,但无参函数一般用于完成特定操作,很少用于计算,所以大部分情况下也没有返回值。
【例4-3】一个简单的无参函数例题。
程序分析:这是一个最简单的函数调用程序,其中printstar()是一个无参函数,函数的功能是打印一行12个“*”号并换行。printstar()函数体只有一条语句,执行打印操作,不需要从主函数中传递参数,也不需要带回返回值,所以函数中没有return语句,且函数类型为void。
在main()函数中,调用两次printstar()函数,在“HelloWorld!”前后各打印一行星号。
3.空函数的定义
空函数定义的一般形式如下:
函数类型函数名()
{ }
说明:空函数什么工作也不做,没有任何实际作用。在编写程序的开始阶段,可以在以后需要扩充功能的地方写上空函数,在编写程序的后期,再用完整的函数替代它。
4.2.2函数的调用
定义一个函数后,就可以在程序中调用这个函数。调用库函数时,要在程序开头加上#include命令包含相应的头文件,否则编译会出现错误:函数未定义。调用自定义函数时,程序中必须包括函数的定义,如果函数定义在调用的后面,则需要在调用前加上函数的声明。
1.函数调用的一般格式
函数调用的一般格式如下:
函数名(实际参数表)
说明:
(1)函数调用和函数定义的区别。函数调用时,实参列表不需要参数类型,实际参数可以是简单变量,也可以是常量;而在函数定义时,参数类型一定不能省略。
(2)库函数调用和用户自定义函数调用的格式是一样的。
(3)如果函数有返回值,则在遇到return时,返回到调用的地方,并带回结果;如果函数没有返回值,则在遇到函数体的后一半花括号时,返回到调用的地方。
2.函数调用流程
系统对函数的调用有如下的流程:
(1)程序从main()函数开始执行,当遇到函数调用时,为被调函数分配存储空间,将实参值复制给形参变量。
(2)主函数暂停执行,转而执行被调用的函数。
(3)被调函数执行完成后(遇到return语句或函数的右边花括号),返回主函数,释放被调函数占用的内存空间,从主函数原先暂停的位置继续执行。
函数调用流程如图4-1所示。图4-1函数调用流程示意图
【例4-4】输入两个整数,调用最大值函数计算两数的最大值,并输出。
如果输入3和5,运行结果为
Enteraandb:35
maxofaandbis5
程序分析:程序功能为输入两个整数,调用max()函数计算两数最大值,并打印输出。main()函数中首先调用scanf()函数输入变量a和b,然后在printf("maxofaandbis%d\n",max(a,b));语句中调用max()。此时,函数跳转到max()函数,在max()函数中,使用if-else语句计算a和b中较大的数,并赋给变量c。最后returnc;语句将最大值带回到main()函数中,替换printf()函数中的max(a,b),并将最大值打印输出。
3.函数调用方式
函数调用作为一个单独的语句,通常用在不需要返回值,只执行特定操作的函数调用中,如printstar();。
如果函数需要带回返回值,则可以采用下面两种调用方式:
(1)函数作为一个表达式的一部分,如sum=sum+factorial(i);。
(2)函数作为其他函数的参数,如printf("maxofaandbis%d\n",max(a,b));。
在编写程序的过程中,可以根据个人习惯和程序的需要,选择适当的调用方式。
【例4-5】编写函数isprime()判断一个数是否为素数,并在main()函数中调用,求20到100所有素数。
运行结果如图4-2所示。图4-2例4-5运行结果
4.2.3函数的声明
C语言要求函数要先定义后调用,即被调函数要放在主调函数之前,就像变量要先定义后使用一样。如果自定义函数放在主调函数之后,就需要在函数调用前加上函数声明。如果函数调用之前,既不定义,也不声明,程序编译时会出现错误:函数未定义。
函数声明的一般格式为
函数类型函数名(形参表);
即只写函数定义中的函数首部,并以分号结束。
【例4-6】一个盒子里有3个红球(x),5个白球(y),6个黑球(z),从中任意取出6个,保证里面一定有红球和白球。请编写函数,输出所有的方案。
运行结果如图4-3所示。图4-3例4-6运行结果
4.2.4函数间的参数传递
函数的参数分为两种:形式参数和实际参数,简称为形参和实参。在定义和声明中,函数参数只有变量类型和变量名,是形式参数;而在函数调用中,函数的参数可以是常量或者是简单变量,是实际参数。
函数调用语句中的实参类型个数和顺序必须与函数定义中的形参类型和个数一致,否则编译时会发生“类型不匹配”的错误。
【例4-7】输入x和a,调用mypow()函数计算xa,并输出结果。
程序分析:在主函数的函数调用语句y=mypow(x,a);中,x和a为实际参数,而在函数定义和声明doublemypow(doublex1,inta1)中,x和a为形式参数。
参数传递过程:在main()函数中执行函数调用语句y=mypow(x,a);时,x和a的值会复制给x1和a1。在mypow()函数中,经过计算得到结果10.24,通过return语句带回到y=mypow(x,a);语句中,替换mypow(x,a)。参数传递过程如图4-4所示。图4-4参数传递过程
【例4-8】猜数游戏:计算机产生一个1~1000之间的随机整数,用户输入一个正整数,判断是否与计算机产生的随机数相同。若猜中,输出所猜次数和该数;若没猜中,输出“Sorry,youarefailed!”。用户一共有10次猜数的机会。
编写函数intGuess(intn)完成猜数功能:参数n是计算机产生的随机数,用户输入一个所猜的正整数x,判断是否与n相等。若x与n相等,返回所猜次数;若x比n小,输出“toosmall!”;若x比n大,输出“toobig!”。没猜中可以继续,但最多可猜10次,若10次都没猜中,则返回0。
【例4-9】一个参数单向值传递的例子。
运行结果:
Beforefun1,a=5
Afterfun1,a=5
请思考为什么运行结果不是:
Beforefun1,a=5
Afterfun1,a=10
程序分析:main()函数在调用fun1()函数时,会为fun1()中的参数和变量重新分配存储空间。虽然函数调用fun1(a)中的实参和函数定义voidfun1(inta)中的形参同类型,而且同名,但并不是同一个变量。在fun1()函数中,a=10;语句修改的是形参a,而参数传递是单向的,所以修改不会传递到main()函数中去,main()函数中实参a的值仍然是5,而不是10。
fun1()函数参数传递的过程如图4-5所示。
图4-5例4-9中参数传递过程
【例4-10】输入两个整数,调用swap函数将两数交换。
4.3函数的嵌套和递归调用
4.3.1函数的嵌套调用C语言不允许函数的嵌套定义,但允许函数的嵌套调用。如果函数A调用函数B,而函数B又调用函数C,这样的调用方式就叫函数的嵌套调用。
下面举一个两层嵌套的例子。在main()函数中调用f1()函数,而在f1()函数中又调用f2()函数,这样就形成了两层的嵌套调用。当函数f2()执行完成后会返回函数f1(),从调用的地方继续执行,直到函数f1()执行结束,返回main()函数,然后从调用函数f1()的地方往后执行,最后在main()函数中程序运行结束。函数嵌套调用过程如图4-6所示。图4-6函数嵌套调用示意图
【例4-11】编程:从键盘输入两个整数a和b,调用两函数分别求a和b的最大公约数、最小公倍数,并输出结果。
程序分析:求最大公约数采用辗转相除法,而最小公倍数为m*n/最大公约数,在求最小公倍数时,要嵌套调用最大公约数函数。
4.3.2递归函数
在调用一个函数的过程中,会出现直接或间接地调用函数本身的情况,称为函数的递归调用。简而言之,递归就是函数自己调用自己。
递归可分为直接递归和间接递归。函数直接调用本身叫直接递归;而函数A调用函数B,在函数B中又调用函数A,这种函数间接调用本身的函数调用叫间接递归。
【例4-12】编程:从键盘输入一个正整数n,逐个打印输出1!~n!。要求使用递归函数实现阶乘的计算。
程序分析:阶乘函数的递归调用过程如图4-7所示。图4-7递归函数调用示意图
由图4-7可知,函数的递归调用可分为两个阶段:第一个阶段是递推,即逐层调用自己,直到遇到可直接返回值的return语句;第二个阶段是回归,即将结果逐层代入回去,直到计算出需要的结果。
在编写递归函数时,有两个着眼点:
(1)递归出口:即递归结束的条件,递归函数必须要有出口。在上面的阶乘函数中,if(m==0||m==1)return1;语句就是递归出口,当形参值为0或1时,递归结束。
(2)递归表达式:如n!=n*(n-1)!,即上例中的f=m*fact(m-1)。
同样是编写函数实现阶乘的计算,例4-1中阶乘函数使用的是递推方法,而例4-12中使用的是递归方法。
【例4-13】编程:调用函数求斐波那契(fibonacci)数列的第20项。fibonacci数列:
112358……,该数列的规律为,前两项值为1,从第三项开始,每一项是前两项的和。
程序分析:求fibonacci数列的第20项,要反复使用递推公式,直到求出前两项的值,再逐项求出后面的18项,最后得到第20项的值,返回main()函数,输出结果。
4.4变量的作用域与生存期
4.4.1局部变量与全局变量变量按作用域可以分为局部变量和全局变量。
1.局部变量在一个函数或者复合语句内部定义的变量,叫做局部变量。局部变量只在定义它的函数或者复合语句内部有效,在此范围之外是不能使用这些变量的。
【例4-14】局部变量例题。
程序分析:变量a定义在main()函数中,所以其作用范围就在main()函数内部;而变量b定义在复合语句中,所以其作用范围在复合语句的两个花括号之间。
程序编译时,会在第二个printf()函数处指示错误“'b':undeclaredidentifier”。请思考这是为什么。
2.全局变量
为了解决多个函数间共用变量的问题,C语言中允许定义全局变量。定义在函数外,不属于任何一个函数的变量称为全局变量。一般情况下,全局变量的定义放在第一个函数的前面。全局变量的作用范围是从定义的地方开始,直到程序所在文件结束,作用范围内的所有函数都可以使用全局变量。
函数的作用范围又叫作用域。局部变量的作用域为所在函数或复合语句,而全局变量的作用域为定义的地方直到程序运行结束。
【例4-15】全局变量例题。
运行结果:
Inthecompoundstatement,a=0
Inthefunctionf1,a=15
a=10,b=5
由于全局变量使用不当会导致各函数之间出现干扰,容易出错,因此在一般情况下,应尽量避免使用全局变量,而尽量使用局部变量和函数参数。
4.4.2动态变量与静态变量
变量的存储类型分为动态变量和静态变量。对于动态变量,系统在程序运行期间根据需要为其分配存储空间,对于静态变量,系统在程序运行期间为其分配固定的存储空间。函数(含main()函数)以及复合语句中定义的变量、函数的形参都属于动态存储的变量。动态变量在函数被调用之前是不分配存储空间的,直到函数被调用时系统才会为函数中的局部变量分配存储空间,一旦函数调用结束,系统自动回收函数中局部变量所占用的存储空间。
动态变量包括自动变量和寄存器变量两种。在C语言中,每一个变量和函数有两个属性:数据类型和数据的存储类别。
1.自动变量
自动变量定义的形式是:
auto类型名变量表;
如:
autointa,b;
定义两个自动类型的整型变量a和b。
在定义自动变量时,auto可以省略,形式与我们以前在函数中定义普通变量时完全一样。所以,我们以前编程过程中用到的变量都是自动变量。
2.寄存器变量
寄存器变量把数据存储在计算机寄存器单元上,存取速度比内存快得多。但大多数C语言系统上,并不真正支持寄存器变量的使用,而是把寄存器变量当做普通自动变量来处理。寄存器变量只适用于整型。
寄存器变量定义的形式是:
registerint变量表;
3.静态局部变量
前面讲到的全局变量,由于从定义的地方到程序结束一直有效,因此其对应的存储单元一直有效,而全局变量存储空间的分配也是与自动局部变量不同的,其存储空间是静态分配的,即在程序运行期间一直保持,不会被释放。
与全局变量同样静态分配存储空间的还有静态局部变量,它的作用范围是函数的内部,但是当函数调用结束时,其所占的存储空间不会被释放,值依然保存着,下次调用还可以继续使用。
变量在内存中保存的时间称为变量的生存周期。前面讲过的普通局部变量,生存周期就是函数或复合语句执行期间,与作用域相同。而全局变量和静态局部变量的生存周期则是程序运行期间。请注意,静态局部变量的作用域和生存周期并不一样,作用域是在函数调用期间,而生存周期则是程序运行的整个过程。
静态局部变量的定义格式为:
static类型名变量表;
静态局部变量如果没有在函数中赋初值,系统会自动为静态局部变量赋初值为0。
【例4-16】静态局部变量例题。
程序分析:先来看main()函数,main()函数中for循环会执行3次,即调用3次f1()函数。静态局部变量系统会自动赋初值为0,所以f1()中变量b的初值为0。第一次调用f1()函数时,printf()函数输出b的值应为2,这并不难理解。
4.外部变量
全局变量的作用范围是整个程序,只能在某个模块中定义一次。如果重复定义,在程序连接时会出现错误。当一个模块需要直接访问其他模块中定义的全局变量时,程序编译时会出现“变量未定义”的错误。这就需要在该程序模块开头加上对全局变量的声明,即外部变量。
外部变量的声明格式为:
extern变量名表;
5.静态全局变量
当程序由多个文件组成时,在一个文件中定义的全局变量也可以作用于其他文件,会造成文件的互相影响。
如果把全局变量声明为静态,则这个静态全局变量的作用范围只限于当前的文件,而不会作用于其他文件,这样就避免了同一个程序不同文件中的全局变量的相互影响。
4.5编译预处理命令
4.5.1文件包含所谓文件包含,是指一个源文件可以将另外一个源文件的内容全部包含进来。文件包含的一般形式为#include<文件名>或#include"文件名"
一般来讲,使用C语言标准头文件时,在文件包含中使用尖括号<>;使用编程者自己的文件时,在文件包含中使用双引号" "。这两者并没有严格界限,举例来说,在程序中使用 #include<stdio.h>和#i
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 五年级下英语重点语法填空专项练习《人教版PEP》
- 2026年好女生测试题试卷及答案
- 2026年大王人格测试题及答案
- 2026年双相抑郁测试题及答案
- 2026年检验纯洁测试题及答案
- 2026年解剖学基础测试题及答案
- 2026年苏轼传阅读测试题及答案
- 2026年时光倒流测试题及答案
- 2026年词语选择测试题及答案
- 会计师事务所财务管理制度(3篇)
- 毛概期末考试真题及答案
- 2026年天津市专业技术人员继续教育公需课答案
- 厦门大学《数字贸易学》2025-2026学年期末试卷
- 建筑工地高空坠落安全培训教材
- 四川省绵阳市2025年中考生物学试题附答案
- 2025年中考语文试题分类汇编:作文(江苏专用)解析版
- 医院基本药物使用考核方案
- 临终病人家属灵性关怀操作要点
- 2026年云南丽江市中考地理真题试题(含答案)
- 制造业企业数字化转型成熟度评估规范编制说明
- 四川省消防安全管理条例解读
评论
0/150
提交评论