在前面我们都是在一个函数里面进行编程,到这里将跳到main函数外进行编写其他函数(main函数中的一些算法),之后在main函数中需要一个算法直接调用前面编写的函数,目的是为了提高函数的耦合性和复用性,注意:在main函数中调用的函数称为被调函数,main函数为主调函数,被调函数一定要在主调函数之前。
(1)一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对较大的程序,一般不希望把所有内容全放在一个文件中,而是将它们分别放在若干个源文件中,再由若干个源程序文件组成一个C程序。这样便于分别编写、分别编译,提高调
试效率。一个源程序文件可以为多个C程序共用。
(2)一个源程序文件由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
(3)C程序的执行是从main函数开始的,如是在main函数中调用其他函数,在调用
后流程返回到main函数,在main函数中结束整个程序的运行,(4)所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用 main函数。main函数是系统调用的,
(5)从用户使用的角度看,函数有两种:
①标准函数。标准函数即库函数,它是由系统提供的,用户不必自己定义而直接使用它们。应该说明,不同的C语言编译系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的。
②用户自己定义的函数。它是用以解决用户专门需要的函数。
(6)从函数的形式看,函数分两类。①无参函数。如例8.1中的printstar 和print_message 就是无参函数。在调用无参函数时,主调函数不向被调用函数传递数据。无参函数一般用来执行指定的一组操作。例如,例8.1程序中的printstar函数的作用是输出16个星号。无参函数可以带回或不带回函数值,但一般以不带回函数值的居多。
②有参函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。
1.函数的定义
形式:无参函数:
有参函数:
空函数:
2.函数的参数
在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。这就是前面提到的有参函数。前面已提到:在定义函数时函数名后面括号中的变量名称为“形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括号中的参数(可以是一个表达式)称为“实际参数”简称“实参”)。
3.函数的调用
形式:函数名(实参表列)
如果是调用无参函数,则“实参表列”可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。
函数调用的方式
(1)、函数语句
把函数调用作为一个语句。
(2)、函数表达式
函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。
(3)、函数参数
函数调用作为一个函数的实参。
对被调用函数的声明和函数原型
在一个函数中调用另一个函数(即被调用函数)需要具备的条件如下。
(1)首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。
(2)如果使用库函数,还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明。
函数原型的一般形式有两种,分别为:
(1)函数类型 函数名(参数类型1,参数类型2,…,参数类型n);
(2)函数类型 函数名(参数类型1,参数名1,参数类型2,参数名2,…,参数类型n,参数名n);
说明:
(1)以前的C语言版本的函数声明方式不是采用函数原型,而只声明函数名和函数类型。
(2)如果被调用函数的定义出现在主调函数之前,可以不必加以声明。
(3)如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明。
(4)如果被调用的函数类型为整型,C语言允许在调用函数前不必作函数原型声明。
4.函数的嵌套调用
C语言的函数定义是互相平行、独立的。在定义函数时,一个函数内不能包含另一个函数。C语言不能嵌套定义函数,但可以嵌套调用函数。
两层嵌套(包括main函数共3层函数),其执行过程是:
(1)执行main函数的开头部分;
(2)遇函数调用语句,调用函数a,流程转去a函数;
(3)执行a函数的开头部分;
(4)遇函数调用语句,调用函数b,流程转去函数b;
(5)执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作;
(6)返回到a函数中调用b函数的位置;
(7)继续执行a函数中尚未执行的部分,直到a函数结束;
(8)返回main函数中调用a函数的位置;
(9)继续执行main函数的剩余部分直到结束。
5.函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。
6.数组作为函数参数
1)数组元素作函数实参
由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素当然可以作为函数的实参,与用变量作实参一样,是单向传递,即“值传送”方式。
2)数组名作函数参数
可以用数组名作函数参数,此时形参应当用数组名或用指针变量。
说明:
(1)用数组名作函数参数,应该在主调函数和被调用函数分别定义数组。
(2)实参数组与形参数组类型应一致(今都为float型),如不一致,结果将出错。
(3)在被调用函数中声明了形参数组的大小为10,但在实际上,指定其大小是不起任何作用的。
(4)形参数组可以不指定大小,在定义数组时在数组名后面跟一个空的方括号。
(5)最后应当强调说明一点:用数组名作函数实参时,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元。
3)多维数组名作函数参数
多维数组元素可以作函数参数,可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。
7.局部变量和全局变量
1)局部变量
在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。这称为“局部变量”。
说明:
(1)主函数中定义的变量(m,n)也只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
(2)不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。
(3)形式参数也是局部变量。例
(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为“分程序”或“程序块”。
2)全局变量
程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
说明:
(1)、设置全局变量的作用是增加了函数间数据联系的渠道。
(2)、建议不在必要时不要使用全局变量。
①全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
②它使函数的通用性降低了,因为函数在执行时要依赖于其所在的外部变量。
③使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部 变量的值。
(3)如果在同一个源文件中,外部变量与局部变量同名,则在局部变量的作用范围内,外部变量被“屏蔽”,即它不起作用。
7.变量的存储类别
1)动态存储方式与静态存储方式
从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量。
可以从另一个角度,从变量值存在的时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。
所谓静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。
在动态存储区中存放以下数据:
①函数形式参数。在调用函数时给形参分配存储空间。
②自动变量(未加static声明的局部变量,详见后面的介绍)。
③函数调用时的现场保护和返回地址等。
2)auto变量
函数中的局部变量,如果不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。
3)用static声明局部变量
有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。
对静态局部变量的说明:
(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放。
(2)对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3)如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
(4)虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。
需要用局部静态变量的情况如下:
(1)需要保留函数上一次调用结束时的值。
(2)如果初始化后,变量只被引用而不改变其值,则这时用静态局部变量比较方便,以免每次调用时重新赋值。
4)register变量
一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
说明:
(1)只有局部自动变量和形式参数可以作为寄存器变量,其他(如全局变量)不行。
(2)一个计算机系统中的寄存器数目是有限的,不能定义任意多个寄存器变量。
(3)局部静态变量不能定义为寄存器变量。
5)用extern声明外部变量
外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。编译时将外部变量分配在静态存储区。有时需要用extern来声明外部变量,以扩展外部变量的作用域。
(1)、在一个文件内声明外部变量
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。
(2)、在多文件的程序中声明外部变量
如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一个文件中定义外部变量Num,而在另一文件中用extern对Num作“外部变量声明”。
6)用static声明外部变量
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。
7、关于变量的声明和定义
对变量而言,声明与定义的关系稍微复杂一些。在声明部分出现的变量有两种情况:一种是需要建立存储空间的(如:int a;),另一种是不需要建立存储空间的(如:externa;)。前者称为“定义性声明”(defining declaration),或简称定义(definition)。后者称为“引用性声明”(referencing declaration)。
8.内部函数和外部函数
1)内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:
static 类型标识符 函数名(形参表);
2)外部函数
(1)在定义函数时,如果在函数首部的最左端加关键字extern,则表示此函数是外部函数,可供其他文件调用。
(2)在需要调用此函数的文件中,用extern对函数作声明,表示该函数是在其他文件中定义的外部函数。