函数
- C语言
- 2024-03-27
- 4363热度
- 0评论
函数
函数的声明与定义
函数间的调用关系:由主函数调用其他函数,其他函数可以互相调用。同一个函数可以被一个或多个函数调用任意次。
C 语言的头文件(Header Files)是包含函数原型、常量定义、数据类型声明等信息的文件,其目的是提供给程序员在编写代码时使用的一些基本信息和接口。头文件通常包含在 C 语言程序的源代码中,并通过 #include
预处理指令将其包含到源文件中。
头文件的主要作用包括:
-
声明函数原型:头文件中通常包含函数的原型声明,这样在源文件中就可以直接调用函数而无需提供函数的实现细节。这样可以将函数的接口与实现分离,提高代码的可读性和可维护性。
-
定义常量:头文件中可以定义常量,这些常量可以在程序的多个源文件中共享使用,提高了代码的灵活性和可重用性。
-
声明数据类型:头文件可以声明自定义的数据类型、结构体和枚举类型,使得这些类型可以在程序的多个源文件中使用。
-
包含其他头文件:头文件可以包含其他头文件,通过这种方式可以将不同的功能模块进行组合和封装,提高了代码的模块化和可维护性。
-
提供外部接口:头文件中定义的函数原型和数据类型声明提供了程序的外部接口,使得其他开发者可以使用这些接口来调用函数和操作数据。
总的来说,头文件在 C 语言程序中起着承上启下的作用,它们提供了程序所需的基本信息和接口,同时也帮助组织和管理程序的结构,使得代码更加清晰和易于维护。
下面函数嵌套调用例子中,func.c文件包含了子函数print_start()和print_message()的定义,main.c文件是主函数,func.h中存放标准头文件声明和main函数中调用的两个子函数声明,如果不在头文件中对使用的函数进行声明,那么在编译时会出现警告。
func.c
#include "func.h"
int print_start(int i)
{
printf("***************\n");
printf("print_start %d\n",i);
return i+3;
}
void print_message()
{
printf("how do you do\n");
print_start(3);
}
func.h
#include <stdio.h>
#ifndef UNTITLED_FUNC_H
#define UNTITLED_FUNC_H
#endif //UNTITLED_FUNC_H
int print_start(int i); //函数声明
void print_message(); //函数声明
main.c
#include "func.h"
int main() {
int a=10;
a= print_start(a);
//printf("%d",a); //查看当前变量a的值
print_message();
print_start(a);
int b=1;
}
程序的执行效果如下,读者可以将注释取消,这样能更清楚的看到程序内变量的赋值过程,与输出的情况做对比。
***************
print_start 10
how do you do
***************
print_start 3
***************
print_start 13
C语言的编译和执行具有以下特点:
1、一个C程序由一个或多个程序模块组成,每个程序模块作为一个源程序文件,对于较大的程序,通常将程序内容分别放在若干源文件中,再由若干源程序文件组成一个C程序。这样处理便于分别编写、分别编译,进而提高调试效率。一个源程序文件可以为多个C程序共用。
2、一个源程序文件由一个或多个函数及其他有关内容(如命令行、数据定义等)组成,一个源程序文件是一个编译单位。在程序编译时是以源程序文件为单位而不是以函数为单位进行编译的。main.c和func.c分别单独编译,在链接成为可执行文件时,main中调用的print_start函数和print_message函数才会通过链接去找函数定义的位置。
3、C程序的执行是从main函数开始的,如果在main函数中调用其他函数,那么在调用后会返回到main函数中,在main函数中结束整个程序的运行。
4、所有函数都是平行的,即在定义函数时是分别进行的,并且是互相独立的。一个函数并不从属于另一函数,即函数不能嵌套定义。函数之间可以相互调用,但不能调用main函数。main函数是由系统调用的,如在上面的代码中,main函数调用print_message函数,而print_message函数中又调用print_start函数,这种调用称为嵌套调用。
函数的声明与定义的差异如下:
1、函数的定义是指对函数功能的确定,包括函数名、函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。
2、函数的声明的左右是把函数的名字,函数的类型及形参的类型、个数和顺序通知编译系统,以便在调用该函数时编译系统能正确识别函数并检查调用是否合法。
隐式声明:C语言中有几种声明的类型名可以省略。例如,函数如果不显式地声明返回值的类型,那么它默认返回整型(这不是一个好习惯)
函数的分类与调用
从用户的角度看,函数分为如下两种。
1、标准函数(库函数),由系统提供,用户不必自己定义的函数,可以直接使用,如printf()、scanf()。不同的C系统提供的库函数的数量和功能会有一些不同,但许多基本的函数是相同的。
2、用户自定函数,用于解决特定的需求
从函数的形式看,函数分为如下两种。
1、无参函数,在调用无参函数时,主调函数不向被调函数传递数据。如print_message函数
无参函数的定义形式如下:
类型标识符 函数名()
{
声明语句
语句部分
}
2、有参函数,主调函数在调用被调函数时,通过参数向被调函数传递数据。如print_start函数,int i对应的i为形式参数,主调函数和被调函数之间存在数据传递关系。
有参函数的定义形式如下:
类型标识符 函数名(形式参数表列)
{
声明部分
语句部分
}
递归调用
函数自身调用自身的操作,称为递归函数。递归函数一定要有结束条件,否则会产生死循环。
递归在解决某些问题时,可以让问题变的简单,降低变成的难度,比如下面的题目:假如有n个台阶,一次只能上1个台阶或者2两个台阶,请问走到n个台阶有几种走法?为便于读者理解题意,这里举例说明如下:假如有3个台阶,那么总计就有3种走法:第一种为每次上1个台阶,一共上3次。第二种为先上2个台阶,再上1个台阶。第三种是先上1个台阶,再上2个台阶。
#include <stdio.h>
int f(int n) //解决阶乘问题
{
if(1==n)
{
return 1;
}
return n* f(n-1);
}
int step(int n) //爬楼梯(斐波那契数列)
{
if(1==n)
{
return 1;
}
if(2==n)
{
return 2;
}
return step(n-1)+ step(n-2);
}
int main() {
int n;
int ret;
scanf("%d",&n); //提示输入某个数字,计算阶乘
ret= f(n);
printf("%d\n",ret);
scanf("%d",&n); //提示输入台阶数
ret= step(n);
printf("%d\n",ret);
return 0;
}
函数step: step(n) = step(n-1) + step(n-2)
对应了在第 n 个台阶时,有两种可能性:要么是从第 n-1 个台阶跨一步到达,要么是从第 n-2 个台阶跨两步到达。因此,通过递归的方式计算出了到达第 n 个台阶的总走法数。代码的结束条件是n恒等于1或者恒等于2。
首先,让我们来理解一下斐波那契数列的性质:
斐波那契数列是这样一个数列:0, 1, 1, 2, 3, 5, 8, 13, ...
每一个数都是前两个数的和。也就是说,第 n 个数是第 n-1 个数和第 n-2 个数的和。
现在,让我们来看看 step
函数的执行过程,以及为什么 step(5)
返回 8。
-
step(5)
调用时,由于n
不等于 1 或 2,因此执行step(4) + step(3)
。 -
step(4)
的计算为step(3) + step(2)
,即第 4 个台阶的走法数等于第 3 个台阶的走法数与第 2 个台阶的走法数之和。 -
对于
step(3)
,同样不等于 1 或 2,因此执行step(2) + step(1)
。step(2)
返回 2,step(1)
返回 1,所以step(3)
返回 3。 -
对于
step(2)
,直接返回 2。 -
回到
step(4)
,step(3)
返回 3,step(2)
返回 2,因此step(4)
返回 5。 -
回到
step(5)
,step(4)
返回 5,step(3)
返回 3,因此step(5)
返回 8。
因此,当 n = 5
时,到达第 5 个台阶有 8 种走法,与斐波那契数列的第 6 项相同。
全局变量解析-形参-实参解析
在不同的函数之间传递数据时,可使用以下方法:
1、参数:通过形式参数和实际参数
2、返回值:同return语句返回计算结果
3、全局变量:外部变量
#include <stdio.h>
int i=10;
void print(int i)
{
printf("print i=%d",i);
}
int main() {
{
int j=5; //局部变量的有效范围是离自己最近的花括号
}
//printf("%d",j);
printf("main i=%d\n",i);
//int i=5;//再次定义局部变量,编译器会出现警告
i=5;
print(i);
return 0;
}
全局变量并不是存储在堆空间或者栈空间内,而是存储在数据段内,所以main函数和print函数都是可见的,全局变量不会因为某个函数执行结束而消失,在整个进程的执行过程中始终有效。如果局部变量与全局变量重名,那么将采取就近原则,即实际获取和修改的值是局部变量的值。
关于形参与实参的说明如下:
1、定义函数中指定的形参,如果没有函数调用,那么它们并不会占用内存中的存储单元,只有在发生函数调用时,函数print中的形参才会被分配内存单元,在调用结束后,形参所占的内存单元也会被释放。
2、实参可以是常量、变量或者表达式,但是要求它们有确定的值,例如print(i+3)在调用时将实参的值i+3赋给形参,print函数可以有两个形参,如print(int a, int b)。
3、在被定义的函数中,必须指定形参的类型,如果实参列表中包含多个实参,那么各参数间用逗号隔开,实参与形参的个数应想等,类型应匹配,且实参与形参按顺序对应,一一传递数据。
4、实参与形参的类型应相同或赋值应兼容。
5、实参向形参的数据传递应是单向"值传递",只能由实参传给形参,而不能由形参传回给实参,在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。
6、形参相当于局部变量,因此不能再定义局部变量与形参同名,否则会造成编译错误。
局部变量与全局变量
1、内部变量
在一个函数内部定义的变量称为内部变量,它只在本函数范围内有效,即只有在本函数内才能使用这些变量,故也称局部变量
关于局部变量需要注意:
(1) 主函数中定义的变量只在主函数内有效,而不因为在主函数中定义而在整个文件或程序中有效,主函数也不能使用其他函数中定义的变量。
(2) 不同函数中可以使用相同名字的变量,他们代表不同的对象,互不干扰。
(3) 形式参数也是局部变量。
(4) 在一个函数内部,可以使用在复合语句中定义变量,这些变量只会在本复合语句中有效,这种复合语句也称为"分程序"或"程序块"。上述代码中的int j=5就是如此,只在离自己最近的花括号内有效,若离开花括号,在花括号下面使用该变量会造成编译错误。
(5)在for循环的小括号内定义的int i,在离开for循环后,是不可以再次使用的。
2、外部变量
函数之外定义的变量称为外部变量,外部变量可以为本文件中其他函数共用,它的有效范围是从定义变量的位置开始到本源文件结束,所以也称全程变量。
关于全局变量需要注意如下几点:
(1) 全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
(2) 使用全局变量过多会降低程序的清晰性,在各个函数执行时都可能改变外部变量的值,程序容易出错,因为要有限制的使用全局变量。
(3) 因为函数在执行时依赖于其所在的外部变量,如果将一个函数移到另一个文件中,那么还要将有关的外部变量及其值一起移过去,然而如果该外部变量与其他文件的变量同名,那么就会出现问题,即降低程序的可靠性和通用性。C语言一般要求把程序中的函数做成一个封闭体,除可以通过"实参-形参"的渠道与外界发生联系外,没有其他渠道。