第4章单片机C语言2.ppt
4.7 4.7 函数函数 4.6.14.6.1函数的分类及定义函数的分类及定义 C C 语言程序由函数组成,从用户使用角度划分,函数分为库函数和语言程序由函数组成,从用户使用角度划分,函数分为库函数和用户定义函数:用户定义函数:库函数是编译系统已经为用户设计好的一系列标准函数库函数是编译系统已经为用户设计好的一系列标准函数 (见本书附录见本书附录二二),用户只需正确调用,如前面所用到的头文件,用户只需正确调用,如前面所用到的头文件reg51.hreg51.h等,有的头等,有的头文件中包括了一系列函数,要使用其中的函数必须先使用文件中包括了一系列函数,要使用其中的函数必须先使用#Include#Include 来申明,然后才能调用。来申明,然后才能调用。用户自定义函数是用户根据任务自己编写编写的函数。用户自定义函数是用户根据任务自己编写编写的函数。从参数形式上函数分为无参函数和有参函数。从参数形式上函数分为无参函数和有参函数。无参函数无参函数:函数中无参数定义。函数中无参数定义。有参函数:函数中定义了形式参数,在调用时,调用函数用实际参数有参函数:函数中定义了形式参数,在调用时,调用函数用实际参数代替形式参数,调用完将运算结果给调用函数。代替形式参数,调用完将运算结果给调用函数。4.6.24.6.2函数的定义函数的定义 无参函数的定义:无参函数的定义:返回值类型返回值类型 函数名函数名()()函数体语句函数体语句 如果函数没有返回值,可以将返回值类型设为如果函数没有返回值,可以将返回值类型设为void void;函数以函数以“”开始,以开始,以“”结束。结束。有参函数的定义:有参函数的定义:返回值类型返回值类型 函数名函数名 (形式参数表形式参数表)形式参数类型说明形式参数类型说明/*/*单独说明单独说明*/函数体语句函数体语句 return(return(返回形参名返回形参名)也可以这样定义:也可以这样定义:返回值类型返回值类型 函数名函数名 (类型说明形式参数表类型说明形式参数表)/)/*说明放在参数表内说明放在参数表内*/函数体语句函数体语句 return(return(返回形参名返回形参名)其中形式参数表中的各项要用其中形式参数表中的各项要用 ,隔开,通过隔开,通过returnreturn语句将语句将需返回的值返回给调用函数需返回的值返回给调用函数。4.6.3.4.6.3.函数的调用函数的调用 函数调用的形式为:函数调用的形式为:函数名函数名 (实际参数表实际参数表);对于无参函数当然不存在实际参数表。实参和形参的数目要相对于无参函数当然不存在实际参数表。实参和形参的数目要相等类型要一致。等类型要一致。函数的调用方式有三种函数的调用方式有三种 函数调用语句函数调用语句 :即把被调函数名作为调用函数的一个语句,如:即把被调函数名作为调用函数的一个语句,如 fun1fun1()();被调函数作为表达式的运算对象:被调函数作为表达式的运算对象:如如 rett=2*get(a,b)rett=2*get(a,b);此时此时getget函数中的函数中的 a a,b b 应为实参,用它的返回值参予式中的运算。应为实参,用它的返回值参予式中的运算。被调函数作为另一个数的实际参数:被调函数作为另一个数的实际参数:如如 m=max(a,get(a,b)m=max(a,get(a,b);函数函数 get(a,b)get(a,b)作为作为max()max()的一个实际参数被调用。的一个实际参数被调用。4.7.4 4.7.4 对被调函数的说明对被调函数的说明 如果被调函数出现在主函数之后,在主函数前应对被调函数加以说如果被调函数出现在主函数之后,在主函数前应对被调函数加以说明,形式为:明,形式为:返回值类型返回值类型 被调函数名被调函数名 (形参表形参表);如:如:int fun1(a,b);int fun1(a,b);/*/*函数说明函数说明*/main()main()/*/*主函数主函数*/int d,u=3,v=2;int d,u=3,v=2;d=2*fun1(u,v);d=2*fun1(u,v);int fun1(a,b)int fun1(a,b)int a,b;/*int a,b;/*形参类型的说明形参类型的说明*/int c;int c;c=a+b;c=a+b;return(c);/*return(c);/*函数返回值函数返回值*/此例被调函数放在主函数之后、所以在主函数前要先对被调函数进此例被调函数放在主函数之后、所以在主函数前要先对被调函数进行说明。行说明。被调函数出现在主函数之前,可以不对被调函数说明。下面给出一个被调函数出现在主函数之前,可以不对被调函数说明。下面给出一个简单例子:简单例子:int fun1(a,b)int fun1(a,b)int a,b;int a,b;int c;int c;c=a+b;c=a+b;return(c);return(c);main()main()int d,u=3,v=2;int d,u=3,v=2;d=2*fun1(u,v);d=2*fun1(u,v);此例中被调函数出现在主函数前,所以不需事先说明。此例中被调函数出现在主函数前,所以不需事先说明。4.8 4.8 单片机的单片机的 C C 语言编程实例语言编程实例 由于由于 C51 C51 编译器是针对单片机的,因此编译器是针对单片机的,因此 ANSI C ANSI C 中的中的 scanf scanf 和和 printf printf 等用于等用于 PC PC 机的输入输出语句无效。运算的数据可以通过设置机的输入输出语句无效。运算的数据可以通过设置变量的方法来置入或取出,也可以由用户自行通过具体的内存地址置入数变量的方法来置入或取出,也可以由用户自行通过具体的内存地址置入数据或从特定地址取出数据。据或从特定地址取出数据。C C 语言的上机调试和汇编程序使用同一仿真调试软件。语言的上机调试和汇编程序使用同一仿真调试软件。下面是一个下面是一个C C语言程序编译后生成的机器代码及对应的反汇编程序。语言程序编译后生成的机器代码及对应的反汇编程序。4.8.14.8.1、C C语言程序的反汇编程序语言程序的反汇编程序(源代码源代码)在在2.42.4节曾用汇编语言完成了外部节曾用汇编语言完成了外部RAMRAM的的000EH000EH单元和单元和000FH000FH单元的内单元的内容交换,现改用容交换,现改用C C语言编程。语言编程。C C语言对地址的指示方法可以采用指针变量,语言对地址的指示方法可以采用指针变量,也可以引用也可以引用absacc.habsacc.h头文件作绝对地址访问,下面采用绝对地址访问的头文件作绝对地址访问,下面采用绝对地址访问的方法。方法。#include main()char c;for(;)c=XBYTE14;XBYTE14=XBYTE15;XBYTE15=c;程序中为方便反复观察,使用程序中为方便反复观察,使用了死循环语句了死循环语句for(;),for(;),用用Ctrl+C Ctrl+C 可退出死循环。可退出死循环。左面程序通过编译后的机器代左面程序通过编译后的机器代码和反汇编程序如下:码和反汇编程序如下:0000 LJMP 0014H 0003 MOV DPTR,#000EH 0006 MOVX A,DPTR 0007 MOV R7,A 0008 INC DPTR 0009 MOVX A,DPTR 000A MOV DPTR,#000EH 000D MOVX DPTR,A 000E INC DPTR 000F MOV A,R7 0010 MOVX DPTR,A 0011 SJMP 0003H 0013 RET 0014 MOV R0,#7FH 0016 CLR A 0017 MOV R0,A 0018 DJNZ R0,0017H 001A MOV SP,#07H 001D LJMP 0003例中可见:例中可见:一进入一进入C C语言程序,首先执行将内部语言程序,首先执行将内部RAMRAM的的0 07FH 1287FH 128个单元清零,个单元清零,然后置然后置SPSP为为07H(07H(视变量多少不同,视变量多少不同,SPSP置不同值,依程序而定置不同值,依程序而定),因此如,因此如果要对内部果要对内部RAMRAM置初值,一定要在执行了一条置初值,一定要在执行了一条C C语言语句后进行。语言语句后进行。C C语言编译程序会按照定义设定变量,自行安排寄存器或存贮器作语言编译程序会按照定义设定变量,自行安排寄存器或存贮器作参数传递区,通常使用参数传递区,通常使用R0R0R7(R7(一组或两组,视参数多少定一组或两组,视参数多少定),因此,在,因此,在设置数据时,要设法避开设置数据时,要设法避开R0R0R7R7的地址区域。的地址区域。如果不特别指定变量的存贮类型,通常被安排在内部如果不特别指定变量的存贮类型,通常被安排在内部RAMRAM中。中。4.8.24.8.2、顺序程序的设计、顺序程序的设计例例1.1.完成完成19805198052450324503的编程的编程 分析:两个乘数比较大,其积更大,采用分析:两个乘数比较大,其积更大,采用unsigned longunsigned long类型,类型,设乘积存放在外部数据存贮器设乘积存放在外部数据存贮器0 0号开始的单元。程序如下:号开始的单元。程序如下:main()unsigned long xdata*p;/*设定指针设定指针p p*/unsigned long a=19805;/*设置设置a a的类型的类型*/unsigned long b=24503,c;/*设置设置b b和积和积c c为为 unsigned long类类 型,并赋初值型,并赋初值*/p=0;/*设地址指向设地址指向0 0号单元号单元*/c=a*b;*p=c;/*积存入外部积存入外部RAM 0RAM 0号单元号单元*/上机通过软件仿真调试,在变量观察窗口看到运算结果上机通过软件仿真调试,在变量观察窗口看到运算结果 c=485281915c=485281915,即为乘积的十进制数。观察,即为乘积的十进制数。观察XDATAXDATA区区(外部外部RAM)RAM)的的0000H0000H0003H0003H单元分别为单元分别为1C EC D0 7B1C EC D0 7B,即存放的是,即存放的是485281915 485281915 的十六进制数。的十六进制数。观察观察DATADATA区:区:00 01 02 03 04 05 06 07 08 09 0A 0B00 01 02 03 04 05 06 07 08 09 0A 0B 1C EC D0 7B 00 00 4D 5D 00 00 5F B7 1C EC D0 7B 00 00 4D 5D 00 00 5F B7 c c变量(积)变量(积)a a变量变量 b b变量变量 由于定义为由于定义为unsigned longunsigned long类型,给每个变量分配了四个单元,如果类型,给每个变量分配了四个单元,如果定义类型不对,将得不到正确的结果。如果未定义变量类型,默认为内部定义类型不对,将得不到正确的结果。如果未定义变量类型,默认为内部 RAMRAM,将,将 a a、b b、c c变量安排在内部变量安排在内部RAMRAM中。中。对于复杂的运算通常采用查表的方法。和汇编程序设计一样,在程序对于复杂的运算通常采用查表的方法。和汇编程序设计一样,在程序存贮器中建立一张表,在存贮器中建立一张表,在C C语言中将表格定义为数组,表内数据语言中将表格定义为数组,表内数据(元素元素)的的偏移量就是数组的下标。数组的使用也要先进行定义:即说明数组名、维偏移量就是数组的下标。数组的使用也要先进行定义:即说明数组名、维数、数据类型和存贮类型,在定义数组的同时还可以给数组各元素赋初值。数、数据类型和存贮类型,在定义数组的同时还可以给数组各元素赋初值。通过下例说明通过下例说明C51C51数组的定义方法和用数组的定义方法和用C C语言编查表程序的方法语言编查表程序的方法例例4-6 4-6 片内片内RAM 20HRAM 20H单元存单元存放着一个放着一个0 05H5H的数,用查的数,用查表法,求出该数的平方值表法,求出该数的平方值放入内部放入内部RAM 21HRAM 21H单元。单元。main()char x,*p;char code tab6=0,1,4,9,16,25;p=0 x20;x=tab*p;p+;*p=x;进入进入whilewhile表达式为表达式为 真真?循环体语句循环体语句退出循环退出循环执行下条语句执行下条语句表达式为真表达式为真?YN4.8.34.8.3、循环程序的设计、循环程序的设计 C C 语言的循环语句有以下几种形式语言的循环语句有以下几种形式 1.while(1.while(表达式表达式)语句;语句;其中表达式为循环条件,语句为其中表达式为循环条件,语句为循环体,当表达式值为真循环体,当表达式值为真(值为值为1)1),重复执行重复执行“语句语句”。2.do 2.do 语句;语句;while(while(表达式表达式)表达式为真执行循环体表达式为真执行循环体“语句语句”,直至表达式为假,退出循环执行下一,直至表达式为假,退出循环执行下一个语句。个语句。循环体语句循环体语句表达式为真表达式为真?退出循环退出循环执行下条语句执行下条语句进入进入whilewhileYN3.for(3.for(表达式表达式1 1;表达式;表达式2 2;表达式;表达式3 3;)语句;语句;执行下条语句执行下条语句赋表达式赋表达式 1 1 初值初值表达式表达式2 2为真为真?求解表达式求解表达式3 3循环体语句循环体语句 循环体语句可以循环体语句可以只有一条只有一条,以,以“;”结尾;也可以由结尾;也可以由多条组成多条组成复合语句,复合语句必须用复合语句,复合语句必须用 括起;也括起;也可以没有语句可以没有语句,这时通常用,这时通常用于等待中断。于等待中断。for(;);for(i=0;i=10;i+)sum+=i;for(i=0;i=10;i+)sum+=i;tot=tot+sum;NY例例4-9.4-9.将上例改用将上例改用forfor语句编程语句编程 main()int sum=0,i;for(i=0;i=10;i+)sum+=i;例例4-8.4-8.分析下列程序的执行结分析下列程序的执行结果:果:main()int sum=0,i;do sum+=i i+;while(i =10);本程序完成本程序完成0+1+2+0+1+2+10+10的的累加,执行后累加,执行后sum=55sum=55。4.7.44.7.4、分支程序的设计、分支程序的设计 C C语言的分支选择语句有以下几种形式:语言的分支选择语句有以下几种形式:1.If(1.If(表达式表达式)语句语句;句中表达式为真则执行语句,否则执行下一条语句。当花括号中的语句中表达式为真则执行语句,否则执行下一条语句。当花括号中的语句不只一条时,花括号不能省。句不只一条时,花括号不能省。2.If(2.If(表达式表达式)语句语句1 1;else else 语句语句2 2;句中表达式为真则执行语句句中表达式为真则执行语句1 1,否则执行语句,否则执行语句2 2再执行下一条语句再执行下一条语句,见见下面流程图。下面流程图。If If 语句可以嵌套。语句可以嵌套。表达式为真?表达式为真?语句语句下条语句下条语句N NY YIf If 语句流程语句流程 表达式为真?表达式为真?语句语句1 1下条语句下条语句语句语句2 2N NY YIf If else else 语句流程语句流程例:片内例:片内RAMRAM的的20H20H单元存放一个有符号数单元存放一个有符号数 x x,函数,函数y y与与x x有如下关系式:有如下关系式:(设设y y存放于存放于21H21H单元单元)x x0 y =20H x=0 x+5 x 0 ,程序如下,程序如下:main()signed char x,*p,*y;p=0 x20;y=0 x21;for(;)x=*p;if(x0)*y=x;if(x 0)*y=x+5;if(x=0)*y=0 x20;程序中为观察不同数的程序中为观察不同数的执行结果,采用了死循环语执行结果,采用了死循环语句句for(;)for(;),上机调试时要退,上机调试时要退出死循环可用出死循环可用Ctrl+CCtrl+C。3.Switch 3.Switch case case 语句语句 该语句常用于多分支转移,格式如下:该语句常用于多分支转移,格式如下:switch(switch(表达式表达式)case case 常量表达式常量表达式1:1:语句语句1 1;break break;case case 常量表达式常量表达式2:2:语句语句2 2;break break;case case 常量表达式常量表达式n:n:语句语句n n;break break;default;(default;(语句语句n+1;n+1;当当casecase语句后有语句后有breakbreak语句时,执行完这一语句时,执行完这一casecase语句后,跳出语句后,跳出switchswitch语句,当语句,当case case 后面无后面无breakbreak语句,程序将执行下语句,程序将执行下一条一条casecase语句。语句。如果如果casecase中常量表达中常量表达式值和表达式的值都不匹式值和表达式的值都不匹配,就执行配,就执行defaultdefault后面的后面的语句。如果无语句。如果无defaultdefault语句语句就退出就退出switchswitch语句。语句。defaultdefault的次序不影的次序不影响执行的结果,也可无此响执行的结果,也可无此语句。语句。说明:说明:语句首先进行表达式语句首先进行表达式的运算,当表达式的值与某的运算,当表达式的值与某一一casecase后面的常量表达式相后面的常量表达式相等,就执行它后面的语句。等,就执行它后面的语句。4-9.4-9.有两个数有两个数a a和和b b,根据,根据R3R3的内容转向不同子程序的内容转向不同子程序 r3=0r3=0,执行子程序,执行子程序pr0(pr0(完成两数相加完成两数相加)r3=1 r3=1,执行子程序,执行子程序pr1(pr1(完成两数相减完成两数相减)r3=2 r3=2,执行子程序,执行子程序pr2(pr2(完成两数相乘完成两数相乘)r3=3 r3=3,执行子程序,执行子程序pr3(pr3(完成两数相除完成两数相除)分析:分析:C C语言中的子程序即为函数,因此需编四个处理函数。语言中的子程序即为函数,因此需编四个处理函数。在在C51C51编译器中通过头文件编译器中通过头文件 reg51.hreg51.h可以识别特殊功能寄存器,可以识别特殊功能寄存器,但不能识别但不能识别R0R0R7R7通用寄存器,因此通用寄存器,因此R0R0R7R7只有通过绝对地址访问识别,只有通过绝对地址访问识别,程序如下:程序如下:#include#define r3 DBYTE0 x03int c,c1,a,b;Pr0()c=a+b;pr1()c=a-b;pr2()c=a*b;pr3()c=a/b;main()a=90;b=30;for(;)switch(r3)case 0:pr0();break;case 1:pr1();break;case 2:pr2();break;case 3:pr3();break;c1=56;