5.1 函数的基本概念
函数是从英文 function 翻译过来的,其实,function 在英文中的意思既是 "函数",也是 "功能"。从本质意义上来说,函数就是用来完成一定的功能的。这样,对函数的概念就很好理解了,所谓函数名就是给该功能起一个名字,如果该功能是用来实现数学运算的,就是数学函数。
注意:函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能。
5.2 函数的调用、结构和定义
5.2.1 函数的定义
- 无参函数的定义一般形式
类型标识符 函数名 ()
{
声明部分
语句部分
}
- 有参函数定义的一般形式
类型标识符 函数名 (形式参数表列)
{
声明部分
语句部分
}
- 空函数的一般形式
类型标识符 函数名 ()
{}
5.2.2 函数的结构
- 形式参数:函数名后面括号中的变量名称为 "形式参数"(简称 "形参")。
- 实际参数:主调函数中调用一个函数时,函数名后面括号中的参数(可以是一个表达式)称为 "实际参数"(简称 "实参")。
- 函数返回值:return 后面的括号中的值作为函数带回的值(称函数返回值)。
5.2.3 函数的调用
- 调用形式:
函数名(实参表列)
注意:
- 如果是调用无参函数,则 "实参表列" 可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开。
- 如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。
- 如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。
按函数调用在程序中出现的形式和位置来分,可以有以下 3 种函数调用方式。
- 函数调用语句
把函数调用单独作为一个语句。如printf_ star();
,这时不要求函数带回值,只要求函数完成一定的操作。 - 函数表达式
函数调用出现在另一个表达式中,如c = max(a,b);
,max(a,b)
是一次函数调用,它是赋值表达式中的一部分。这时要求函数带回一个确定的值以参加表达式的运算。例如:c = 2 * max(a,b);
- 函数参数
函数调用作为另一个函数调用时的实参。例如:m = max(a,max(b,c));
其中max(b,c)
是一次函数调用,它的值作为 max 另一次调用的实参。经过赋值后,m 的值是 a,b,c 三者中的最大者。
在一个函数中调用另一函数(即被调用函数)需要具备的条件:
- 首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。
- 如果使用库函数,还应该在本文件开头用
#include
命令将调用有关库函数时所需用到的信息 "包含" 到本文件中来。 - 如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面,应该在主调函数中对被调用的函数作声明。
函数原型的一般形式为:
-
函数类型 函数名(参数类型1,参数类型2……);
-
函数类型 函数名(参数类型1,参数名1,参数类型2,参数名2……);
函数的定义和声明的区别:
-
函数的定义是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。
-
函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
-
声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。
5.3 函数的调用关系和返回值
主调函数和被调用函数之间有数据传递的关系。在不同的函数之间传递数据,可以使用的方法有:
- 参数:通过形式参数和实际参数。
- 返回值:用 return 语句返回计算结果。
- 全局变量:外部变量。
5.3.1 函数的调用过程
- 在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时,函数 max 的形参被临时分配内存单元。
- 将实参对应的值传递给形参。如上图所示,实参的值为 2,把 2 传递给相应的形参 x,这时形参 x 就得到值 2,同理,形参 y 得到值 3。
- 在执行 max 函数期间,由于形参已经有值,就可以利用形参进行有关的运算(例如,把 x 和 y 比较,把 x 或 y 的值赋给 z 等)。
- 通过 return 语句将函数值带回到主调函数。上图例子中,return 语句中指定的返回值是 z,这个 z 就是函数max 的值(又称返回值)。执行 return 语句就把这个函数返回值带回主调函数 main。应当注意返回值的类型与函数类型一致。如 max 函数为 int 型,返回值是变量 z,也是 int 型。二者一致。
如果函数不需要返回值,则不需要 return 语句。这时函数的类型应定义为 void 类型。 - 调用结束,形参单元被释放。注意:实参单元仍保留并维持原值,没有改变。如果在执行一个被调用函数时,形参的值发生改变,不会改变主调函数的实参的值。例如,若在执行 max 函数过程中 x 和 y 的值变为 10 和 15,但 a 和 b 仍为 2 和 3。这是因为实参与形参是两个不同的存储单元。
注意:
实参向形参的数据传递是 "值传递",单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占有不同的存储单元,实参无法得到。
5.3.2 函数的返回值
-
函数的返回值是通过函数中的 return 语句获得的。
return 语句将被调用函数中的一个确定值带回到主调函数中去(见上图的从 return 语句返回的箭头)。如果需要从被调用函数带回一个函数值(供主调函数使用),被调用函数中必须包含 return 语句。如果不需要从被调用函数带回函数值可以不要 return 语句。
一个函数中可以有一个以上的 return 语句,执行到哪一个 return 语句,哪一个 return 语句就起作用。return 语句后面的括号可以不要,如return z;
与return(z);
等价。return 后面的值可以是一个表达式。 -
函数值的类型。 既然函数有返回值,这个值当然应属于某一个确定的类型,应当在定义函数时指定函数值的类型。
-
在定义函数时指定的函数类型一般应该和 return 语句中的表达式类型一致。 例如,指定 max 函数值为整型,而变量 z 也被指定为整型,通过 return 语句把 z 的值作为 max 的函数值,由 max 带回主调函数。z 的类型与 max 函数的类型是一致的,是正确的。
如果函数值的类型和 return 语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。 -
对于不带回值的函数,应当用定义函数为 "void类型"(或称 "空类型")。 这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。此时在函数体中不得出现 return 语句。
5.4 局部变量和全局变量
5.4.1 局部变量:
- 定义:在一个函数内部定义的变量称内部变量。它只在本函数范围内有效,即:只有在本函数内才能使用这些变量,故称为 "局部变量" 。
注意:
主函数中定义的变量只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。
形式参数也是局部变量。
在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为 "分程序" 或 "程序块"。
5.4.2 全局变量:
- 定义:函数之外定义的变量称为外部变量。外部变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。所以也称 "全程变量"。
注意:不必要时不要使用全局变量,原因如下:
- 全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
- 使用全局变量过多,会降低程序的清晰性。在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
- 降低函数的通用性。因为函数在执行时要依赖于其所在的外部变量。如果将一个函数移到另一个文件中,还要将有关的外部变量及其值一起移过去。但若该外部变量与其他文件的变量同名时,就会出现问题,降低了程序的可靠性和通用性。一般要求把 C 程序中的函数做成一个封闭体,除了可以通过 "实参——形参" 的渠道与外界发生联系外,没有其他渠道。
5.4.3 变量的存储方式(非考纲要求)
-
从变量的作用域(即从空间)角度来分,变量可以分为全局变量和局部变量。
-
从变量值存在的时间角度来分,变量又可以分为静态存储方式和动态存储方式。
-
静态存储方式:指在程序运行期间由系统分配固定的存储空间的方式。
-
动态存储方式:则是在程序运行期间根据需要进行动态的分配存储空间的方式。这个存储空间可以分为三部分:1. 程序区、2. 静态存储区、3. 动态存储区
-
C 语言中,变量和函数有两个属性:数据类型 和 数据的存储类别。存储类别指的是数据在内存中存储的方式。
存储方式分为两大类:静态存储类和动态存储类。包含 4 种:自动的(auto)、静态的(statis)、寄存器的(register)、外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。
-
auto 变量:不专门声明为 static 存储类别的局部变量都是动态分配存储空间,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。
- 函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类。
- 自动变量用关键字 auto 作存储类别的声明。
- 例如:
int f(int a) /*定义f函数,a为形参 */
{
auto int b, c = 3; /*定义b、c为自动变量 */
}
-
static 声明局部变量:当函数中的局部变量的值在函数调用结束后不消失而保留原值时,该变量称为静态局部变量。用关键字static进行声明。
- 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放。
- 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。
- 如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值 0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。
- 虽然静态局部变量在函数调用结束后仍然存在,但其他函数不能引用它。
-
register 变量:变量的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。 经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
- 如果有一些变量使用频繁,则为存取变量的值要花费不少时间。为提高执行效率,C 语言允许将局部变量的值放在 CPU 中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做 寄存器变量,用关键字 register 作声明。
-
extern 声明外部变量:外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。编译时将外部变量分配在静态存储区。用 extern 来声明外部变量,以扩展外部变量的作用城。
- 在程序设计中,某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个 staitic 声明。
变量的声明和定义:
- 定义性声明:需要建立存储空间的(如:int a; )声明。
- 引用性声明:不需建立存储空间的声明(extern a;)。
- 声明包括定义,但并非所有的声明都是定义。对
int a;
而言,它既是声明,又是定义。而对extern a;
而言,它是声明而不是定义。
5.4.4 内部函数和外部函数(非考纲要求)
根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。
- 内部函数:如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加 static。即
static类型标识符 函数名(形参表)
。例如:static int fun(int a, int b)
- 外部函数:
- 定义函数时,如果在函数首部的最左端加关键字
extern
,则表示此函数是外部函数,可供其他文件调用。例如,函数首部可以写为extern int fun (int a, int b)
,这样,函数 fun 就可以为其他文件调用。如果在定义函数时省略 extern,则隐含为外部函数。 - 在需要调用此函数的文件中,用 extern 对函数作声明,表示该函数是在其他文件中定义的外部函数 。
- 定义函数时,如果在函数首部的最左端加关键字
5.5 函数参数的传递
- 数组元素作函数实参:由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素可以作为函数的实参,与用变量作实参一样,是单向传递,即 "值传送" 方式。
- 数组名作函数参数:用数组名作函数参数时,此时形参应当用数组名或用指针变量 。
- 多维数组名作函数参数:用多维数组名作为函数实参和形参。在被调函数中对形参数组定义时可以指定每一维的大小 。
5.6 标准库函数
5.7 递归函数
- 函数的嵌套调用:在定义一个函数时,其函数体内又包含另一个函数的完整定义 。
- 函数的递归调用:在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C 语言的特点之一就在于允许函数的递归调用。
评论(没有评论)