第九章:函数-上

一个较大的程序一般应分为若干个程序模块,每一个模块用来实现一个特定的功能。所有的高级语言中都有子程序,用来实现模块的功能。在C语言中,子程序的作用是由函数完成的。
本章致力于使读者了解关于函数的概念,掌握函数的定义及其组成部分;熟悉函数的调用方式;了解内部函数和外部函数的作用范围,区分局部交量和全局变量的不同;最后能将函数应用于程序中,将程序分成模块。通过阅读本章,您可以:
了解函数的概念

掌握函数的定义方式

熟悉返回语句和函数参数的作用

掌握函数的调用

了解内部函数和外部函数的概念

区分局部变量和全局变量

**

9.1 函数概述

**

构成C程序的基本单元是函数。

函数中包含程序的可执行代码。每个C程序的入口和出口都位于main 函数之中。编写程序时,并不是将所有内容都放在主函数main 中。为了方便规划、组织、编写和调试,一般的做法是将一个程序划分成若干个程序模块,每一个程序模块都完成一部分功能。这样,不同的程序模块可以由不同的人来完成,从而可以提高软件开发的效率。

也就是说,主函数可以调用其他函数,其他函数也可以相互调用。在main 函数中调用其他函数,这些函数执行完毕之后又返回到 main 函数中。

通常把这些被调用的函数称为下层函数。

函数调用发生时,立即执行被调用的函数,而调用者则进入等待的状态,直到被调用函数执行完毕。函数可以有参数和返回值。

例如盖一栋楼房,在这项工程中,在工程师的指挥下,有工人搬运盖楼的材料,有建筑工人建造楼房,还有工人在楼房外粉刷涂料。编写程序与盖楼的道理是一样的,主函数就像工程师一样,其功能是控制每一步程序的执行,其中定义的其他函数就好比盖楼中的每一道步骤,分别去完成自己特殊的功能。

图 9.1是某程序的函数调用示意图。

在这里插入图片描述
例 9.1 在主函数中调用其他函数

通过定义函数来完成某种特定的功能,为了表示函数完成的功能,在这里使用输出的信息进行表示。希望读者通过这个实例先对函数的概念有一个更为具体的认识。

在这里插入图片描述
在查看程序的结果之前,先对程序进行分析和讲解。首先,一个源文件由一个或者多个函数组成。一个源程序文件是一个编译单位,即以源程序为单位进行编译,而不是以函数为单位进行编译。库函数由C系统提供,用户无须定义,在调用函数之前也不必在程序中作类型说明,只需在包程序前包含有该函数原型的头文件即可在程序中直接调用。例如,在上面程序中用于在控制台显示信息的 printf函数,之前应在程序开始部分包含 stdio.h这个头文件,又如要使用其他字符串操作函数 strlen、strcmp等时,也应在程序开始部分包含 string.h。用户自定义函数,就是用户自己编写的用来实现特定功能的函数,例如上面程序中的Move、Build 和 Paint 函数都是自定义函数。

在这个程序中,要使用 printf函数首先要包含 stdio.h头文件,然后声明3个自定义的函数。最D后在主函数 main 中调用这3个函数,在主函数 main 外可以看到这3个函数的定义。运行程序,显示效果如图 9.2 所示。

在这里插入图片描述
**

9.2 函数的定义

**

在程序中编写函数时,函数的定义是让编译器知道函数的功能。定义的函数包括函数头和函数体两部分。

1、函数头

函数头分为以下3个部分:

1-1、返回值类型。返回值可以是某个C数据类型。

1-2、函数名。函数名也就是函数的标识符,函数名在程序中必须是唯一的。因为是标识符,所以函数名也要遵守标识符命名规则。

1-3、参数表。参数表可以没有变量也可以有多个变量,在进行函数调用时,实际参数将被复制到这些变量中。

2、函数体包括局部变量的声明和函数的可执行代码。

前面最常提到的就是 main 函数,下面对其进行介绍。所有的C程序都必须有一个main 函数。该函数已经由系统声明过了,在程序中只需要定义即可main 函数的返回值为整型,并可以有两个参数。这两个参数一个是整数,一个是指向字符数组的指针。虽然在调用时有参数传递给 main 函数,但是在定义 main 函数时可以不带任何参数,在前面的所有实例中都可以看到 main 函数就没有带任何参数。除了 main 函数外,其他函数在定义和调用时,参数都必须是匹配的。
程序中从来不会调用 main 函数,系统的启动过程在开始运行程序时调用 main 函数。当 main 函数结束返回时,系统的结束过程将接收这个返回值。
至于启动和结束的过程,程序员不必关心,编译器在编译和链接时会自动提供。不过根据习惯,当程序结束时,应该返回整数值。其他返回值的意义由程序的要求所决定,通常都表示程序非正常终止。为了让读者习惯 main 函数的返回值,可以看到本书所有实例中的 main 函数都定义为如下形式:

在这里插入图片描述
9.2.1 函数定义的形式

C语言的库函数在编写程序时是可以直接调用的,如 printf输出函数。而自定义函数则必须由用户对其进行定义,在其函数的定义中完成函数特定的功能,这样才能被其他函数调用。

一个函数的定义分为函数头和函数体两个部分。函数定义的语法格式如下:

返回值类型 函数名(参数列表)
{
函数体(函数实现特定功能的过程);
}

定义一个函数的代码如下:
int AddTwoNumber(int iNum1 .int iNum2) //函数头文件
{
//函数体部分,实现函数的功能
int result; //定义整型变量
result = iNum1+iNum2; //进行加法操作
return result; //返回操作结果,结束程序
}

通过代码分析一下定义函数的过程。

1、函数头
函数头用来标志一个函数代码的开始,这是一个函数的入口处。函数头分成返回值类型、函数名和参数列表3个部分,
在上面的代码中,函数头如图 9.3 所示。

在这里插入图片描述
2、函数体

函数体位于函数头的下方位置,由一对大括号括起来,大括号决定了函数体的范围。函数要实现的特定功能,都是在函数体部分通过代码语句完成的,最后通过return语句返回实现的结果。
在上面的代码中,AddTwoNumber函数的功能是实现两个整数加法,因此定义一个整数用来保存加法的计算结果,之后利用传递进来的参数进行加法操作,并将结果保存在result 变量中,最后函数要将所得到的结果进行返回。
通过这些语句的操作,实现了函数的特定功能。

现在已经了解到定义一个函数应该使用怎样的语法格式,在定义函数时会有如下几种特殊的情况:

无参函数:
无参函数也就是没有参数。无参函数的语法格式如下:
返回值类型 函数名
{
函数体
}
通过代码来看一下无参函数。例如,使用上边的语法定义一个无参函数如下:
在这里插入图片描述
空函数

顾名思义,空函数就是没有任何内容的函数,也没有什么实际作用。空函数既然没有什么实际功能,那么为什么要存在呢?原因是空函数所处的位置是要放一个函数的,只是这个函数现在还未编好,用这个空函数先占一个位置,以后用一个编好的函数来取代它。空函数的形式如下:

空函数的形式如下:

类型说明符 函数名()
{
}
例如定义一个空函数,留出一个位置以后再添加其中的功能:
void ShowTime() //函数头
{
}

9.2.2 定义与声明

在程序中编写函数时,要先对函数进行声明,再对函数进行定义。

函数的声明是让编译器知道函数的名称、参数、返回值类型等信息。

函数的定义是让编译器知道函数的功能。函数声明的格式由函数返回值类型、函数名、参数列表和分号4部分组成,其形式如下:

返回值类型 函数名(参数列表);

此处要注意的是,在声明的最后要有分号“;”作为语句的结尾。例如,声明一个函数的代码如下:

Int ShowNumber(int iNumber);

例如在前面的实例中会看到这样的代码格式,在使用一个函数之前先进行声明。

例 9.2 函数的定义与声明

通过代码可以看到函数声明与函数定义的位置,及其在程序中的作用。

在这里插入图片描述(1)观察上面的程序,可以看到在 main 函数的开头进行 ShowNumber 函数的声明,声明的作用是告知其函数将在后面进行定义。
(2)在 main 函数体中,首先定义一个整型的变量 iShowNumber,之后输出一条提示消息。
(3)在消息提示下输入整型变量,最后调用 ShowNumber 函数进行输出操作,最后在 main 函数的定义之后就可以看到 ShowNumber 函数的定义。
运行程序,显示效果如图 9.4 所示。
在这里插入图片描述
注意:如果将函数的定义放在调用函数之前,就不需要进行函数的声明,此时函数的定义就包含了函数的声明。例如将上面的程序改为如下代码:
在这里插入图片描述
9.3 返回语句

在函数的函数体中常会看到这样一句代码:
return 0;
这就是返回语句。返回语句有以下两个主要用途:

1、利用返回语句能立即从所在的函数中退出,即返回到调用的程序中去。
2、返回语句能返回值。将函数值赋给调用的表达式中,当然有些函数也可以没有返回值,例如返回值类型为 void 的函数就没有返回值。
下面对这两个用途进行说明。

9.3.1 从函数返回

从函数返回就是返回语句的第一个主要用途。在程序中,有两种方法可以终止函数的执行,并返回到调用函数的位置。一种是在函数体中,从第一句一直执行到最后一句,当所有语句都执行完,程序遇到结束符号“}”后返回。

例9.3 从函数返回

通过一个简单的函数,在函数的适当位置输出提示信息,进而观察有关从函数的返回过程。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
有部分编译器,如果函数结束没有return 0;编译是不通过的。

9.3.2 返回值

通常调用者希望能调用其他函数得到一个确定的值,这就是函数的返回值。例如下面的代码:

在这里插入图片描述
从上面的代码中可以看到,首先定义了一个进行减法操作的函数 Minus,在 main 主函数中通过调用 Minus函数将计算的减法结果赋值给在main函数中定义的变量iResult。下面对函数进行说明:

(1)函数的返回值都通过函数中的 retum语句获得,retum 语句将被调用函数中的一个确定值返回到调用函数中,例如上面代码中 Minus 自定义函数的最后就是使用return 语句将计算的结果返回到主函数 main 调用的位置。

说明::return语句后面的括号是可以省略的,例如return 0和 return(0)是相同的,在本书的实例中都将括号进行了省略,因此在此对 retumn 进行说明。

(2)函数返回值的类型。既然函数有返回值,这个值当然应该是属于某一种确定的类型,因此应当在定义函数时明确指出函数返回值的类型。例如:
int Max(int iNum1,int iNum2);
double Min(double dNum1,double dNum2);
char Show(char cChar);

(3)如果函数值的类型和 return 语句中表达式的值不一致,则以函数返回值的类型为准。数值型数据可以自动进行类型转换,即函数定义的返回值类型决定最终返回值的类型。

例 9.4 返回值类型与return值类型

自定义的函数返回值类型与最终retum 语句返回值的类型不一致,但是通过类型转换后,函数的返回类型和定义类型一致。
在这里插入图片描述
(1)在程序代码中,首先为程序声明一个 ShowChar 函数,在主函数 main 中定义一个字符型的变量 cResult,调用自定义函数 ShowChar 得到返回的值,使用 printf函数将所得到的结果进行输出显示。
(2)在主函数 main 外是 ShowChar 函数的定义,在其函数体中定义的是一个整型变量,用户通过提示信息输入数据,最后将数据进行返回。
(3)在这里可以看到虽然在 ShowChar 函数中返回的是 int 型变量,但是由于定义时指定的返回值类型是 char 型,因此返回值是 char 类型。运行程序,显示效果如图 9.6 所示。

在这里插入图片描述
**

9.4 函数参数

**
在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系,这就是前面提到的有参数的函数形式。函数参数的作用是传递数据给函数使用,函数利用接收的数据进行具体的操作处理。函数参数在定义函数时放在函数名称的后面,如图9.7所示。
在这里插入图片描述
9.4.1 形式参数与实际参数

在使用函数时,经常会用到形式参数和实际参数。两者都叫做参数,那么二者有什么关系?二者之间的区别是什么?两种参数各自又起到什么作用?接下来通过形式参数与实际参数的名称和作用来进行理解,再通过一个比喻和实例进行深入理解。
1、通过名称理解
形式参数:按照名称进行理解就是形式上存在的参数。
实际参数:按照名称进行理解就是实际存在的参数

2、通过作用理解
形式参数:在定义函数时,函数名后面括号中的变量名称为“形式参数”。在函数调用之前,传递给函数的值将被复制到这些形式参数中。
实际参数:在调用一个函数时,也就是真正使用一个函数时,函数名后面括号中的参数为“实际参数”。函数的调用者提供给函数的参数叫实际参数。实际参数是表达式计算的结果,并且被复制给函数的形式参数。
通过图 9.8可以更好地理解。
在这里插入图片描述
形式参数简称形参,实际参数简称实参。

3、通过一个比喻来理解形式参数和实际参数

母亲拿来了一袋牛奶,将牛奶倒入一个空奶瓶中,然后喂宝宝喝牛奶。函数的作用就相当于宝宝用奶瓶喝牛奶这个动作,实参相当于母亲拿来的一袋牛奶,而空的奶瓶就相当于形参。牛奶放入奶瓶这个动作相当于将实参传递给形参,使用灌好牛奶的奶瓶就相当于函数使用参数进行操作的过程。

下面通过一个实例对形式参数和实际参数进行实际的讲解。

例 9.5 形式参数和与实际参数的比喻实现。
将上面的比喻进行了实际的模拟,希望读者可以一边实际动手操作,一边通过上面的比喻
对形式参数和实际参数加深理解,更好地掌握知识点。

在这里插入图片描述
现在根据上面的实例,一边理解一边对本程序进行讲解。
(1)首先声明程序中要用到的函数 DrinkMilk,在声明函数时 cBottle 变量称为形式参数,这就相当于之前母亲为孩子准备好的一袋牛奶。
(2)在主函数 main 中,定义一个字符数组变量用来保存用户输入的字符。
(3)通过 printf库函数显示信息,表示此时孩子饿了,妈妈应该喂孩子吃东西。
(4)使用 scanf库函数在控制台上输入字符串,将其字符串保存在 cPoke 变量中。
(5)cPoke 获得数据之后,调用 DrinkMilk 函数,将 cPoke 变量作为 DrinkMilk 函数的参数传递。此时的 cPoke 变量就是实际参数,而传递的对象就是形式参数。这就相当于妈妈把牛奶袋打开后,将
牛奶放入空奶瓶中。
(6)既然调用 DrinkMilk 函数,程序就会调转到 DrinkMik 函数的定义处。在函数定义的函数参数 cBottle 为形式参数,不过此时 cBotle 已经得到了 cPoke 变量传递给它的值。这样,在下面使用输出语句 printf 输出 cBottle 变量时,显示的数据就是 cPoke 变量保存的数据。此时就相当于使用灌满牛奶的奶瓶喂宝宝喝牛奶一样。
(7)DrinkMilk 函数执行完,回到主函数 main 中,retumn语句返回 0,程序结束。此时,宝宝已经喝饱了,妈妈就可以安心地做其他事情。
运行程序,显示效果如图 9.9 所示。
在这里插入图片描述
9.4.2 数组作函数参数

本节将讨论数组作为实参传递给函数的这种特殊情况。将数组作为函数参数进行传递,不同于标准的赋值调用的参数传递方法。
当数组作为函数的实参时,只传递数组的地址,而不是将整个数组赋值到函数中。当用数组名作为实参调用函数时,指向该数组的第一个元素的指针就被传递到函数中。
注意:C语言中没有任何下标的数组名,而是一个指向该数组第一个元素的指针。例如定义一个具有 10个元素的整型数组:
int Count[10]; //定义整型数组
其中的代码没有下标的数组名 Count与指向第一个元素的指针*Count 是相同的.

声明函数参数时必须具有相同的类型,根据这一点,下面将对使用数组作为函数参数的各种情况
进行详细的讲解。

1、数组元素作为函数参数

由于实参可以是表达式形式,数组元素可以是表达式的组成部分,因此数组元素可以作为函数的实参,与用变量作为函数实参一样,是单向传递。

例 9.6 数组元素作为函数参数

在实例中定义一个数组,然后将赋值后的数组元素作为函数的实参进行传递,当函数的形参得到实参传递的数值后,将其进行显示输出。
在这里插入图片描述
(1)在源文件的开始处为了下面要使用的函数进行声明,在主函数 main 的开始处首先定义一个整型的数组和一个整型变量i,变量i用于下面要使用的循环语句。
(2)变量定义完成之后要对数组中的元素进行赋值,在这里使用for循环语句,变量i作为循环语句的循环条件,并且作为数组的下标指定数组元素位置。
(3)通过一个循环语句调用 ShowMember 函数显示数据,其中可以看到i作为参数中数组的下标,表示指定要输出的数组元素。
运行程序,显示效果如图 9.10 所示。
在这里插入图片描述
2、数组名作为函数参数

可以用数组名作为函数参数,此时实参与形参都使用数组名。

例 9.7 数组名作为函数参数

通过使用数组名作为函数的实参和形参,实现与实例96同样的程序显示结果。
在这里插入图片描述
(1)首先是对程序中将要使用的函数进行声明操作,在声明语句中可以看到函数参数中是用数组的名作为参数名。
(2)在主函数 main 中,定义一个具有 10个元素的整型数组 iArray。
(3)定义整型数组之后,调用 Evaluate 函数,这时可以看到iAray 作为函数参数传递数组的地址。在 Evaluate 的定义中可以看到,通过使用形参iArrayName 对数组进行了赋值操作。
(4)调用 Evaluate 函数后,整型数组已经被赋值,此时又调用 Display 函数将其数组进行输出可以看到在函数参数中使用的也是数组名称。
运行程序,显示效果如图 9.11所示。
在这里插入图片描述
3、可变长度数组作为函数参数

可以将函数的参数声明成长度可变的数组,在此基础上利用上面的程序进行修改。声明方式的代码为:
void Function(iint iArrayName[]); //声明函数
int iArray[10]; //定义整型数组
Function(iArray); //将数组名作为实参进行传递
从上面的代码中可以看到,在定义和声明一个函数时将数组作为函数参数,并且没有指明数组此时的大小,这样就将函数参数声明为数组长度可变的数组。

例 9.8 可变长度数组作为函数参数

修改实例 9.7,使其参数为可变长度数组,通过两个程序的比较使读者对此加深印象。

在这里插入图片描述
本程序的执行过程与实例 9.7相似,只是在声明和定义函数参数时,使用的是可变长度数组的形式运行程序,显示效果如图 9.12 所示。
在这里插入图片描述
4、使用指针作为函数参数

最后一种方式是将函数参数声明为一个指针。前面的讲解中也曾提到,当数组作为函数的实参时只传递数组的地址,而不是将整个数组赋值到函数中去。当用数组名作为实参调用函数时,指向该数组的第一个元素的指针就被传递到函数中。

说明::将函数参数声明为一个指针的方法,也是C语言程序比较专业的写法。

例如声明一个函数参数为指针时,传递数组方法如下:

void Function(int* pPoint); //声明函数
int iArray[10]; //定义整型数组
Function(iArray); //将数组名作为实参进行传递

从上面的代码中可以看到,指针在声明Function 时作为函数参数。在调用函数时,可以将数组名
作为函数的实参进行传递。

例9.9 指针为函数参数

还是使用相同功能的实例,在之前实例程序的基础上进行修改,使之满足新的情况。

在这里插入图片描述
(1)在程序的开始处声明函数时,将指针声明为函数参数。
(2)主函数 main 中,首先定义一个具有 10 个元素的数组。
(3)将数组名作为 Evaluate 函数的参数。在 Evaluate 函数的定义中,可以看到定义函数参数也为指针。在 Evaluate 函数体内,通过循环对数组进行赋值操作。可以看到虽然 pPoint 是指针,但也可以使用数组的形式进行表示。
(4)在主函数 main 中调用 Display 函数进行显示输出操作。
运行程序,显示效果如图 9.13 所示。

在这里插入图片描述
9.4.3 main函数的参数

在前面介绍函数定义的内容中,曾在讲解函数体时提到过主函数 main 的有关内容,下面在此基础上对 main 函数的参数进行介绍。

在运行程序时,有时需要将必要的参数传递给主函数。主函数main的形式参数如下:

main(int argc,char* argv[])

两个特殊的内部形参 argc 和 argv 是用来接收命令行实参的,这是只有主函数 main 具有的参数。

argc 参数保存命令行的参数个数,是整型变量。这个参数的值至少是1,因为至少程序名就是0第一个实参。

argv 参数是一个指向字符指针数组的指针,这个数组中的每一个元素都指向命令行实参。所有命令行实参都是字符串,任何数字都必须由程序转变成为适当的格式.

例 9.10 main函数的参数使用

通过使用 main 函数的参数,将其程序的名称进行输入。
在这里插入图片描述
运行程序,显示效果如图 9.14 所示
在这里插入图片描述

函数-上主要介绍了一些函数的定义,声明,以及关于函数的形参实参的传递等,由于篇幅较长,将接下来的调用,递归调用等,放在下篇讲解。所涉及到的例程,会在函数-下附件。

感谢您在百忙之中,与培林一起学习关于C语言的内容,祝您有所收获。与此同时还请动动您的金手指,点个关注,点个赞,给在下一点点更新下去的动力。感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

培林将军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值