C语言程序设计 设计用函数实现模块化程序设计

1 函数是什么

函数就是功能。每一个函数用来实现一个特定的功能。函数的名字反映其代表的功能。
一个C程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。
例题 想输出以下的结果,用函数调用实现。
在这里插入图片描述

编写程序和运行结果:
在这里插入图片描述
分析:
print_star和print_message都是用户定义的函数名,分别实现输出一行“*”号和一行信息的功能。两个函数的类型为void,意为函数为空类型,即无函数值,执行这两个函数后不会把任何值带回main函数。

说明:
(1)一个源程序文件可以为多个C程序所调用。
(2)在程序编译时是以源程序文件尾单位进行编译的。
(3)在程序执行时总是main函数开始执行的。
(4)函数间可以相互调用,但不能调用main函数
(5)从用户使用的角度来看,函数有两种。
一,库函数,是由编译系统提供的。二,用户自定义函数,是用户自己设计的,用来实现用户指定的功能。
从函数的形式来看,函数分两类。
一,无参函数。主函数和调用函数之间不发生传递的数据。无参函数应指定为void类型。二,执行被调用函数时会得到一个函数值,供主函数使用。有参函数应定义与返回值相同的类型。

2 函数的定义和调用

2.1 定义函数

所有函数,必须“先定义,后使用”。指定它的功能和它的名字。
定义函数应包括以下4个内容:
(1)指定函数的名字,以便以后按名调用。
(2)指定函数的类型,即函数值的类型。
(3)指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项。
(4)指定函数的功能。

2.1.1 定义无参函数

定义无参函数的一般形式:
类型名 函数名()
{
函数体
}
函数体包括声明部分和执行部分。
类型名指定函数值得类型,即函数带回来的值的类型。

2.1.2 定义有参函数

定义有参函数的一般形式:
类型名 函数名(形式参数表列)
{
函数体
}
例如:
在这里插入图片描述
return z的作用是将z的值作为函数的值带回到主函数中,成为函数返回值。

2.2 调用函数

2.2.1 调用无参函数的形式

函数名()
如 print_star()

2.2.2 调用有参函数的形式

函数名(实参数表列)
如max(a,b)
实参数表列包含多个实参,各参数用逗号隔开。实参与形参的个数应相等,类型一致。实参与形参按顺序对应,实参向形参传递数据。
例题:输入两个整数,求输入两者中的大者。要求主函数中输入两个整数,用一个函数max求出其中的大者,并在主函数输出此值。
编写程序和运行结果:
在这里插入图片描述
分析:
定义函数是函数后面括号中的变量名称为形参。在主函数中调用一个函数时,函数名后面括号中的参数称为实参。
函数名max和两个形参x,y以及形参的类型int。max后面的括号内的a和b是实参。a=1传给x,b=2传给y。

函数调用的过程:
(1)在定义函数中指定的形参,在未出现函数调用时,他们并不占内存中的存储单元。
(2)将实参对应的值传给形参。
(3)在执行max函数期间,由于形参已经有值,就可以进行有关运算。
(4)通过return语句将函数值带回到主函数。如果含税不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。
(5)调用结束,形参单元被释放。
(6)调用函数的方式。
1 函数语句
调用没有返回值的函数,函数调用单独作为一个语句。如:print_star();
2 函数表达式
函数出现在一个表达式中,这种表达式成为函数表达式。这时要求函数带回一个确定的值参加表达式的运算。如:
在这里插入图片描述
3 函数参数
函数调用作为一个函数的实参。如:
在这里插入图片描述
把max(a,b)作为printf函数的一个参数

2.2.3 对被调用函数的声明和函数原型

被调用函数需要具备以下的条件:
(1)首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)。
(2)如果使用库函数,必须使用#include指令将该函数有关的信息“包含”到本文件中来。例如使用数学库中的函数,用下面的头文件:
在这里插入图片描述
声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统。
在函数调用之前做了函数声明。
将已经定义的函数的首部,再加一个分号,就是对函数的声明。函数的声明成为函数原型。
在这里插入图片描述
这是max函数,int max(int x,int y)是函数的首部,int max(int x,int y);是声明max函数。
实际上,函数声明中的参数名可以省略。例如:
在这里插入图片描述
因为编译系统并不检查参数名,只检查参数类型。因此参数名是什么都无所谓。甚至可以写成其他的参数名。如:
在这里插入图片描述
关于使用函数声明的说明:
如果被调用函数的定义放在主函数之前,此时不用声明。

3 函数的嵌套调用和递归调用

3.1 函数的嵌套调用

在调用一个函数的过程中,又调用另一个函数。
在这里插入图片描述

执行过程:
(1)执行main函数的开头部分;
(2)遇函数调用语句,调用函数a,流程转去a函数;
(3)执行a函数的开头部分;
(4)遇函数调用语句,调用函数b,流程转去函数b;
(5)执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作
(6)返回到a函数中调用b函数的位置;
(7)继续执行a函数中尚未执行的部分,直到a函数结束;
(8)返回main函数中调用a函数的位置;
(9)继续执行main函数的剩余部分直到结束

例题:输入4个整数,找到其中最大的数。
思路:定义一个函数max_4来实现从4个数中找到最大的数。用max函数找出两个数中的大者。先用max(a,b)找出a和b中的大者,赋给变量m。再用max(m,c)函数求出a,b,c三者中的大者,再赋给m。再用max(m,d)求出a,b,c,d四者中的大者,它就是a,b,c,d四个数中的最大者。
编写程序和运行结果:
在这里插入图片描述
分析:
在这里插入图片描述

max函数的函数体可以用一个return语句,返回一个条件表达式的值:
在这里插入图片描述

3.2 函数的递归调用

在调用一个函数的过程中又出现直接或间接调用该函数本身,称为函数的递归调用。
注意:
在调用一个函数过程中调用另一个函数,称为函数的嵌套调用。
在调用一个函数过程中直接或间接调用本函数,称为函数的递归调用。

递归必须有一个结束递归过程的条件。
一个递归的问题可以分为“回溯”和“递推”两个阶段。要经历若干步才能求出最后的值。
例题:
分别用递推方法和递归方法求n!,即1×2×…×n。
1 用递推方法求n!
思路:
从1开始,乘2,再乘3…一直乘到n。
编写程序和运行结果:
在这里插入图片描述
分析:
在这里先主函数的位置到fac函数之后,所在主函数中不需要对fac函数声明。
2 用递归方法求n!
从目标出发提出问题。求5!。必须先算出4!,想算出4!,必须先算出3!,想算出3!,必须先算出2!,想算出2!,必须先算出1!,即为1。到此不必再回溯,1!为结束递归过程的条件。
在这里插入图片描述
编写程序和运行结果:
在这里插入图片描述
分析:
若fac函数输出值小,则采用int类型。若fac函数输出值大,则采用long类型。
在这里插入图片描述
在这里插入图片描述
1!=fac(1)=1
2!=fac(1)*2=1!2=12=2
3!=fac(2)*3=2!3=23=6
4!=fac(3)*4=3!*4=24
5!=fac(4)*5=4!*5=120

执行“未知-未知-…-直到出现递归边界条件”,然后执行“已知-已知-已知”的过程。
注意:
一个问题能否用递归方法处理,取决于以下3个条件:
(1)所求解问题能转化为用同一方法解决的子问题。
(2)子问题的规模比原问题的规模小。
(3)必须有递归结束条件。

4 数组作为函数参数

4.1 用数组元素作函数实参

实参可以是表达式,数组的元素可以是表达式的组成部分,因此,数组元素当然可以作为函数的实参,传递方式是单向传递,即“值传送”方式
数组元素只能作函数实参,而不能作为函数形参。
例题:
有两个运动队a和b,各有10个队员,每个队员有一个综合成绩。将两个队的每个队员的成绩按顺序一一对应地逐个比较(即a队第1个队员与b队第1个队员比.….如果a队队员的绩高于b队相应队员成绩的数目多于b队队员成绩高于a队相应队员成绩的数目(例如,a队赢6次,b队赢4次),则认为a队胜。统计出两队队员比较的结果(a队高于、等于和低于b队的次数)。
解题思路:设两个数组a和b.各有10个元素,分别存放10个队员的成绩,将两个数组的相应元素逐个比较,用3个变量n.m.k分别累计a队队员高于、等于和低于b队队员的次数。用一个函数higher来判断每一次比较的结果。如果a队员高于b队员, 结果为1; 二者相等, 结果为0;a队员低于b队员,结果为一1。最后比较n和k即可得到哪队胜的结果。
编写程序:
在这里插入图片描述
在这里插入图片描述
运行结果:
在这里插入图片描述
分析:
(1)n表示a队队员成绩高于b队队员成绩的次数,m表示a队队员成绩等于b队队员成绩的次数,k表示a队队员成绩低于b队队员成绩的次数。
(2)定义higher函数,返回值为flag,flag为局部变量。比较a[i],b[i]的大小,若a[i]>b[i],则flag=1,而n自增一次,因此a队胜一次;若a[i]=b[i],则flag=0,而m自增一次,因此平局;若a[i]<b[i],则flag=-1,而k自增一次,因此a队输一次,即b队胜一次。
(3)输出输赢部分。用嵌套的if语句。若n>k,则a队赢,若n<k,则b队赢,若n=k,则平局。
(4)higher(a[i],b[i])这里用数组元素作函数实参。

4.2 用数组名作函数参数

在函数中处理整个数组的元素,此时可以用数组名作为函数实参。由于数字名代表数组首元素的地址,因此只是将数组的首元素的地址传给对应的形参。
例题:有10个学生成绩,用一个函数求全体学生的平均成绩。
思路:
在主函数横纵定义一个实型数组score,将输入的10个学生成绩存放在数组中。设计一个函数average,用来求学生的平均成绩。
把数组有关信息传递给average函数,采取数组名作为实参,把数组地址传给average函数,在average函数对数组进行求和,再求平均值,返回平均值给主函数。
编写程序和运行结果:
在这里插入图片描述
分析:
(1)用数组名作函数实参时,被调用函数需要声明形参数组。array是形参数组名,需要在被调用函数声明。
(2)实参数组和形参数组类型应一致(先都为float型)。
(3)用数组名作函数实参时,只是将实参数组的首元素的地址传给形参数组名,所以型参数组名获得了实参数组的首元素的地址,所以score[i]和array[i]共占同一存储单元,是指同一单元,score[i]和array[i]具有相同的值。如下图。
在这里插入图片描述
2000为地址
(4)C语言编译对形参数组大小不做检查,因此形参数组可以不指定大小。如:
在这里插入图片描述

5 变量的作用域和生存期

5.1 变量的作用域——局部变量和全局变量

5.1.1 局部变量

只有在本函数或复合语句内范围内有效的变量,称为局部变量或内部变量,只有在本函数或复合语句内才能使用他们,在此函数或复合语句以外是不能使用这些变量的。
说明:
(1)主函数定义的变量也只在主函数有效,主函数也不能使用其他函数定义的变量。
(2)不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。
(3)形式参数也是局部变量。
(4)在一个函数内部,可以在符合语句中定义变量,这些变量只在本复合语句中有效。

5.1.2 全局变量

在函数之外定义的变量是外部变量,也成全局变量(或全程变量)。全局变量的有效范围从定义变量的位置开始到本源程序文件结束。
说明:
(1)在一个函数中既可以使用函数中的局部变量,也可以使用全局变量。
(2)在同一个源文件中,全局变量与局部变量同名,此时只有局部变量是有效的。

例题:有4个学生,5们课的成绩,要求输出其中的最高成绩以及它属于第几个学生、第几门课程。
思路:
通过调用highest_score函数,得到最高分。题目需要输出3个结果。但是调用一个函数只能得到一个函数值,可以使用全局变量,通过全局变量从函数中得到所需要的值。
编写程序和运行结果:
在这里插入图片描述
分析:
(1)两个全局变量Row,Column用来保存最高分的行和列,由于全局变量在整个文件范围都有效,因此在higest_score函数中将行序号i和列序号j赋给全局变量Row和Column。
(2)数组序号从0开始,出于习惯可以将主函数最后一个语句的两个变量+1。
(3)函数highest_score与外界的联系图如下。
在这里插入图片描述说明:
(1)如果一个函数中改变了全局变量的值,就能影响其他函数,相当于各个函数间有直接的传递通道。
(2)在调用函数时有意改变某个全局变量的值,党函数执行结束后,不仅能得到一个函数值返回值,而且能使全局变量获得一个新值,从效果上看,相当于通过调用函数得到一个以上的值。
(3)习惯将全局变量名的第一个字母用大写表示。
(4)全局变量在程序的全部执行过程中都占用存储单元。
(5)如果使用全局变量与其他文件的变量同名时,就会冲突。C程序中的函数做成一个封闭体,除了通过“实参-形参”的渠道与外界发生联系外,没有其他渠道。
(6)限制使用全局变量。

5.2 变量的生存期和存储方式

变量还有一个重要的属性:变量的生存期,即变量值存在的时间。
变量的存储有两种不同的方式:静态存储方式和动态存储方式。
全局变量采用静态存储方式,在程序开始执行时给全局变量分配存储区,程序执行完毕释放。在程序执行过程中它们占据存储单元。
动态存储方式是在函数调用开始时分配动态存储空间,函数结束时释放这些空间。
每一个变量函数有两个属性:数据类型和数据的存储类别。在定义变量时,在需要时还可以指定其存储类别。
C语言中可以指定以下存储类别:
1 auto-声明自动变量
自动变量在函数调用结束时就自动释放这些存储空间,关键字auto可以省略,此时默认为“自动存储类别”,它属于动态存储方式。例如:

相当于
在这里插入图片描述
2 static-声明静态变量
在函数调用结束后不消失而继续保留原值,即占用的存储单元不释放。
例题:
输出1~5的阶乘值。要求:每一次的连乘值保留。

思路:
写一个连乘函数,在主函数中调用。
可以用static来指定变量不释放,保留原值。

编写程序和运行结果:
在这里插入图片描述
关于静态局部变量的说明:
(1)静态局部变量在程序整个运行期间都不释放。自动变量(即动态局部变量),函数调用结束后即释放。
(2)静态局部变量以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。
(3)静态局部变量不赋初值,自动赋值为0。自动变量不赋初值则它的值是一个不确定的值。
(4)静态局部变量在函数调用结束后仍然存在。
(5)若非必要,不要多用静态局部变量。
3 register-声明寄存器变量
C语言允许将局部变量的值放在CPU中的寄存器,寄存器的存取速度远高于内存的存取速度,可以提高执行效率,这种变量叫做寄存器变量,用register作声明。如:
在这里插入图片描述
实际上用register声明变量是不必要的。

4 extern-声明外部变量的作用范围
在一个文件内扩展外部变量的作用域,应该在引用之前用关键字extern对该变量作“外部变量声明”,例如:extern A;,表示把该全局变量A的作用域扩展到此位置。
将外部变量的作用域扩展到其他文件。在任一个文件中定义全局变量,而在另一个文件用extern对全局变量作“外部变量声明”。例如:extern A;
综上,对一个数据的定义,指定两种属性:数据类型和存储类别,分别使用两个关键字。例如:
在这里插入图片描述

5.3 关于作用域和生存期的小结

从作用域角度分,有局部变量和全局变量,他们采用的存储类别如下:
在这里插入图片描述

从变量存在时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储时程序整个运行时间都存在,而动态存储是在存储时在调用函数时临时分配单元。
在这里插入图片描述

从变量值存放的位置来区分,可分为:
在这里插入图片描述

6 内部函数和外部函数

6.1 内部函数

在定义内部函数时,在函数首部分的最左端加关键字static,即
static 类型标识符 函数名(形参表列);
如:
在这里插入图片描述
内部函数又称静态函数,因为它是用static声明的。内部函数使函数的作用域值局限所在文件。

6.2 外部函数

在定义函数时,在函数首部分的最左端加关键字extern,此函数就是外部函数,可供其他文件调用。
如:
在这里插入图片描述
函数fun就可以为其他文件调用,C语言规定,在定义函数是省略extern,则隐含为外部函数,大多数的函数都是外部函数。

7 提高部分

选择排序
例题:用一个函数实现用选择法对10个整数按升序排序。
思路:进行n-1轮排序,每一轮将最小的数与a[n-1]对换。每一轮对换两个数的位置。例如:
用选择法对5个数排序的步骤。
在这里插入图片描述

例题编写代码和运行结果:
在这里插入图片描述

8 小结

(1)函数是用来完成某一个特定功能的。C程序是由一个成多个函数组成的。函数是C程序中的基本单位。执行程序就是执行主角数和由主函数调用其他函数,因此编写C程序,主要就是编写函数。
(2)有两种函数系统提供的库函数和用户根据需要自己定义的函数,如果在程序中使用库函数,必须在本文件的开头用#include指令把与该数有关的头文件包含到本文件中来(如用数学闲数时要加上#include<math.h>) 。如果用自己定义的函数, 必须先定义、后调用。需要注意:如果数的调用出现在函数定义位置之前,应该在调用函数之前用函数的原型对该函数进行引用声明。如:
float average(float array[10]);//声明average函数
float average(float array[])//定义average函数

(3)函数的“定义“和“声明“不是一回事,函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位,而承数的声明的作用则是把函数的名字、数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
(4)函数原型有两种形式:
①函数类型函数名(参数类型1参数名1,参数类型2参数名2.,参数类型a参数名n);
函数类型函数名(参数类型1,参数类型2,,参数类型n);
第①种形式就是函数的首部加一个分号。

(5)调用函数时要注意实参与形参个数相同、类型一致(或赋值兼容),**数据传递的方式是从实参到形参的单向值传递。**在函数调用期间如出现形参的值发生变化,不会影响实参原来的值。

(6)在调用一个函数的过程中,又调用另外一个函数,称为函数的嵌套调用。可以有多层的嵌套调用。在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用

(7)用数组元素作为函数实参,其用法与用普通变量作实参时相同,向形参传递的是数组元素的值,用数组名作函数实参,向形参传递的是数组首元素的地址,而不是数组全部元素的值。
如果形参也是数组名,则形参数组首元素与实参数组首元素具有同一地址,两个数组共占同一段内存空间,利用这一特性,可以在调用函数期间改变形参数组元素的值,从而改变实参数组元素的值。

(8)变量的作用域是指变量有效的范围。根据定义变量的位置不同,变量分为局部变量全局变量
凡是在函数内或复合语句中定义的变量都是局部变量,其作用城限制在数内或复合语句内,函数成复合语句外不能引用该变量。
在函数外定义的变量都是全局量、其作用城为从定义点到本文件末尾, 可以用extern对变量作“外部声明”, 将作用域扩到本文件中作extern声明的位置处, 或在其他文件中用extern声明将作用城扩展到其他件。用static声明的静态全局变量禁止其他文件引用该变量, 只限本文件内引用。

(9)变量的生存期指的是变量存在的时间,全局变量的生存期是程序运行的整个时间。局部变量的生存期是不相同的。用auto或register声明的局部变量的生存期与所在函数被调用的时间段相同, 函数调用结束, 变量就不存在了。用static声明的局部变量在数调用结束后内存不释放,变量的生存期是程序运行的整个时间。凡不声明为任何存储别的都默认为auto(自动变量)。

(10) 变量的存储类别共有4种:auto, static, register, extern。auto, static, register用于局部变量,改变变量的生存期,extern只用于全局变量,可改变变量的作用域。

(11)区别对变量的定义与声明。定义变量时,要指明数据类型,编译系统要据此给变量分配存储室间,又称为定义性声明,凡不引起空间分配的变量声明(如extern声明) , 不必指定数据类型,因为数据类型已在定义时指定了。这种声明只是为了引用的需要,这种声明称为引用性声明,简称声明。在一个作用域内,对同一变量,只能出现一次定义,而声明可以出现多次。

(12)函数有内部函数与外部函数之分,函数本质上是外部的,可以供本文件或其他文中的函数调用, 但是在其他文件调用时要用extern对函数进行声明。如果在定义函数时时static声明, 表示其他文件不得调用此函数, 即把它“屏蔽”起来。

猜你喜欢

转载自blog.csdn.net/qq_45059457/article/details/114013994