3.4 C51语言的函数

3.4 C51语言的函数

1.函数的定义

函数定义格式如下:

一个函数包括如下几个要素:返回值、函数名、形式参数(简称形参)变量、函数体。形参变量就是该函数的局部变量,在该函数被调用时,主调者会将具体数据传递给形参变量,也就是说,没发生函数调用之前,这个函数的形参变量没有实际意义。形参变量的声明同定义普通变量一样,格式为:形参变量数据类型形参变量存储位置形参变量名称。

在函数体里定义的变量叫局部变量,局部变量只能在定义它们的函数体里使用,局部变量在函数体开始大括号“{”之后定义。函数体的最后一句是return,用来处理函数的返回值。一个函数可以没有形式参数变量和返回值,定义格式如下:

2.主函数

用户程序里必须且只能有一个名字叫main的主函数,main函数不能有形参和返回值,main函数不能被其他函数调用。main函数的函数体是设计者根据自己的需要编写的主程序,在主程序里可以调用其他子函数(不包括中断函数)。在单片机上电后,用户程序总是从main函数的函数体的第一条语句开始执行。主函数的书写格式为:

3.中断函数

如果使用单片机的中断功能,则需要编写中断函数。中断函数同样不能有形参和返回值,也不能被其他函数调用。中断函数可以调用其他函数,因为涉及再入问题,使用时要十分小心,尽最大可能不在中断函数里调用其他函数。

中断函数通过使用interrupt关键字和中断号来实现。使用该扩展属性的函数声明语法如下:

返回值数据类型函数名inter rupt n

中断函数和其他子函数的名字可以任意起,interrupt关键字是中断函数和其他子函数的主要区别,定义中断函数时必须使用interrupt关键字。51系列单片机的中断号如表3-11所示。中断号告诉编译器中断程序的入口地址,它对应着IE寄存器中的使能位,即IE寄存器中的0位对应着外部中断0,相应的外部中断0的中断编号是0,依此类推。例如,可以这样定义定时器/计数器T0的中断函数:

表3-11 中断号

4.库函数

Keil C51编译软件里包含10个标准函数库,如果要使用某个函数,就必须把该函数所在的库包含进来。这10个函数库如表3-12所示。

表3-12 函数库

5.函数的调用

在main函数中可以调用其他子函数,例如,在main函数里调用TwoData Add函数:

变量a、b称为实参,调用TwoDataAdd()函数时,实参a、b的值分别传递给形参FirstData和Second Data,用变量c接收函数的返回值。值传递的特点是,在函数调用结束后,实参仍旧保持原来的值,即a=38,b=99,而形参被释放。我们再通过程序说明地址传递:

上面的子程序用数组做形参,在实际调用时只需将实参数组名(数组名就是该数组第一个元素的存储地址)传递给形参数组,地址传递给形参后,形参数组指向了实参数组的实际存储地址,子函数对形参数组排序实际上就是对实参数组进行排序。可以这样定义子函数:

执行结果是一样的。

函数通过return语句只能返回一个数据,通过数组的地址传递方式可以修改一系列同类型的数据,如果要操作且返回多个不同类型的变量,则使用全局变量。全局变量使用是最方便的,但是全局变量可以在多处被修改,使用时要相当细心。

【例3-1】 写出一个完整的调用子函数的例子,用单片机控制一个LED闪烁发光。用P1口的第一个引脚控制一个LED。

在上面的程序中,可以看到在打开和关闭LED的两条语句之后,是两个完全相同的for嵌套语句:

在C51语言中,如果有些语句不止一次用到,而且语句的内容都相同,那么就可以把这样的一些语句写成一个不带参数的子函数,当在主函数中需要这些语句时,直接调用这些语句就可以了。上面的for嵌套语句就可以写成如下子函数的形式:

其中,void表示这个函数执行完后不返回任何数据,即它是一个无返回值的函数;delay是函数名,一般写成方便记忆和读懂的名字,也就是一看到函数名就知道此函数实现的内容是什么,但注意函数名不要和C51语言中的关键字相同。紧跟函数名的是一个空括号,这个括号里没有任何数据或符号(即C51语言中的参数),因此这个函数是一个无参数的函数。接下来的两个大括号中的语句是子函数中的语句,这就是无返回值、无参数函数的写法。

需要注意的是,子函数可以写在主函数的前面或是后面,但是不可以写在主函数的里面。当写在后面时,必须要在主函数之前声明子函数。声明方法:将返回值特性、函数名及后面的小括号完全复制,若无参数,则小括号里面为空;若是带参数函数,则需要在小括号里依次写上参数类型,且只写参数类型,无须写参数,参数类型之间用逗号隔开,最后在小括号的后面加上分号“;”。当子函数写在主函数前面时,不需要声明,因为写函数体的同时就已经相当于声明了函数本身。通俗地讲,声明子函数的目的是为了编译器在编译主程序的时候,当它遇到一个子函数时知道有这样一个子函数存在,并且知道它的类型和带参情况等,以方便为这个子函数分配必要的存储空间。

例3-2就是调用不带参数子函数的例子,通过调用子函数代替for嵌套语句。

【例3-2】

对于前面讲的子函数delay(),假设根据系统设定的时钟频率计算,该子函数调用一次延时500 ms,如果要延时300 ms,就要在子函数里把x的值赋为300,要延时200 ms就要把x的值赋为200。这样做会很麻烦,而如果使用带参数的子函数会让问题简单化。将前面的子函数改为:

上面代码中delay后面的括号中多了一句“unsigned int z”,这就是这个函数所带的一个参数,z是一个unsigned int型变量,又叫这个函数的形参,在调用此函数时用一个具体真实的数据代替此形参,这个真实数据又被称为实参,在子函数里面所有和形参名相同的变量都被实参代替。使用这种带参数的子函数会使问题方便很多,例如,要调用一个延时300 ms的函数,可以写成“delay(300);”,要延时200 ms可以写成“delay(200);”。例3-3是一个调用带参数函数的例子。

【例3-3】 设系统延时500 ms的子函数如例3-2,用单片机控制一个LED闪烁发光。用P1口的第一个引脚控制一个LED,让它亮500 ms,熄灭800 ms。