将一段经常需要使用的代码封装起来,在需要使用时可以直接调用,这就是程序中的函数。
注意:所有函数都是平行的,且相对独立,一个函数并不从属于另一个函数,即函数不能嵌套定义。函数可以相互调用,但不能调用main函数。main函数是被操作系统调用的。
这里需要理解两个概念(函数声明和函数定义):
函数的定义是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体。
也就是说:声明是为了让编译器正确处理对声明变量和函数的引用;定义是一个给变量分配内存的过程,或者是说明一个函数具体干什么用。
函数是C语言程序基本的组成单位,每个程序有且只能有一个主函数(main()),其他的函数都是子函数。按照不同的额分类标准,函数可以分成不同的类:
函数从是否有返回值,可以将函数分为又返回值和无返回值函数两种;
函数从是否需要参数,可以将函数分为有参和无参函数两种。在函数定义及函数声明时的参数,称为形参;在函数调用时的参数,称为实参。
在C语言中,一个函数的函数体中(包括main()函数),不能再定义另一个函数,即不能嵌套定义。但是允许函数之间的相互调用,也允许嵌套调用、递归调用。
在C语言程序中,**一个函数的定义可以放在在任何位置,既可以在main()函数之前,也可以在main()函数之后。**但其实这句话,仔细推敲是有很多的东西在的,还和函数的定义和声明有很大的关系。
下面以几个例子来说明它们之间的关系:
例子1:
#include
int main()
{
fun1();
return 0;
}
void fun1()
{
printf("Hello!\n");
}
此时程序出错,错误提示:fun1()函数未定义(假设外部返回int);fun1()函数重定义。
#include
#include
void fun1()
{
printf("Hello!\n");
}
int main()
{
fun1();
return 0;
}
此时程序运行通过(即:在调用的函数前定义函数,此时可以不需要声明)。
例子3:
#include
void fun1(void);
int main()
{
fun1();
return 0;
}
void fun1()
{
printf("Hello!\n");
}
此时程序运行通过(即:在调用的函数前声明函数,函数定义可以任意位置;函数声明也可以通过#include文件来包含)。
例子4:
#include
int main()
{
int a;
a=fun1();
return 0;
}
int fun1()
{
return 0;
}
此时程序运行通过。这段程序中fun1()函数同样没有声明,同时也在main()函数之后,为什么就又能运行通过呢?
解释:在C语言中,函数在调用前不一定非要声明。如果没有声明,那么编译器会自己主动依照一种隐式声明的规则,为调用函数确定函数类型。而这个隐式声明的函数类型为int(这是编译器假设的)!
总结;如果是在main()函数之后进行函数定义的,那么调用时就一定要在main()函数之前进行声明。如果不进行函数声明,编译器会假设返回值为int(隐式声明),除非函数返回值也为int才没有问题;
如果在自定义函数在main()函数之前定义的,就可以不需要进行声明。因为在调用之前,编译系统已经知道了被调函数的函数类型、参数个数、类型、顺序等。
形式参数和实际参数
在调用有参函数时,主调函数和被调函数之间有数据传递关系,从前面已知:在定义函数时函数名后面括号中的变量名的变量名称称为形式参数。在主调函数中调用一个函数时,函数名后面括号中的参数
称为实际参数。
实参和形参之间的数据传递
在调用函数过程中,系统会把实际参数的值传递给被调用函数的形参。或者说,形参从实参得到一个值。该值在函数调用期间有效,可以参见函数中的运算。
说明:
实参可以是常量,变量或者表达式。
实参和形参的类型应相同或赋值兼容。
当函数被调用时,函数体内往往会执行一些执行语句,完成具体的操作。如果想将这些操作的结果返回给主调函数,那么就需要使用函数的返回值。
例子1:
#include
void add(int a);
int main()
{
int a=0;
add(a);
printf("%d\n", a);
return 0;
}
void add(int a) {
a++;
}
程序的返回值为0,也就是说外部函数的各种操作的结果,只能通过返回值给主调函数。
例子2:
#include
void add();
int a = 0;
int main()
{
add();
printf("%d\n", a);
return 0;
}
void add() {
a++;
}
程序的返回值为1,此时通过全局变量(绕过返回值),实现函数之间的数据通信和交换。
当然除了这些方法之外,还有一个办法可以完成将函数内操作的结果返回给主调函数,就是利用指针。不利用指针,函数的形参都是临时分配内存空间,出了函数就自动销毁。而指针,是针对地址而言的,相当于直接操作原本的数据。
说明:
1)函数的返回值是通过函数中的return语句获得的。
2)函数值的类型:既然函数有返回值,这个值当然有有属于某一确定的类型,应该在定义函数时指定函数值的类型。
3)在定义函数时指定的函数类型一般于return语句中的表达式类型一致。
4)对于不带返回值的函数,应当定义函数为void类型。
如果在调用一个函数的过程中,又直接或间接地调用了该函数本身,这种形式称为函数的递归调用,而这个函数就称为递归函数。递归函数分为直接递归和间接递归两种。
直接递归就是函数在处理过程中又直接调用了自己;
间接递归就是函数p调用函数q,而函数q又反过来调用函数p。
但是运行递归函数会无休止地调用自身,为了防止这一点,必须在函数体内有终止递归调用的手段。常用的办法是加上条件判断,满足某种条件之后就不再进行递归调用。怎么才能不进行递归呢?return关键字的使用,如果有返回值,直接return返回值;如果没有返回值,就直接return;就可以了。
递归的方法实现Fibonacci数列的第10个数(斐波那契数列:1 1 2 3 5 8 13 21 34……):
#include
long fibonacci(int n);
int main()
{
long y;
y = fibonacci(10);
printf("%d\n", y);
return 0;
}
long fibonacci(int n) {
if (n == 1 || n == 2) {
return 1;
}else {
return (fibonacci(n - 1) + fibonacci(n - 2));
}
}
终止递归调用通常情况下,我们使用的是递归到最里层的值(return 初值),而不是选取在递归终点的时候return 终值。
数组可以作为函数的参数只用,进行数据传送。数组用作函数参数有两种形式:一种是把数组元素作为实参使用;另一种是把数组名作为函数的形参和实参使用。
数组元素,与普通的变量并无区别,因此它作为函数实参使用与普通变量是完全相同的;
数组名作为函数参数时,则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。
在用数组名作为函数参数时,不是进行值的传送,即不是把实参数组的每个元素的值都赋予形参数组的每个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。
那么,数据的传送是如何实现的呢?我们曾介绍道,数组名就是数组的首地址。因此在数组名作为函数参数时所进行的传送只是地址的传送,也就是说把是参数组的首地址赋予形参数组名。形参数组名取得到该首地址之后,也就等于拥有了实在的数组。实际上是形参数组和是参数组为同一数组,共同拥有一段内存区间。
同时,在函数形参上,允许不给出形参数组的额长度,或者用一个变量来表示数组的元素个数:
void order(int a[5]); //函数声明
int x[10];
order(x); //函数调用
void order(int a[]); //函数声明
int x[10];
order(x); //函数调用
void order(int a[],int n); //函数声明
int x[10];
order(x,10); //函数调用
这就有一个很有意思的点了:基本数据类型和数组分别作为函数参数,一个是分配内存单元,用完就销毁;一个是不分配内存单元,直接操作地址。这两种方式有什么区别呢?来看两个很简单的比较两个数的大小的例子:
利用基本数据类型:
#include
void order(int a, int b);
int main()
{
int x, y;
scanf_s("%d %d", &x, &y);
order(x, y);
printf("previous:%d %d\n", x, y);
return 0;
}
void order(int a, int b) {
int t;
if (a > b) {
t = a;
a = b;
b = t;
}
printf("result:%d %d\n", a, b);
}
这段程序运行的结果为:
5 3
result:3 5
previous:5 3
请按任意键继续. . .
利用数组:
#include
void order(int a[]);
int main()
{
int x[2];
scanf_s("%d %d", &x[0], &x[1]);
order(x);
printf("previous:%d %d\n", x[0], x[1]);
return 0;
}
void order(int a[]) {
int t;
if (a[0] > a[1]) {
t = a[0];
a[0] = a[1];
a[1] = t;
}
printf("result:%d %d\n", a[0], a[1]);
}
这段程序运行的结果为:
5 3
result:3 5
previous:3 5
请按任意键继续. . .
相比较之下,这就非常显而易见了。
在基本数据类型做函数参数时,所进行的值传送是单向的,即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同,而形参的值发生改变后,实参并不变化;
在数组名做函数参数时,由于实际上形参和实参为同一数组,因此当形参数组发生变化时,实参数组也随之变化。也就是说,所进行的值传送是双向的。