28
4.6 C 函函函函函函函函函 函函函函函函函函函 4.6.1 函函 函函函函函函 函函函函函函函函函 函函函函 函函函函函函函函函 函函函函函函函 函函函函函函函 函函函函函函 统一 ( 函函 函函 函) 函函函函函函函 函函函函函函函函函函函函函函函函 函函函函函 ,,, 函函函函函 reg51.h 函 函函函函函函函函函 函函函函函 函函函函 ,一, 函函函函函函函函函 # 函函函函 函函函函函函函 ,。 函函函函函函函函函函函函函函函函函函函. 函函函函函函 函函函函函函函函函函函函函

4.6 函 数

Embed Size (px)

DESCRIPTION

4.6 函 数. C 语言程序由函数组成,下面介绍函数的要点 4.6.1 函数的分类及定义 从用户使用角度划分,函数分为 库函数 和 用户定义函数 库函数 是编译系统为用户设计的一系列标准函数 ( 见本书附录二 ) ,用户只需调用,而无需自己去编写这些复杂的函数,如前面所用到的头文件 reg51.h 等,有的头文件中包括一系列函数,要使用其中的函数必须先使用 # 包含语句,然后才能调用。 用户自定义函数 是用户根据任务编写的函数。 . 从参数形式上函数分为无参函数和有参函数。. 无参函数 : 函数中无参数定义。 - PowerPoint PPT Presentation

Citation preview

Page 1: 4.6   函  数

4.6 函 数 C 语言程序由函数组成,下面介绍函数的要点

4.6.1 函数的分类及定义 从用户使用角度划分,函数分为库函数和用户定

义函数• 库函数是编译系统为用户设计的一系列标准函

数 ( 见本书附录二 ) ,用户只需调用,而无需自己去编写这些复杂的函数,如前面所用到的头文件 reg51.h 等,有的头文件中包括一系列函数,要使用其中的函数必须先使用 # 包含语句,然后才能调用。

• 用户自定义函数是用户根据任务编写的函数。 . 从参数形式上函数分为无参函数和有参函数。

Page 2: 4.6   函  数

• 无参函数 : 函数中无参数定义。• 有参函数:函数中定义形式参数,在调用时,调用函数

用实际参数代替形式参数,调用完返回结果给调用函数。 4.6.2 函数的定义

• 无参函数的定义: 返回值类型 函数名 ( ) { 函数体语句 } 如果函数没有返回值,可以将返回值类型设为 void 函数以“ {” 开始,以“ }” 结束,• 有参函数的定义: 返回值类型 函数名 ( 形式参数表列 ) 形式参数类型说明 { 函数体语句 return ( 返回形参名 )}

Page 3: 4.6   函  数

也可以这样定义 返回值类型 函数名 ( 类型说明形式参数表列 )

{ 函数体语句 return ( 返回形参名 )

}

其中形式参数表列的各项要用 " , " 隔开,通过 return 语句将需返回的值返回给调用函数。

4.6.3. 函数的调用• 函数调用的形式为: 函数名 ( 实际参数表列 ) ; 对于无参函数当然不存在实际参数表列 实参和形参的数目相等类型一致。

Page 4: 4.6   函  数

函数的调用方式有三种

• ① 函数调用语句:即把被调函数名作为调用函数的一个语句,如 fun1() ;

• ② 被调函数作为表达式的运算对象

如 rett=2* get(a,b) ;

此时拿函数中的 a , b 应为实参,其以返回值参予式中的运算。

• ③ 被调函数作为另一个数的实际参数

如 m=max (a,get(a,b)) ;

函数 get(a,b) 作为 max ( ) 的一个实际参数被调用。

Page 5: 4.6   函  数

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;

Page 6: 4.6   函  数

{ int c ;

c=a+b;

return (c);}

上例中被调函数在后、在主调函数前对被调函数进行明

• 被调函数出现在主调函数之前,可以不对被调函数说明下面以一个简单例子来说明

int fun1(a,b)

int a,b;

{ int c;

c= a+b;

return (c);}

Page 7: 4.6   函  数

main()

{

int d, u=3,v=2;

d=2* fun1(u,v);

}

此例中被调函数在主调函数前,不用说明

4.7 单片机的 C 语言编程实例

由于 C51 编译器是针对单片机的,因此 ANSI C

中的 scanf 和 printf 等对 PC 机的输入/输出语句无效,运算的数据可以通过变量置入或取出,

Page 8: 4.6   函  数

这时 C51 会自动安排使用的存贮单元。也可以用户自行通过具体的内存地址置入数据或从特定地址取出数据。

C 语言的上机调试和汇编程序使用同一仿真调试软件。

下面是一个 C 语言程序编译后生成的机器代码及对应的反汇编程序。

Page 9: 4.6   函  数

4.7.1 、 C 语言程序的反汇编程序 ( 源代码 )

在 2.4 节曾用汇编语言完成了外部 RAM 的 000EH 单元和 000FH 单元的内容交换,现改用 C语言编 程。 C 语言对地址的指示方法可以采用指针变量,也可以引用 absacc.h 头文件作绝对地址访 问,下面采用绝对地址访问方法。

# include <absacc.h>

main()

{ char c ;

for(; ;)

{

Page 10: 4.6   函  数

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

Page 11: 4.6   函  数

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

Page 12: 4.6   函  数

例中可见:

① 一进入 C 语言程序,首先执行将内部 RAM的 0 ~ 7FH 128 个单元清零,然后置 SP 为 07H( 视变量 多少不同, SP 置不同值,依程序而定 ) ,因此如果要对内部 RAM 置初值,一定要在执行了一条 C 语言语句后进行。

②C 语言程序设定的变量, C51 自行安排寄存器或存贮器作参数传递区,通常在 R0 ~ R7( 一组 或两组,视参数多少定 ) ,因此,如果对具体地址置数据,应避开这些 R0 ~ R7 的地址。

③ 如果不特别指定变量的存贮类型,通常被安排在内部 RAM 中。

Page 13: 4.6   函  数

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 号单元 */

Page 14: 4.6   函  数

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 变量

Page 15: 4.6   函  数

可见定义为 unsigned long 类型,给每个变量分配四个单元,如果定义类型不对,将得不到 正确的结果。

如果未定义变量类型,默认为内部 RAM ,如 a 、b 、 c 变量。

对于复杂的运算通常采用查表的方法。如同汇编程序设计一样,在程序存贮器建立一张表, 在 C 语言中表格定义为数组,表内数据 ( 元素 ) 的偏移量表现为下标。数组的使用如同变量一 样,要先进行定义:即说明数组名、维数、数据类型和存贮类型,在定义数组的同时还可以 给数组各元素赋初值。通过下例说明 C51 数组的定义方法和用 C 语言编查表程序的方法。

Page 16: 4.6   函  数

例 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;

}

Page 17: 4.6   函  数

4.7.3 、循环程序的设计 C 语言的循环语句有以下几种形式

1. while( 表达式 ){ 语句; }

其中表达式为循环条件,语句为循环体,当表达式值为真 ( 值为 1) ,重复执行“语句”。

进入 while

表达式为 真 ?

循环体语句 退出循环执行下条语句

表达式为 真 ?

Page 18: 4.6   函  数

2. do { 语句; } while ( 表达式 )

表达式为真执行循环体“语句”,直至表达式为假,退出循环执行下一个语句。

进入 while

循环体语句

表达式为 真 ?

执行下条语句

Page 19: 4.6   函  数

• 3.for ( 表达式 1 ;表达式 2 ;表达式 3 ; ) { 语句; }

执行下条语句循环体语句

求解表达式 1

表达式 2 为真 ?

求解表达式 3

语句可只一条以“;”结尾;可以多条组成复合语句,复合语句必须用 {} 括起;也以没有语句,通常用于等待中断,或查询。

Page 20: 4.6   函  数

例 4-8. 分析下列程序的执行结果:

main( ){

int sum = 0,i;

do {

sum+ = i

i++ ;

}

while(i < = 10);

本程序完成 0+1+2+…+10 的累加,执行后 sum=55

Page 21: 4.6   函  数

例 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 ; }

Page 22: 4.6   函  数

句中表达式为真执行语句 1 ,否则执行语句 2再执行下一条语句 , 见下面流程图。 If 语句可以嵌套。

表达式为真?

语句 1

下条语句

语句 2

N

Y 表达式为真?

语句

下条语句

N

Y

If 语句流程

If …else 语句流程

Page 23: 4.6   函  数

例 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; } }

Page 24: 4.6   函  数

程序中为观察不同数的执行结果,采用了死循环语句 for(;;) ,上机调试时退出死循环可用 Ctrl+C 。

3. Switch …… case 语句 该语句常用于多分支转移,格式如下: switch( 表达式 ){ case 常量表达式 1 ; { 语句 1 ; } break ; case 常量表达式 2 ; { 语句 2 ; } break ;

… … …; case 常量表达式 n ; { 语句 n ; } break ;

default;{( 语句 n+1;} }

Page 25: 4.6   函  数

说明 ① 语句先进行表达式的运算,当表达式的值与某一 case 后面的常量表 达式相等,就执行它后面的语句。 ② 当 case 语句后有 break 语句时,执行完这一 case

语句后,跳出 switch 语句,当 case 后面无 break 语句,程序将执行下一条 case 语句。

③ 如果 case 中常量表达式值和表达式的值都不匹配,就执行 default 后面的语句。 如果无 default 语句就退出 switch 语句。

④ default 的次序不影响执行的结果,也可无此语句。

Page 26: 4.6   函  数

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 只有通过绝对地址访问识别,程序如下:

Page 27: 4.6   函  数

# include <absacc.h># define r3 DBYTE [ 0x03 ]int 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)

{

pr0( );break ;

case 1:

pr1( );break;

case 2:

pr2( );break;

case 3:

pr3( );break;

}

c1=56;

} }

case 0:

Page 28: 4.6   函  数

在上述程序中,为便于调试观察,加了 C1=56 语句,并使用了死循环语句 for(;;) ,用 Ctrl+C 可退出死循环。