在编写程序过程中,如果程序的功能比较多,把所有程序的代码都集中在一个主函数(main函数)中,就会使主函数变得庞杂,使阅读和维护变得复杂。而函数就是为了解决这种问题。
1.定义函数
(1)为什么定义函数
C 语言要求,在程序中用到的所有函数,必须"先定义,后使用"。例如想用max函数去求两个数中的大者,必须事先按规范对它进行定义,指定它的名字、函数返回值类型、函数实现的功能以及参数的个数与类型,将这些信息通知编译系统。这样,在程序执行max时,编译系统就会按照定义时所指定的功能执行。
(2)函数的基本组成
①指定函数的名字,以便以后按名调用。
②指定函数的类型,即函数返回值的类型。
③指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项。
④指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能。
(3)函数的基本介绍
函数声明的作用是把有关函数的信息(函数名,函数类型,函数参数的个数与类型)通知编译系统,以便在编译系统对程序进行编译时,能进行准确的调用。
①函数分类:
(a)从用户的使用角度看,可分为两种:
a.库函数。它是由系统提供的,用户不必自己定义,可以直接使用它们。
b.用户自己定义的函数。
(b)从函数的形式来看,可分为两种:
a.无参函数。在调用函数时主调函数无需向被调用函数传递数据。
b.有参函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据。一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。
(4)定义函数的方法
①定义无参函数
定义无参函数的一般形式为:
类型名 函数名()
{
函数体
}
或
类型名 函数名(void)
{
函数体
}
其中,void表示“空”,即函数没有参数
②定义有参函数
类型名 函数名(形式参数表列)
{
函数体
}
注:在程序设计中有时会用到空函数,它的形式为:
类型名 函数名()
{
函数体
}
在主函数中插入空函数,是为了占一个位置,在之后往里面插新函数,时函数完成锦上添花。
2.调用函数
(1)调用函数的形式
调用函数的一般形式为:
函数名(实参表列)
如果是调用无参函数,实参表列可以没有。如果实参表列包含多个实参,则个参数之间用逗号隔开。
除了这种方式,还有三种函数调用方式。
①函数调用语句。如:
printf——star(); //这是一个无参函数
②函数表达式。如:
c=max(a,b);
③函数参数。
函数调用作为另一个函数调用的实参。如:
m=max(a,max(b,c));
(2)函数调用时的数据传递
①形式参数和实际参数
在调用有参函数时,主调函数和被调用函数之间有数据传递关系。
在定义函数时,函数名后面括号中的变量名称为“形式参数”(简称“形参”)或“虚拟参数”。在主调函数中调用一个函数时,函数名后面括号中的参数为“实际参数”(简称“实参”)。实际参数可以是常量、变量或表达式。
②实参和形参间的数据传递
在调用函数过程中发生的实参与形参间得的数据传递称为“虚实结合”。
实参可以是常量、变量或表达式。
实参和形参的类型应该相同或相互兼容。如果实参是float型而形参是int型,则按不同类型是指的赋值规则进行转换。如:实参a为float变量,值为3.5,而形参x为int型,则在传递过程中先将实数3.5转换为整数3,然后送到形参x。字符型与int型可以互相通用。
(3)函数调用的过程
①在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时,函数的形参才被临时分配内存单元。
②将实参的值传递给对应形参。
③在执行函数期间,由于形参已经有值,就可以利用形参进行有关的运算(例如把x和y比较,把x或y的值赋给 z 等)。
④通过 return 语句将函数值带回到主调函数。 应当注意返回值的类型与函数类型一致。
如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。
⑤调用结束,形参单元被释放。注意:实参单元仍保留并维持原值,没有改变。如果在执行一个被调用函数时,形参的值发生改变,不会改变主调函数的实参的值。
注意:实参向形参的数据传递是“值传递”,单向传递,只能由实参传递给形参,而不能由形参传递给实参。
(4)函数的返回值
通常希望通过函数调用能得到一个确定的值,这就是函数值(函数的返回值)。
①函数的返回值是通过函数中的return语句获得的。
return语句可以有两种表达方式:
return (z); 等价于 return z;
return后边还可以跟一个表达式。
②函数值的类型。既然函数有返回值,这个值当然应属于某一个确定的类型,应当在曾义函数时指定函数值的类型。例如下面是3个函数的首行:
int max(float x,float y); //函数值为整型
char letter(char cl ,char c2); //函数值为字符型
double min(int x ,int y); //函数值为双精度型
③在定义函数时指定的函数类型一般应该和 return 语句中的表达式类型一致。如果函数值的类型和 return 语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值类型。
3.对被调用的函数的声明和函数原型
在一个函数中调用另一个函数,必须具备如下条件:
①首先被调用的函数必须是已经定义过的函数(是库函数或是用户自己定义的函数)。
②如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息“包含”到本文件中来。
③如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(同一个文件中),应该在主调函数中对被调用函数做声明(declaration)。声明的作用是把函数名,函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。
函数的声明一般有两种:
①函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2,···,参数类型n 参数n);
如:
void input(int num,char sex,float score);
②函数类型 函数名(参数类型1 ,参数类型2 ,···,参数类型n ,);
如:
void input(int ,char ,float );
第二种方式比较精炼,第一种方式则更利于理解程序。
注意:对函数的"定义"和"声明"不是同一回事。函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如:函数名是否正确,实参与形参的类型和个数是否一致),它不包含函数体。
4.函数的嵌套调用
C语言的函数的定义是互相平行、独立的。也就是说,在定义函数时,一个函数中不得在定义另一个函数,即不可嵌套定义,但可以嵌套调用。即在调用一个函数中,又调用另一个函数。
5.函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。如:
int s(int x)
{
int y,z;
z=s(y); //在执行s函数时又调用s函数
return (3*z);
}
6.数组作为函数参数
一维数组名的本质:首元素地址。
(1)数组元素做函数实参
数组元素可以用作函数实参,但不能用作形参。因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。
(2)一维数组名做函数参数
数组名可以做函数的实参和形参。
用数组名作函数参数,应该在主调函数和被调用函数分别定义数组。同时,实参数组和形参数组类型应一致。形参数组可以不指定大小,在定义数组时在数组名后边跟一个空的方括号。如:
float average(float score[]);
(3)多维数组名做函数参数
可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。如:
int a[3][10]; 等价于 int a[][10];
但是不能把第二维或者更高维的大小说明省略。下面的定义是不合法的:
int s[][];
在第二维的大小相同的情况下,形参数组的第一位可以和实参数组不同。如:
实参: int s[3][4];
形参:int w[5][4];
二维数组不能只指定第一维的大小。下面的说法是不正确的:
int a[3][];
7.局部变量和全局变量
变量都有自己的作用域。
定义一个变量有三种情况:
①在函数开头定义
②在函数内的复合语句内定义
③在函数的外部定义
(1)局部变量
在一个函数内部定义的变量只在本函数范围内有效,在复合语句中定义的变量只能在本复合语句中有效,以上这些称为“局部变量”。
注:
①在主函数中定义的变量只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
②不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
③形式参数也是局部变量。
在函数内定义的变量为局部变量。
(2)全局变量
在函数之外定义的变量称为外部变量,外部变量也称全局变量(也称全程变量)。全局变量可以为本文件中其他函数所用。它的有效范围是从定义变量的位置开始到本源文件结束。
8.变量的存储方式和生存期
(1)动态存储方式和静态存储方式
静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。
内存中供用户使用的存储空间可分为三部分:程序区,静态存储区,动态存储区。
全局变量全部存放在静态存储区。在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。
在动态存储区中存放以下数据:
①函数形式参数。在调用函数时给形参分配存储空间。
②函数中定义的没有用关键字 static 声明的变量,即自动变量(详见后面的介绍)。
③函数调用时的现场保护和返回地址等。
对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的。
在C语言中,每一个变量和函数都有两个属性,数据类型和数据的存储类别。
数据类型指整型,浮点型等,存储类别指数据在内存中的存储方式,如静态存储和动态存储。
C语言的存储类别有四种,自动的(auto),静态的(static),寄存器的(register),外部的(extren).
(2)局部变量的存储类别
①自动变量(auto)
函数中的局部变量,如果不专门声明为 static (静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中,函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量,自动变量用关键字 auto 作存储类别的声明。
实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”。如:
int a,b=2; 等价于 auto int a,b=3;
②静态局部变量(static)
如果要使函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即占用的存储单元不释放,在下一次调用时该变量已有值,就定义static变量。
③寄存器变量(register)
如果有一些变量使用频繁(例如,在一个函数中执行100000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花费不少时间。为提高执行效率,允许将局部变量的值放在CPU中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做寄存器变量。
④全局变量
一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时医序设计人员希望能扩展外部变量的作用域。有以下几种情况。
(a)在一个文件内扩展外部变量的作用域
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字 extern 对该变量作"外部变量声明",表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从"声明 处起,合法地使用该外部变量。
(b)将外部变量的作用域扩展到其他文件
如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量,不能分别在两个文件中各自定义一个外部变量,否则在进行程序的连接时会出现"重复定义"的错误。正确的做法是:在任一个文件中定义外部变量,而在另一文件中用 extern 对该外部变量作"外部变量声明,即"extern 外部变量"。在编译和连接时,系统会由此知道该外部变量有"外部链接",可以从别处找到已定义的外部变量,并将在另一文件中定义的外部变量的作用域扩展到本文件,在本文件中可以合法地引用外部变量。
(c)将外部变量的作用域限制在本文件中
加上static声明,可以将变量限制在本文件范围内,这种外部变量称为静态外部变量。
9.内部函数和外部函数
(1)内部函数
如果一个函数只能被本文件中的其他函数所调用,称为内部函数(静态函数)。即:
static 类型名 函数名(形参表)
如:
static int s(int a,int b)
(2)外部函数
可供其他函数调用的函数称为外部函数。即:
extern int s(int a,int b)
在别的文件中调用此函数时,需要对此函数进行声明,并且加上关键字extern。