函数
一、自定义函数
1.函数的定义
函数在使用前也需要定义,定义的格式如下:
数据类型 函数名([数据类型 参数1],[数据类型 参数2]...)
{
语句
}
大多数函数都会通过return
语句返回一个值,前面函数名之前的数据类型就是该返回值的类型。函数名和变量名的定义规则一样。即使函数没有参数(即使参数个数为0),小括号也是不可省略的。
函数是不允许嵌套定义的。但可以在一个函数中调用另外一个函数。
2.函数的调用
格式:
函数名([参数1],[参数2]...);
函数名后面的小括号内根据需要给出相应的参数。同样地,即使不需要参数,这个小括号也不能省略。
函数调用时的小括号内的参数称为实际参数,简称实参,而把函数定义时的小括号内的参数称为形式参数,简称形参。当函数执行时,会将实参的值赋给相对应的形参,所以实参的类型、顺序、和数量要和形参的类型、顺序和数量一一匹配。
在进行函数调用时,程序的执行流程从当前语句转入函数,在函数执行完毕后,再返回至函数调用的语句,继续向下执行。
3.函数的声明
格式:将函数的函数头部分取出来,后面加上一个分号。
在函数声明中,形参的变量名是可以被省略的,但形参的数据类型不可省略。
自定义函数在主函数之前,所以在主函数中调用自定义函数时,不需要进行函数的声明。
自定义函数在主函数之后,所以在主函数中调用自定义函数时,需要进行函数的声明。
函数的声明位置和次数是无限制的,我们可以把函数声明和函数的调用放在同一个函数中,那么在该函数中,函数声明可以覆盖之后的所有函数调用,但其它函数中的函数调用仍然需要进行函数声明;也可以把函数声明放于包含函数调用的函数之前,这样函数声明可以覆盖其后的所有函数中的函数调用。
二、函数的分类
1.无返回值的函数
void
放在函数名的前面,用来表示这个函数是没有返回值的。
无返回值的函数有什么好处呢
我们编写一个名为showInfo
的无返回值函数
void showInfo()
{
printf("~~~~~~~~~~~~~~~\n");
printf("please enter an integer:\n");
printf("~~~~~~~~~~~~~~~\n");
}
1、可以精简代码。
2、便于代码维护,以后需要改动的就改动函数而不用去主函数逐一修改。
2.有返回值的函数
如果这个函数要把数值返回给调用函数的地方,就是一个有返回值的函数了
int getUserEnter()
{
int n;
printf("~~~~~~~~~~~~~~~\n");
printf("please enter an integer:\n");
printf("~~~~~~~~~~~~~~~\n");
scanf("%d",&n);
return n;
}
函数名之前的int
,表示函数是有返回值的,而且返回值的类型是int
。
在返回值函数中,数据类型必须是非void
的,而且在函数体中必须有return
语句。函数能够通过return
语句返回一个值,即采用return
表达式的形式,return
关键字后面的表达式的类型必须和函数名前所定义的数据类型匹配。一旦return
语句被执行它会结束函数的执行流程,并将表达式的值返回到函数调用的地方。
3.无参函数
所谓无参函数,就是没有参数的函数,即函数名后面是一对空的小括号。
定义无参函数的时候,小括号内可以是空的,也可以放入一个void
关键字。
void showInfo2(void)
{
printf("~~~~~~~~~~~~~~~\n");
printf("please enter an integer:\n");
printf("~~~~~~~~~~~~~~~\n");
}
4.有参函数
和无参函数相对应的是带参函数,也就是需要参数的函数,即在函数名后面的小括号内必须有一个或多个的参数,如果是多个参数,参数之间使用逗号进行分隔。
void printResult(int res)
{
printf("result:%d\n",res);
}
最后总结一下四种类型的函数头定义示例
void func()
void func(int a,float b,double c)
int func()
int func(int a,float b,double c)
三、再谈函数调用
1.函数的嵌套调用
函数是不能嵌套定义的,但是函数是可以嵌套调用的。所谓函数的嵌套调用,就是在一个函数体中又出现了函数调用语句。简单说,就是函数又调用了函数。
void A()
{
printf("Start function A.\n");
printf("End function A.\n");
}
void B()
{
printf("Start function B.\n");
A();
printf("End function B.\n");
}
int main()
{
printf("Start main function.\n");
B();
printf("End main function.\n");
return 0;
}
执行后结果为
Start main function.
Start function B.
Start function A.
End function A.
End function B.
End main function.
2.函数的递归调用
(1)递归调用与递归函数
函数的递归调用的原理很简单,就是函数的自身调用。它其实就是一种特殊的函数嵌套调用。C语言允许函数调用自身,也就是在一个函数的函数体中,又调用了函数自己。
void A()
{
A();
}
函数A是一个递归函数,通过它的执行能引起函数的递归调用。
(2)递归调用的终止
怎么才能让递归调用终止呢?还得依靠我们的老朋友return
语句。也就是在进行递归调用时,需要在满足某种条件的情况下,通过return
语句来终止递归调用。
(3)递归调用的算法原理
大家有没有考虑过,为什么要有递归调用?什么情况下才需要使用递归调用?这就需要了解递归调用的算法原理,也就是需要如何通过递归调用来达到所要求的功能。
它的主体思想就是对于一个我们不容易完成的、比较大的目标,通过不断地对它进行分解化小,直到小到极致,也就是可以轻松完成的时候,再反过来一点点由小到大,最终完成那个大的目标。
(4)编写递归函数
编写一个递归函数,能够计算1~n的累加值,其中n的值大于等于1,并在主函数中调用该函数。
int sum(int n)
{
if(n == 1)
return 1;
return sum(n - 1) + n;
}
四、再谈变量
1.自动变量与静态变量
变量在定义的时候,除了需要指定数据类型之外,还可以指定它的存储类型。在C语言中,可以使用auto
和static
两个关键字来指定变量的存储类型。
auto int a;//自动存储类型变量,简称自动变量
static int b;//静态存储类型变量,简称静态变量
默认情况下,在函数中定义的变量属于自动变量,所以前面的auto
关键字是可以省略的,即auto int a与int a是一样的,都表示自动变量。
(1)存储位置
代码区:程序文件的二进制码
静态区:字符串常量、全局变量、静态变量等
堆:由程序员管理的内存区域
栈:自动变量、函数参数等,这是由系统管理的内存区域
栈是由系统管理的一块内存区域,例如我们定义一个自动变量时,系统会根据变量的数据类型大小在栈内开辟一块内存,当不再使用这个变量时,系统会将它所使用的内存回收。
而堆是由程序员管理的一块内存区域,其中内存的分配、使用和回收都是由程序员通过调用相应的内存管理函数来完成。
(2)初始化
自动变量不会被默认初始化,而静态变量会被默认初始化(默认为0)。
(3)生命期
只有在变量的生命期内,用户才可以去使用这个变量。在之前和结束都无法使用这个变量。
自动变量的生命期是从变量所在的函数被执行后,变量被定义时开始,至函数结束时,其生命期结束,也可以说自动变量是属于函数生命期。
静态变量的生命期是从程序运行时开始,至程序结束时,其生命期结束,也可以说静态变量属于程序生命期。
2.局部变量与全局变量
按照定义的位置不同,变量还可以分为局部变量与全局变量,在函数内定义的变量是局部变量,包括形参变量,在函数外定义的变量是全局变量。
int a;
void test(int b)
{
int c;
static int d;
}
a全局变量;b、c、d局部变量
(1)作用域
生命期控制着变量的生命权限,而作用域控制着变量的访问权限。
局部变量的作用域从定义开始,直到函数的结尾处。
全局变量的作用域是所有程序文件。
(2)全局变量的声明
函数的作用域也是所有的程序文件,所以全局变量和函数拥有同样的作用域。而且在使用时,全局变量与函数也非常相似,即全局变量在使用前也要进行声明。
在其它文件使用这个文件的全局变量之前,我们可以对它进行声明
extern int a;
注意:在对全局变量进行声明时,不要对它进行初始化。
(3)全局变量的存储类型
全局变量存储在静态区,和静态变量一样,也是属于静态存储类型,因此它拥有和静态变零一样的生命期,即程序生命期。
和静态变量另外一个相同之处是,全局变量在定义时,如果没有对其初始化,编译器会自动将其值初始化为0。
3.只读变量
只读变量只能被访问,而其值是不允许被修改的。但只读变量不是常量,因为它的值并非真正地无法改变,通过后面介绍的指针,还可以间接地修改它的值。
const int a = 10;
能访问它的值,但不能通过变量名来修改它的值。