任务3 用函数调用简化运动程序
在后面讲节中,机器人将需要执行各种运动来避开障碍物和完成其他动作。不过,无论机器人要执行何种动作,都离不开前面讨论的各种基本动作。为了各种应用程序方便使用这些基本动作程序,可以将这些基本动作放在函数中,供其他函数调用来简化程序。
C语言提供了强大的函数定义功能。在本书第1讲中已经介绍过,一个C程序就是由一个主函数和若干个其他函数构成,由主函数调用其他函数,其他函数也可以相互调用。同一个函数可以被一个或多个函数调用任意多次。
实际上,为了实现复杂的程序设计,在所有的计算机高级语言中都有子程序或者子过程的概念。在C语言程序中,子程序的作用就是由函数来完成的。
从函数定义的角度来看,函数有两种
(1)标准函数,即库函数。由开发系统提供,用户不必自己定义而直接使用,只需在程序前包含有该函数原型的头文件即可在程序中直接调用,例如前面已经用到的串口标准输入(printf)和输出(scanf)函数。应该说明,不同的语言编译系统提供的库函数的数量和功能会有一些不同,但许多基本函数是共同的。
(2)用户定义函数,以解决你的专门需要。不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
从有无返回值角度来看,函数又分为以下两种
(1)有返回值函数。函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。由用户定义的返回函数值的函数,必须在函数定义中明确返回值的类型。
(2)无返回值函数。此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。用户在定义此类函数时可指定它的返回为“空类型”,即“void”。
从主调函数和被调函数之间数据传送的角度看,函数也可分为两种
(1)无参函数。函数定义、说明及调用中均不带参数,主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。
(2)有参函数。在函数定义及说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)。进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。
第1讲就已经给出了函数定义的一般形式:
类型标志符 函数名(形式参数列表) { 声明部分 语句 }
其中类型标志符和函数名称为函数头。类型标志符指明了本函数的类型,函数的类型实际上是函数返回值的类型。函数名是由用户定义的标志符,函数名后有一个括号(不可少写)。若函数无参数,则括号内可不写内容或写“void”;若有参数,则形式参数列表给出各种类型的变量,各参数之间用逗号间隔。
{}中的内容称为函数体。函数体中的声明部分,是对函数体内部用到的变量的类型说明。在很多情况下都不要求函数有返回值,此时函数类型符可以写为void。
main函数的返回值
前面说过,main函数是不能被其他函数调用的,那它的返回值类型int是怎么回事呢?
其实不难理解,main函数执行完后,它的返回值是给操作系统的。虽然在main函数体内并没有什么语句来指出返回值的大小,但系统默认的处理方式是:当main函数成功执行时,它的返回值为1;否则为0。
现在看看下面的函数定义。
void Forward(void) { int i; for(i=1;i<=65;i++) { P1_1=1; delay_nus(1700); P1_1=0; P1_0=1; delay_nus(1300); P1_0=0; delay_nms(20); } }
Forward函数可以使机器人向前运动约1.5s,该函数没有形式参数,也没有返回值。在主程序中,可以调用它来让你的机器人向前运动约1.5s。但是这个函数并没有太大的使用价值,如果想让你的机器人向前运动2s,该怎么办呢?是重新写一个函数来实现这个运动吗?当然不是!通过修改上面的函数,给它增加两个形式参数,一个是脉冲数量,另一个是速度参数。这样主程序调用时就可以按照你的要求灵活设置这些参数,从而使函数真正成为一个有用的模块。重新定义向前运动函数如下:
void Forward(int PulseCount,int Velocity) /*Velocity should be between 0 and 200 */ { int i; for(i=1;i<=PulseCount;i++) { P1_1=1; delay_nus(1500+Velocity); P1_1=0; P1_0=1; delay_nus(1500-Velocity); P1_0=0; delay_nms(20); } }
函数定义下方,增加了一行注释,提醒你在调用该函数时,速度参量的值必须在0~200之间。
注释符
除“//”外,C语言还提供了另一种语句注释符——“/*”和“*/”。
“/*”和“*/”必须成对使用,在它们之间的内容将被注释掉。它的作用范围比“//”大:“//”仅仅对它所在的一行起注释作用;但“/*…*/”可以对多行注释。
注释是你在学习程序设计时要养成的良好习惯。
下面是一个完整的使用向前、左转、右转和向后四个函数的例程。
例程:MovementsWithFunctions.c
输入、保存、编译、下载并运行程序MovementsWithFunctions.c。
#include<BoeBot.h> #include<uart.h> void Forward(int PulseCount,int Velocity) /*Velocity should be between 0 and 200 */ { int i; for(i=1;i<=PulseCount;i++) { P1_1=1; delay_nus(1500+Velocity); P1_1=0; P1_0=1; delay_nus(1500-Velocity); P1_0=0; delay_nms(20); } } void Left(int PulseCount,int Velocity) /*Velocity should be between 0 and 200 */ { int i; for(i=1;i<=PulseCount;i++) { P1_1=1; delay_nus(1500-Velocity); P1_1=0; P1_0=1; delay_nus(1500-Velocity); P1_0=0; delay_nms(20); } } void Right(int PulseCount,int Velocity) /*Velocity should be between 0 and 200 */ { int i; for(i=1;i<=PulseCount;i++) { P1_1=1; delay_nus(1500+Velocity); P1_1=0; P1_0=1; delay_nus(1500+Velocity); P1_0=0; delay_nms(20); } } void Backward(int PulseCount,int Velocity) /*Velocity should be between 0 and 200 */ { int i; for(i=1;i<=PulseCount;i++) { P1_1=1; delay_nus(1500-Velocity); P1_1=0; P1_0=1; delay_nus(1500+Velocity); P1_0=0; delay_nms(20); } } int main(void) { uart_Init(); printf("Program Running!\n"); Forward(65,200); Left(26,200); Right(26,200); Backward(65,200); while(1); }
这个程序的运行结果与程序ForwardLeftRightBackward.c产生的效果是相同的。很明显,还有许多方法可以构造一个程序而得到同样的结果。实际上,四个函数的具体实现部分几乎完全一样,有没有可能将这些函数进行归纳,用一个函数来实现所有这些功能呢?当然有,前面的四个函数都用了两个形式参数,一个是控制时间的脉冲个数,另一个是控制运动速度的参数,而四个函数实际上代表了四个不同的运动方向。如果能够通过参数控制运动方向,显然这四个函数就完全可以简化成为一个更为通用的函数,它不仅可以涵盖以上四个基本运动,同时还可以使机器人朝你希望的方向运动。
由于机器人由两个轮子驱动,实际上两个轮子的不同速度组合控制着机器人的运动速度和方向,因此可以直接用两个车轮的速度作为形式参数,就可以将所有的机器人运动用一个函数来实现。
例程:MovementsWithOneFuntion.c
这个例子使你的机器人做同样动作,但是它只用了一个子函数来实现。
#include<BoeBot.h> #include<uart.h> void Move(int counter,int PC1_pulseWide,int PC0_pulseWide) { int i; for(i=1;i<=counter;i++) { P1_1=1; delay_nus(PC1_pulseWide); P1_1=0; P1_0=1; delay_nus(PC0_pulseWide); P1_0=0; delay_nms(20); } } int main(void) { uart_Init(); printf("Program Running!\n"); Move(65,1700,1300); Move(26,1300,1300); Move(26,1700,1700); Move(65,1300,1700); while(1); }
● 输入、保存并运行程序MovementsWithOneFuntion.c;
● 你的机器人是否执行了前、左、右、后运动呢?
● 修改MovementsWithOneFuntion.c,使机器人走一个正方形。第一边和第二边向前走,另外两个边向后走。