C语言中用函数实现模块化程序设计

为什么要用函数?

一段经常需要使用的代码封装起来,在需要使用时可以直接调用,这就是程序中的函数。

注意:所有函数都是平行的,且相对独立,一个函数并不从属于另一个函数,即函数不能嵌套定义。函数可以相互调用,但不能调用main函数。main函数是被操作系统调用的

如何声明和定义函数

这里需要理解两个概念(函数声明和函数定义):

函数的定义是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体。

也就是说:声明是为了让编译器正确处理对声明变量和函数的引用定义是一个给变量分配内存的过程,或者是说明一个函数具体干什么用

函数的分类

函数是C语言程序基本的组成单位,每个程序有且只能有一个主函数(main()),其他的函数都是子函数。按照不同的额分类标准,函数可以分成不同的类:

函数从是否有返回值,可以将函数分为又返回值和无返回值函数两种;
函数从是否需要参数,可以将函数分为有参和无参函数两种。在函数定义及函数声明时的参数,称为形参;在函数调用时的参数,称为实参。
在C语言中,一个函数的函数体中(包括main()函数),不能再定义另一个函数,即不能嵌套定义。但是允许函数之间的相互调用,也允许嵌套调用、递归调用。

函数的定义位置

在C语言程序中,**一个函数的定义可以放在在任何位置,既可以在main()函数之前,也可以在main()函数之后。**但其实这句话,仔细推敲是有很多的东西在的,还和函数的定义和声明有很大的关系

下面以几个例子来说明它们之间的关系:
例子1:

#include <stdio.h>
 
int main()
{
    fun1();
    return 0;
} 
 
void fun1()
{
    printf("Hello!\n");
}

此时程序出错,错误提示:fun1()函数未定义(假设外部返回int);fun1()函数重定义。
#include <stdio.h>

#include <stdio.h>
 
void fun1()
{
    
    
    printf("Hello!\n");
}
 
int main()
{
    
    
    fun1();
    return 0;
}

此时程序运行通过(即:在调用的函数前定义函数,此时可以不需要声明)。
例子3:


#include <stdio.h>
 
void fun1(void);
 
int main()
{
    
    
    fun1();
    return 0;
} 
 
void fun1()
{
    
    
    printf("Hello!\n");
}

此时程序运行通过(即:在调用的函数前声明函数,函数定义可以任意位置;函数声明也可以通过#include文件来包含)。
例子4:

#include <stdio.h>
 
int main()
{
    
    
    int a;
    a=fun1();
    return 0;
} 
 
int fun1()
{
    
    
    return 0;
}

此时程序运行通过。这段程序中fun1()函数同样没有声明,同时也在main()函数之后,为什么就又能运行通过呢?

解释:在C语言中,函数在调用前不一定非要声明。如果没有声明,那么编译器会自己主动依照一种隐式声明的规则,为调用函数确定函数类型。而这个隐式声明的函数类型为int(这是编译器假设的)!

总结;如果是在main()函数之后进行函数定义的,那么调用时就一定要在main()函数之前进行声明。如果不进行函数声明,编译器会假设返回值为int(隐式声明),除非函数返回值也为int才没有问题;
如果在自定义函数在main()函数之前定义的,就可以不需要进行声明。因为在调用之前,编译系统已经知道了被调函数的函数类型、参数个数、类型、顺序等

函数调用时的数据传递

形式参数和实际参数

在调用有参函数时,主调函数和被调函数之间有数据传递关系,从前面已知:在定义函数时函数名后面括号中的变量名的变量名称称为形式参数在主调函数中调用一个函数时,函数名后面括号中的参数
称为实际参数。

实参和形参之间的数据传递

在调用函数过程中,系统会把实际参数的值传递给被调用函数的形参。或者说,形参从实参得到一个值。该值在函数调用期间有效,可以参见函数中的运算。

说明
实参可以是常量,变量或者表达式。

实参和形参的类型应相同或赋值兼容。

函数的返回值

当函数被调用时,函数体内往往会执行一些执行语句,完成具体的操作。如果想将这些操作的结果返回给主调函数,那么就需要使用函数的返回值。
例子1:

#include<stdio.h>
 
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<stdio.h>
 
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<stdio.h>
 
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<stdio.h>
 
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<stdio.h>
 
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
请按任意键继续. . .

相比较之下,这就非常显而易见了。

在基本数据类型做函数参数时,所进行的值传送是单向的,即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同,而形参的值发生改变后,实参并不变化;
在数组名做函数参数时,由于实际上形参和实参为同一数组,因此当形参数组发生变化时,实参数组也随之变化。也就是说,所进行的值传送是双向的。

猜你喜欢

转载自blog.csdn.net/weixin_43491077/article/details/109899565