280 likes | 420 Views
4.6 函 数. C 语言程序由函数组成,下面介绍函数的要点 4.6.1 函数的分类及定义 从用户使用角度划分,函数分为 库函数 和 用户定义函数 库函数 是编译系统为用户设计的一系列标准函数 ( 见本书附录二 ) ,用户只需调用,而无需自己去编写这些复杂的函数,如前面所用到的头文件 reg51.h 等,有的头文件中包括一系列函数,要使用其中的函数必须先使用 # 包含语句,然后才能调用。 用户自定义函数 是用户根据任务编写的函数。 . 从参数形式上函数分为无参函数和有参函数。. 无参函数 : 函数中无参数定义。
E N D
4.6 函 数 C 语言程序由函数组成,下面介绍函数的要点 4.6.1函数的分类及定义 从用户使用角度划分,函数分为库函数和用户定义函数 • 库函数是编译系统为用户设计的一系列标准函数 (见本书附录二) ,用户只需调用,而无需自己去编写这些复杂的函数,如前面所用到的头文件reg51.h等,有的头文件中包括一系列函数,要使用其中的函数必须先使用#包含语句,然后才能调用。 • 用户自定义函数是用户根据任务编写的函数。. 从参数形式上函数分为无参函数和有参函数。
无参函数:函数中无参数定义。 • 有参函数:函数中定义形式参数,在调用时,调用函数用实际参数代替形式参数,调用完返回结果给调用函数。 4.6.2函数的定义 • 无参函数的定义: 返回值类型 函数名 ( ) {函数体语句} 如果函数没有返回值,可以将返回值类型设为void 函数以“{”开始,以“}”结束, • 有参函数的定义: 返回值类型 函数名 (形式参数表列) 形式参数类型说明 {函数体语句 return (返回形参名) }
也可以这样定义 返回值类型 函数名 (类型说明形式参数表列) { 函数体语句 return (返回形参名) } 其中形式参数表列的各项要用 "," 隔开,通过return语句将需返回的值返回给调用函数。 4.6.3.函数的调用 • 函数调用的形式为: 函数名 (实际参数表列) ; 对于无参函数当然不存在实际参数表列 实参和形参的数目相等类型一致。
函数的调用方式有三种 • ①函数调用语句:即把被调函数名作为调用函数的一个语句,如 fun1() ; • ② 被调函数作为表达式的运算对象 如 rett=2* get(a,b) ; 此时拿函数中的 a , b 应为实参,其以返回值参予式中的运算。 • ③ 被调函数作为另一个数的实际参数 如 m=max (a,get(a,b)); 函数 get(a,b)作为max ( ) 的一个实际参数被调用。
4.6.4 对被调函数的说明 • 如果被调函数出现在主调函数之后,在主调函数前应对被调函数作以说明,形式为 返回值类型 被调函数名 (形参表列) ; 如: int fun1(a,b);/*函数说明*/ main() /*主函数*/ { int d,u=3,v=2; d=2*fun1(u,v); } int fun1(a,b) int a,b;
{ int c ; c=a+b; return (c);} 上例中被调函数在后、在主调函数前对被调函数进行明 • 被调函数出现在主调函数之前,可以不对被调函数说明下面以一个简单例子来说明 int fun1(a,b) int a,b; { int c; c= a+b; return (c);}
main() { int d, u=3,v=2; d=2* fun1(u,v); } 此例中被调函数在主调函数前,不用说明 4.7 单片机的 C 语言编程实例 由于 C51 编译器是针对单片机的,因此 ANSI C 中的 scanf 和 printf 等对PC机的输入/输出语句无效,运算的数据可以通过变量置入或取出,
这时 C51 会自动安排使用的存贮单元。也可以用户自行通过具体的内存地址置入数据或从特定地址取出数据。 C 语言的上机调试和汇编程序使用同一仿真调试软件。 下面是一个C语言程序编译后生成的机器代码及对应的反汇编程序。
4.7.1、C语言程序的反汇编程序(源代码) 在2.4节曾用汇编语言完成了外部RAM的000EH单元和000FH单元的内容交换,现改用C语言编 程。C语言对地址的指示方法可以采用指针变量,也可以引用absacc.h头文件作绝对地址访 问,下面采用绝对地址访问方法。 # include <absacc.h> main() { char c ; for(; ;) {
c=XBYTE[14]; XBYTE[14]=XBYTE[15]; XBYTE[15]=c ; } } 程序中为方便反复观察,使用了死循环语句for(;;)只要用Ctrl+C即可退出死循环。 上面程序通过编译后机器代码和反汇编程序如下: 0000 020014 L JMP 0014H 0003 90000E MOV DPTR,#000EH 0006 E0 MOVX A,@DPTR 0007 FF MOV R7,A 0008 A3 INC DPTR 0009 E0 MOVX A,@DPTR
000A 90000E MOV DPTR,#000EH 000D F0 MOVX @DPTR,A 000E A3 INC DPTR 000F EF MOV A,R7 0010 F0 MOVX @DPTR,A 0011 80F0 SJMP 0003H 0013 22 RET 0014 787F MOV R0,#7FH 0016 E4 CLR A 0017 F6 MOV @R0,A 0018 D8FD DJNZ R0,0017H 001A 758107 MOV SP,#07H 001D 020003 LJMP 0003H
例中可见: ①一进入C语言程序,首先执行将内部RAM的0~7FH 128个单元清零,然后置SP为07H(视变量 多少不同,SP置不同值,依程序而定),因此如果要对内部RAM置初值,一定要在执行了一条 C语言语句后进行。 ②C语言程序设定的变量,C51自行安排寄存器或存贮器作参数传递区,通常在R0~R7(一组 或两组,视参数多少定),因此,如果对具体地址置数据,应避开这些R0~R7的地址。 ③如果不特别指定变量的存贮类型,通常被安排在内部RAM中。
4.7.2、顺序程序的设计 例1.完成19805×24503的编程 分析:两个乘数比较大,其积更大,采用unsigned long类型,设乘积存放在外部数据存贮 器0号开始的单元。程序如下: main() { unsigned long xdata *p; /*设定指针p*/ unsigned long a=19805; /* 设置a的类型*/ unsigned long b=24503,c; /*设置b和积c为 unsigned long类型,并赋初值 */ p=0; /*设地址指向0号单元*/
c=a*b; *p=c; } /*积存入外部RAM 0号单元*/ 上机通过软件仿真调试,在变量观察窗口看到运算结果 c=485281915,即为乘积的十进制 数。观察XDATA区(外部RAM)的0000H~0003H单元分别为1C EC D0 7B,即存放的为485281915 的十六进制数。 观察DATA区: 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 1C EC D0 7B 00 00 4D 5D 00 00 5F B7 C变量(积) a变量 b变量
可见定义为unsigned long类型,给每个变量分配四个单元,如果定义类型不对,将得不到 正确的结果。 如果未定义变量类型,默认为内部 RAM,如a 、b、 c变量。 对于复杂的运算通常采用查表的方法。如同汇编程序设计一样,在程序存贮器建立一张表, 在C语言中表格定义为数组,表内数据(元素)的偏移量表现为下标。数组的使用如同变量一 样,要先进行定义:即说明数组名、维数、数据类型和存贮类型,在定义数组的同时还可以 给数组各元素赋初值。通过下例说明C51数组的定义方法和用C语言编查表程序的方法。
例4-6 片内RAM 20H单元存放着一个0~05H的数,用查表法,求出该数的平方值放入内部RAM 21H单元 main(){ char x,*p; char code tab[6]={0,1,4,9,16,25}; p=0x20; x=tab[*p]; p++; *p=x; }
进入while 表达式为 真? 表达式为 真? 退出循环 执行下条语句 循环体语句 4.7.3、循环程序的设计 C语言的循环语句有以下几种形式 1. while(表达式){语句;} 其中表达式为循环条件,语句为循环体,当表达式值为真(值为1),重复执行“语句”。
进入while 循环体语句 表达式为 真? 执行下条语句 2. do {语句;} while (表达式) 表达式为真执行循环体“语句”,直至表达式为假,退出循环执行下一个语句。
求解表达式 1 表达式 2为真? 循环体语句 求解表达式3 执行下条语句 • 3.for (表达式1;表达式2;表达式3;) { 语句;} 语句可只一条以“;”结尾;可以多条组成复合语句,复合语句必须用{}括起;也以没有语句,通常用于等待中断,或查询。
例4-8.分析下列程序的执行结果: main( ){ int sum = 0,i; do { sum+ = i i++; } while(i < = 10); } 本程序完成0+1+2+…+10的累加,执行后sum=55
例4-9.将例2改用for语句编程 main{ int sum=0,i ; for(i=0 ; i <=10 ; i++) sum+=i ; } 4.7.4、分支程序的设计 C语言的分支选择语句有以下几种形式: 1. If (表达式){语句;} 句中表达式为真执行语句,否则执行下一条语句。当花括号中的语句不只一条,花括号不能 省。 2. If (表达式){语句1;} else {语句2;}
表达式为真? 表达式为真? N N Y Y 语句 语句1 语句2 下条语句 下条语句 句中表达式为真执行语句1,否则执行语句 2再执行下一条语句,见下面流程图。If 语句可以嵌套。 If 语句流程 If …else 语句流程
例1.片内RAM的20H单元存放一个有符号数 x,函数y与x有如下关系式:(设y存放于21H单元) x x>0 y = 20H x=0 x+5 x <0 ,程序如下: main(){ Signed char x,*p,*y; p=0x20;y=0x21; for(;;){ x=*p; if (x>0) *y=x; if (x <0) *y=x+5; if (x==0) *y=0x20; } }
程序中为观察不同数的执行结果,采用了死循环语句for(;;),上机调试时退出死循环可用Ctrl+C。程序中为观察不同数的执行结果,采用了死循环语句for(;;),上机调试时退出死循环可用Ctrl+C。 3. Switch ……case 语句 该语句常用于多分支转移,格式如下: switch(表达式){ case 常量表达式1; {语句1;} break; case 常量表达式2; {语句2;} break; … … …; case 常量表达式n; {语句n;} break; default;{(语句n+1;} }
说明 ① 语句先进行表达式的运算,当表达式的值 与某一case后面的常量表 达式相等,就执行它后 面的语句。 ② 当case语句后有break语句时,执行完这一case语句后,跳出switch语句,当case 后面无break语句,程序将执行下一条case语句。 ③ 如果case中常量表达式值和表达式的值都不匹配,就执行default后面的语句。 如果无default语句就退出switch语句。 ④ default的次序不影响执行的结果,也可无此语句。
4-9.有两个数a和b,根据R3的内容转向不同子程序4-9.有两个数a和b,根据R3的内容转向不同子程序 r3=0,执行子程序pr0(完成两数相加) r3=1,执行子程序pr1(完成两数相减) r3=2,执行子程序pr2(完成两数相乘) r3=3,执行子程序pr3(完成两数相除) 分析:① C语言中的子程序即为函数,因此需编四个处理函数。 ② 在C51编译器中通过头文件 reg51.h可以识别特殊功能寄存器,但不能识别R0~R7通用寄 存器,因此R0~R7只有通过绝对地址访问识别,程序如下:
Pr0( ) {c=a+b;} pr1( ) {c=a-b;} pr2( ) {c=a*b;} pr3( ) {c=a/b;} main( ){ a=90;b=30; for (; ;){ switch(r3) { pr0( );break ; case 1: pr1( );break; case 2: pr2( );break; case 3: pr3( );break; } c1=56; } } # include <absacc.h># define r3 DBYTE[0x03]int c,c1,a,b; case 0: