第八章:函数基础
概述
本章将讲解函数,函数是数学中非常重要的概念,它本质上是一种对应关系,在对应法则f下从数集A到数集B的对应.
那么在C语言中函数是指什么?函数又有什么作用呢?在一个程序中函数又会扮演什么样的角色呢?
本章就将讲解C语言中的函数.
本章将讲解以下内容:
- 函数的初步认识
- 函数定义
- 函数组成
由于函数的内容很多,因此关于函数的调用将在下一章讲解
函数的初步认识
C语言的函数是指完成特定任务的独立程序代码单元
而使用函数的功能就是:
1.少写重复代码
2.使代码模块化
例如:如果我们如果要求一个程序中输入三个数,然后计算平均数,重复十次这样的操作(不要用循环)
那么这样的工作量是极其庞大的,写这样的程序也非常恶心.
那么如果我们定义一个函数,每次都只需要像用printf()这样只需要一行就能完成输入,求和,输出这一套操作的化,我们的工作量就会极大地减少
//假如说我们有一个函数 Calculate_3num和printf一样只需要一行就能完成输入,计算和输出这一套操作的话
//不仅我们的工作量会减少很多,而且程序会更加简单易读
void main(){
Calculate_3num(1,2,3);
.....
Calculate_3num(a,b,c);
}
所以下面就将介绍C语言中函数的组成和调用.
函数的定义
函数由函数名,参数和函数体组成.
函数名是用户定义的名字,用来唯一标识一个函数
函数的参数用来接收函数传递给他的数据
函数体是函数实现自身功能的一组语句.
其中有的函数只需要完成一些操作,而不需要从主函数接受数据并且对数据进行运算,例如只需要向屏幕上打印666
那么这种函数就不需要参数
还有的函数需要从主函数接受数据,并且对数据进行运算和操作,例如上面的例子,接受三个数字计算平均数
这种函数就需要参数
因此C语言的函数按照有无参数(需不需要参数)可以分为有参函数和无参函数
所以我们的定义可以分成两种:有参函数的定义和无参函数的定义
无参函数的定义
无参函数的定义如下:
类型标识符 函数名()
{
语句
}
例如:
void print666() { for(int i=0;i<3;i++) { printf("666\n") } }
那么每次调用一次这个函数就会打印三行666
需要注意的是:
-
我们这里只是说不需要从主函数中接收数据,没说这个函数自己的语句部分不能接收数据
例如:void printNum() { int num; scanf("%d",num); for(int i=0,i<3;i++) { printf("%d%d%d\n",num,num,num); } } >>>效果就是打印三行指定的数字
调用语句如下:
printNum()
-
同样,所有主函数中能用的语句,在自己定义的函数中都能用
-
函数名的定义规则和变量的定义规则一样
有参函数的定义
有参函数的定义如下:
类型标识符 函数名(形式参数声明)
{
语句
}
例如:
int Calculate(int x,int y) { int sum; sum=x+y; return sum }
调用的语句为
Calculate(1,2); printf("%d",Calculate(1,2)) >>>3
需要说明的点是:
-
形式参数的命名等等问题将在后面函数的参数详细讲解
-
函数可以有返回值也可以没有返回值,例如上面的Calculate函数运行一次之后函数本身就有了一个值3
从这个意义上理解的函数就相当于一个"变量",只不过这个变量的值由他自己产生 -
和无参函数一样,所有主函数中能用的语句,有参函数中也能用
-
和无参函数一样,有参函数的命名规则和变量一样
而且无参函数也能具有返回值,完全可以自己在函数体内的语句要求输入,计算,输出,返回一个值
有参函数除了像无参函数一样在自己的函数体内的语句要求输入,计算,输出,返回一个值外,还可以接受主函数传递值(在后面函数的调用详细讲解,这里只是说一下
函数的组成
从上面介绍的的函数的定义能看出来,一个函数由四部分组成(这里是为了讲解分成四个部分,和上面三个部分的划分没有冲突):
- 函数的类型
- 函数的名称
- 函数的参数(主要是有参函数)
- 函数的函数体
其中函数的类型和函数的返回值有关,因此将在一起讲解.函数的命名规则和变量的命名一样,这里就不赘述
函数的类型和返回值
函数的类型
由于函数可以具有返回值,因此函数本身属于某一个确定的类型
从上面的描述中,可以看到函数的类型应该和函数的返回值相同.
但是,如果函数类型和返回值类型不一致的话,那么返回值类型将服从于函数类型,进行自动类型转换
其次,在没有声明函数的类型时,默认函数类型为整型(int)
但是最好避免以上两种情况,这可能会为程序埋下bug
返回值
有的时候,我们不仅希望函数能够进行一系列操作,更希望函数能够产生一些值(整数,浮点数等等,数组一般返回地址)来在主函数中操作.
但是我们有知道函数中的变量并不能对主函数中的变量产生影响(这点将在后面讲解),换而言之无法做到在函数中对主函数的变量进行赋值运算等等操作.
因此我们就有了返回值,返回值是这个函数进行完操作之后所具有的值
例如我们<math.h>中的pow函数.
pow函数的作用是计算某一个数的n次方并返回计算结果,即:
xy=pow(x,y)
也就是说pow(x,y)在输入具体的x和y之后,就具有了一个值,可以参与各种运算.例如:
1+pow(2,4)
pow(2,4)==4
等各类的可运算的表达式.
所以通过这样就可以实现对于函数对主函数的影响.
不过其实在函数内部可以通过直接对主函数的变量的地址来操作完成对主函数的影响
函数的参数
我们在前面说过,有参函数在主函数调用的时候会从主函数接收数据
其实只要是主调用函数都会向被调用有参函数传递数据.
函数中的参数根据含义的不同,可以分为形式参数和实际参数.
并且函数参数的传递根据内容不同又可以分为数值传递和地址传递
因此下面将分别讲解
形式参数
在定义函数的时候,函数名后面的括号内部所定义的参数就是形式参数
需要先定义形式参数来告诉编译器在编译函数时候来为变量分配内存空间
因为不同类型的变量在内存中的储存格式是不同的.
对于形式参数有以下几点需要声明:
- 在函数没有调用的时候,所定义的函数的形式参数并不会被划分内存空间
只有在调用函数时候才会对函数定义的形式参数分配内存空间
并且在函数运行结束之后内存空间将会被释放
可以理解为在内存中重新开辟一个空间让被调用函数运行
这也就造成了为什么函数内的变量对函数外的变量没有影响 - 在定义有参函数的时候必须定义形式参数的类型
实际参数
实际参数是指在调用函数的时候被调用函数括号内的真实值.
例如: pow(2,3)
2和3就是实际参数
关于实际参数需要声明的点如下:
- 实际参数可以使常量也可以使变量,还可以是表达式,但是要求变量和表达式都有具体的值/可计算出值
- 实际参数要和形式参数的类型一致
数值传递
前面的讲解中讲过,我们在调用函数的时候其实就相当于在内存中重新开辟了一个空间来让被调用函数运行
因此开辟出的空间内的变量与开辟空间外的变量(主函数的变量)就没有任何联系
给形式参数赋予数值的过程其实只是单向的数值的传递.数值传递完毕之后主函数的变量和被调用函数的变量再无联系
例如:
void change(int a ;int b) { int temp; temp=a; a=b; b=temp; } void main(){ int a,b; a=1; b=2; change(a,b); //函数的调用在后面讲解 print("%a,%b",a,b); } >>>1,2
事实上由于被调用函数并不会对调用函数的变量产生影响,因此主函数中的a和b的值不变
地址传递
有的时候我们希望被调用函数能够实现对主函数中变量的影响,因此这个时候我们就能向被调用函数中传递指针.
这样被调用函数的变量的修改就是对主函数的地址的修改而非对新开辟的内存空间的修改
因此在地址传递的方式中实际参数和形式参数就要是指针变量或者数组名(因为这两者都指向变量的实际内存地址)
关于地址的传递将在指针的章节讲解.