第5章 函 数 C语言属于结构化程序设计语言。结构化程序设计的主要思想是以模块化设计为中 心,将待开发的软件系统划分为若干相互独立、功能单一的模块,从而使每一个模块变得 简单而明确。由于模块之间相互独立,因此在设计一个模块时,该模块不会受到其他模块 的影响,可将原来较为复杂的问题简化为一系列简单模块的设计。在C语言中,模块功 能用函数来实现。 1C 语言函数概述 5. 视频讲解 C程序是由函数组成的。虽然在前面各章的程序中大多只有一个main函数,但实用 程序往往由多个函数组成。函数是C程序的基本模块,调用函数模块可以实现特定的功 能。C语言不仅提供了极为丰富的库函数,还允许用户自己定义函数。用户可以把求解 问题的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。可以说,C 程序的全部工作都是由各种各样的函数完成的,所以也将C语言称为函数式语言。 由于采用了模块式的结构,C语言易于实现结构化程序设计,使程序的层次结构清 晰,便于程序的编写、阅读和调试。 一个C程序可以由一个或多个函数组成,其中,必须有且只有一个main函数。main 函数也称为主函数。在C语言中,函数的定义是平行的,不允许嵌套定义函数,即不允许 在一个函数内再定义另一个函数,但函数可以嵌套调用。由主函数调用其他函数,其他函 数也可以再调用函数,同一个函数可以被多个函数调 用,如图5-1所示。对于两个有调用关系的函数而言, 一般用主调函数和被调函数来区分,调用其他函数的函 数称为主调函数。 在C程序中,一个函数的定义可以放在任意位置, 既可放在主函数main之前,也可放在main之后。但 C程序的执行总是从main函数开始,在main函数中调 用其他函数,调用完成后返回到main函数,并在main 函数中结束程序的运行。图5-1C 程序函数调用实例 第5 章 函数1 21 从函数定义的角度而言,函数分为库函数和用户自定义函数。C系统提供了大量的 库函数,在程序中可以直接调用,而程序中大部分函数是由用户按需定义的。 无论库函数还是用户自定义函数又可分为以下类型。 (1)有返回值函数和无返回值函数。有返回值函数被调用后将向主调函数返回一个 值;而无返回值函数用于完成某项特定的功能,执行完成后不向主调函数返回函数值。 (2)有参函数和无参函数。有参函数在进行函数调用时,主调函数向被调函数传送 数据;而对于无参函数,主调函数和被调函数之间不进行数据传送。 本章主要介绍用户自定义函数的定义及使用方法。 5.2 函数的定义 一个函数的完整定义包括两部分:一是函数首部;二是函数体。函数首部即函数定 义中的第一行,包括函数的数据类型、函数名和形式参数列表。函数体包括声明和语句两 部分,用一对大括号“{}”括起来,声明部分是对函数体内部所要用到的变量的类型说明, 语句部分是函数的执行部分。 5.2.1 函数定义的一般形式 1.无参函数的定义 无参函数的一般定义形式如下: 类型说明符函数名() { 声明部分 语句部分 } 其中,类型说明符说明函数的类型,即函数返回值的数据类型。如果函数定义时不指定函 数类型,系统默认函数类型为int型。如果函数调用时不需要带回函数值,可将函数类型 定义为void类型。函数名是由用户定义的合法标识符,在此函数名后的括号内为空,即 没有参数,所以称为无参函数。例如: void fun() { printf("This is a functional program.\n"); } fun函数是一个无参函数,其功能是输出"Thisisafunctionalprogram."字符串。由 于函数类型为void型,所以函数调用后不带回函数值。 2.有参函数的定义 有参函数的一般定义形式如下: 视频讲解 1 22 C/C++ 程序设计进阶教程(第2 版·微课视频版) 类型说明符函数名(形参列表) { 声明部分 语句部分 } 在有参函数中,函数名后面的括号内是形式参数(简称形参)的定义。形参可以是各 种类型的变量,每个形参必须有类型说明,各形参之间用逗号分隔。例如: int plus (int x,int y) //函数首部 { int z; //声明部分 z=x+y; //以下是语句部分 return z; } plus函数是一个有参函数,功能是求两个整型数据之和。该函数是一个整型函数, 由return语句返回一个整数。其形参x和y均为整型变量,其值是在plus函数被调用时 由主调函数的实参传递过来的。 注意:不能将intplus(intx,inty)写成intplus(intx,y),因为每个形参必须有类 型说明。 5.2.2 函数参数与函数返回值 1.实际参数和形式参数 函数的参数有两类,分别是形式参数和实际参数。形式参数出现在函数定义中,在函 数首部,函数名后面的括号中的参数称为形式参数,简称“形参”。实际参数出现在主调函 数中,在调用函数时,函数名后面的括号中的参数(也可为表达式)称为实际参数,简称“实 参”。当函数被调用时,主调函数将实参的值赋给被调函数的形参,从而实现数据的传递。 【例5-1】 函数间的参数传递。 #include int plus(int x,int y) { int z; z=x+y; return z; }i nt main() { int i,j,k; scanf("%d,%d",&i,&j); k=plus(i,j); 视频讲解 第5 章 函数1 23 printf("i+j=%d\n",k); return 0; } 输入: 3,4 运行结果: i+j=7 程序分析: 本程序定义main函数和plus函数两个函数,在main函数中调用plus函数。程序 图5-2 函数间参数传递示例 从main函数开始执行,当遇到plus(i,j)时,main函 数被中断,转去执行plus函数,将主调函数中实参的 值按照参数顺序分别传递给被调函数对应位置上的 形参,即将main函数中实参i的值传递给形参x,将 实参j的值传递给形参y,使两个函数中的参数之间 发生联系,如图5-2所示。plus函数调用结束后,返 回到main函数中,将plus函数返回值赋给变量k, 继续执行main函数中未被执行的语句,直到最后一条语句,至此整个程序执行完毕。 函数的实参和形参有以下特点。 (1)实参可以是常量、变量、表达式、函数和地址等,在进行函数调用时,实参必须有 确定的值,例如plus(8,i+j)。 (2)实参与形参的数量和顺序要严格一致,类型应相同或赋值兼容。如果实参与形 参类型不同,则要按第2章中介绍的不同类型数据的赋值规则进行转换,即把实参的类型 转换成形参的类型,然后送到形参中。 (3)实参向形参的数据传递是单向的“值传递”,即只把实参的值传递给形参,而形参 的值不会传回给实参。因此,在函数调用过程中,形参的值发生改变,而实参中的值不会 变化,因为实参与形参被分配到不同的存储单元。 (4)当调用函数时,形参才被分配存储单元,调用结束时,形参所占存储单元即被释 放,因此形参的有效使用范围仅限于本函数内。 【例5-2】 交换两个变量的值。 #include void swap(int a,int b) { int temp; temp=a; a=b; b=temp; printf("a=%d,b=%d\n",a,b); 1 24 C/C++ 程序设计进阶教程(第2 版·微课视频版) }i nt main() { int i=2,j=1; printf("i=%d,j=%d\n",i,j); swap(i,j); printf("i=%d,j=%d\n",i,j); return 0; } 运行结果: i=2,j=1 a=1,b=2 i=2,j=1 程序分析: 本程序定义了一个swap函数,其功能是交换形参a和b的值。在调用swap函数 时,为形参a和b分配存储单元,并将实参i和j的值分别传递给形参a和b,如图5-3(a) 所示。执行swap函数时,交换了形参a和b的值。由于实参i与对应的形参a是两个不 同的变量,占用不同的存储单元(同理,实参j与对应的形参b也占用不同的存储单元), 所以,虽然形参a和b两个变量值交换了,但实参i和j的值不随形参的变化而变化,如 图5-3(b)所示。在swap函数调用结束时,形参a和b所占的存储单元被释放,main函数 中的i和j并未互换,如图5-3(c)所示。 图5-3 实参和形参变化图 由此可见,当实参和形参为普通变量时,是将实参的值传递给形参,属于单向的“值传 递”方式,形参值的改变不会影响实参值。 2.函数的返回值 函数的返回值是指函数被调用结束后所带回并返给主调函数的一个值,即函数的值。 例如,调用plus(2,4)得到值6,即函数的返回值为6。 说明: (1)函数的值是由return语句返回给主调函数的。 return语句的一般形式如下: return 表达式; 视频讲解 第5 章 函数1 25 或 return (表达式); 该语句的功能是计算表达式的值作为函数返回值,终止函数的执行并返回到主调函 数。如果函数不需要返回值,表达式可以省略,改写为以下形式: return; 该语句的功能是终止函数的执行,并返回到主调函数。 一个函数可以有多个return语句,但每次调用只能有一个return语句被执行,因此 函数调用只能返回一个值。例如: int sign(int x) { if(x<0) return -1; else if(x==0) return 0; else return 1; } (2)如果不需要函数返回一个值,可以将函数定义为无类型或空类型,类型说明符为 void,这样,系统就能保证不使函数返回任何值。另外,在函数体中不能有以下语句: return 表达式; (3)函数的类型应该和return语句中表达式的类型一致,如果二者不一致,则以函数 类型为准,系统自动将表达式的类型转换为函数类型,即函数类型决定返回值类型。 【例5-3】 函数类型决定返回值类型。 #include int min(float i,float j) { float k; k=i int main() { long fac(int n); //fac 函数的声明 int n; long k; scanf("%d",& n); k=fac(n); printf("k=%ld\n",k); return 0; }l ong fac(int n) { long k=1; int i; for(i=1;i<=n;i++) k=k*i; return k; } 输入: 5 1 28 C/C++ 程序设计进阶教程(第2 版·微课视频版) 运行结果: k=120 以下两种情况可以省略对被调函数的声明。 (1)如果被调用函数的定义出现在主调函数之前,在主调函数中可以不必声明而直 接调用函数。 (2)对库函数的调用不必声明,但必须用include命令将相应的头文件放在源文件开 头,例如: #include #include 【例5-5】 对例5-4做简单的修改。 #include long fac(int n) { long k=1; int i; for(i=1;i<=n;i++) k=k*i; return k; }i nt main() { int n; long s,k; scanf("%d",&n); k=fac(n); printf("k=%ld\n",k); return 0; } 输入: 10 运行结果: k=3628800 程序分析: 由于被调用函数fac的定义出现在主调函数main之前,所以在main函数中省略了 对fac函数的声明。 另外,若在程序开头对各函数进行声明,则在之后出现的各函数中不必对被调用函数 进行声明,可直接使用,例如: 第5 章 函数1 29 #include char f1(char,char); int f2(int); float f3(float,float,float); int main() {…} char f1(char c1,char c2) {…} int f2(int a) {…} float f3(float x,float y,float z) {…} 5.4 函数的嵌套调用与递归调用 5.4.1 函数的嵌套调用 C语言不允许在一个函数定义中定义另一个函数,但允许调用另一个函数。函数的 图5-4 函数嵌套调用图 嵌套调用指在被调函数中又调用了其他函数,其 关系如图5-4所示。该图表示两层嵌套关系,即 main函数调用n函数,n函数又调用p函数。其 执行过程如下。 (1)程序从main函数开始执行,遇到调用n 函数时,main函数被中断,转去执行n函数。 (2)在n函数中调用p函数时,n函数被中 断,转去执行p函数。 (3)当p函数执行完后,返回n函数的断点, 继续执行后续语句。 (4)当n函数执行完后,返回main函数的断点,继续执行后续语句,直到main函数 执行完毕,至此整个程序执行完毕。 【例5-6】 求3个数中最大数和最小数的差值。 #include int dif(int x,int y,int z); //dif 函数声明 int max(int x,int y,int z); //max 函数声明 int min(int x,int y,int z); //min 函数声明 int main() { int a,b,c,d; scanf("%d%d%d",&a,&b,&c); 视频讲解