第六章 函数.docx
- 文档编号:15732320
- 上传时间:2023-07-07
- 格式:DOCX
- 页数:18
- 大小:23.08KB
第六章 函数.docx
《第六章 函数.docx》由会员分享,可在线阅读,更多相关《第六章 函数.docx(18页珍藏版)》请在冰点文库上搜索。
第六章函数
第六章函数
一、函数的分类与定义
1、函数的分类
从C语言程序的结构上划分,C语言函数分为主函数main()和普通函数两种,而对于普通函数,又可以分为标准库函数和用户自定义函数。
1)标准库函数
标准库函数是由C编译系统提供的库函数,在C编译系统中将一些独立的功能模块编写成公用函数,并将它们集中存放在系统的函数库中,供程序设计时使用,称之为标准库函数。
丰富的可直接调用的库函数是C51功能及其高效率的重要体现之一,多使用库函数使程序代码简单,结构清晰,易于调试和维护。
C51几类重要库函数及简要说明
对于标准C中原有的,在此不再说明,如MATH.H中的各数学运算函数。
① 专用寄存器include文件
包括了所有8051的SFR及其位定义,一般系统都必须包括本文件。
如REG51.H,AT89X51.H等。
② 绝对地址文件absacc.h,见P337
该文件中实际只定义了几个宏,以确定各存储空间的绝对地址,如定义了XBYTE宏,允许用户访问8051外部数据存储器中的某一字节。
③存储器分配函数,位于stdlib.h中,见P340表B-11.
④字符串操作函数位于“string.h”中,见P341
其中包括拷贝比较移动等函数如:
memccpy、memchr、memcmp、memcpy、memmove、memset
这些函数对缓冲区进行处理很方便。
⑤ 流函数输入输出,位于“stdio.h”中,见P344。
流函数缺省为8051串口,如要修改,比如改为LCD显示,可修改lib目录中的getkey.c及putchar.c源文件,然后在库中替换它们即可定义用户的I/O口数据读写。
2)用户自定义函数
用户自定义函数是用户根据自己的需要而编写的函数。
从函数定义的形式上可以将其划分无参数函数、有参数函数和空函数。
无参数函数被调用时,既无参数输入,也不返回结果给调用函数,它是为完成某种操作而编写的函数。
有参数函数在被调用时,必须提供实际的输入参数,必须说明与实际参数一一对应的形式参数,并在函数结束时返回结果供调用它的函数使用。
定义空函数的目的是为了以后程序功能的扩充。
2、函数的定义
C51对函数的功能进行了扩展,函数定义的完整形式如下:
返回数据类型函数名(形式参数列表)[reentrant][interruptn][usingm]
{局部变量定义
函数体;}
(注意对于原型函数的说明,和定义函数相似,但无函数体,也不能说明工作寄存器组的切换usingn和中断说明interrupt函数。
)
其中:
1)函数类型
静态函数和外部函数。
(1)静态函数(内部函数,默认)
静态函数只能在定义它的文件中被调用,而不能在其他文件中的函数所调用。
(2)外部函数
外部函数可以在定义它的文件和其它文件中被调用。
可以在函数定义和调用时使用extern说明是外部函数。
但必须注意:
在一个文件中,若将主程序放到前面,对后面出现的函数,就必须在文件开始进行说明,说明方式同普通C语言。
否则出现警告如下:
函数**.C(5):
warningC206:
'func':
missingfunction-prototype
所以编程时,要习惯将main()放到最后。
(见“外部函数”)
例:
文件1:
#include
externadd(x1,x2);
unsignedchardatax1=12,x2=2,y;
main()
{
y=add(x1,x2);
}
文件2:
datacharx3;
add(x1,x2)
{
x3=x1+x2;
returnx3;
}
2)函数返回值与数据类型
如果返回数据,进行说明;如果不返回,一般用void说明,也可以不说明。
函数返回值通过returnx返回,返回值是通过函数名带回的,所以一个函数只能有一个返回值。
上例中的x3和y。
3)形参与实参
形参:
在定义函数时,函数名后面括号中的变量称为“形参”,定义时不赋值,由调用函数将值传过来。
实参:
主调用函数后面括号中的表达式为“实参”,实参必须有确定的值。
该值在调用时按对应关系传递给形参。
C语言中参数传递是单向的。
4)可重入函数
什么是可重入函数?
可重入函数就是允许被递归调用的函数。
函数的递归调用是指当一个函数正被调用尚未返回时,又直接或间接调用函数本身。
有两种情况,一种是自身循环调用,另一种是其他函数调用,如程序中正在调用某个函数,而中断中也调用,就可能出现同时调用。
一般的函数不能做到这样,只有重入函数才允许递归调用。
因为8051内部堆栈空间的限制,为了提高效率,C51没有提供这种堆栈,而是提供一种压缩栈。
每个函数有一个给定存储空间,用于存放局部变量。
函数中的每个变量都存放在这个空间的固定位置。
当递归调用该过程时会导致变量被覆盖,所以通常情况下C51中的函数是不能重入的。
可重入函数为此必须使用reentrant函数属性来声明函数是可重入的。
与不可重入函数的参数传递和局部变量的存储分配方法不同,C51编译器为再入函数生成一个模拟栈,通过这个模拟栈来完成参数传递和存放局部变量。
这样每次函数调用时的局部变量都会被单独保存,再入函数一般占用较大的内存空间,运行起来也比较慢,并且不允许传递bit类型的变量,也不能定义局部位变量。
可重入函数经常在实时系统中应用,也可在中断函数和非中断函数同时调用同一个函数时使用。
5)规定函数使用的寄存器组切换usingm
可使用using函数说明属性来规定函数所使用的寄存器组。
m是一个0-3的整形参数,分别对应0-3组工作寄存器。
这个参数表示使用的寄存器组的编号,这个参数不能使用带运算符的表达式。
using属性只能在函数定义中使用,不能在函数原型声明中使用。
使用using属性的函数将自动完成以下操作:
进入函数前,将当前使用的寄存器组的标号保存在堆栈中。
更改PSW的寄存器组选择位,选择设定的寄存器组作为当前的寄存器组。
函数退出时,将寄存器组恢复成进入函数前的寄存器组。
6)中断函数说明interruptn
C51最大支持32个中断,在单片机中n常用0-5。
对应中断源见P177。
注意:
仅能在函数定义时使用interrupt函数属性,不能在函数声明时使用interrupt函数属性。
中断函数在运行过程中自动完成以下工作:
1)当中断产生时,将特殊功能寄存器ACC、B、DPH、DPL、PSW的值将被保存在堆栈中。
2)如果中断函数未使用using属性进行修饰,中断函数中所使用的寄存器的值将保存在堆栈中。
3)中断函数运行完成退出时,堆栈中保存的数据将被恢复。
4)中断函数退出时,其对应的汇编代码使用RETI指令退出。
中断函数应遵循以下规则:
(1)中断函数不能进行参数传递。
(要传递参数怎么办)
(2)中断函数没有返回值。
(要返回值怎么办)
(3)不能在其它函数中直接调用中断函数
(4)如果在中断中调用了其他函数,必须保证这些函数和中断函数使用了相同的寄存器组,并且这些函数应为可重入函数。
(5)C51编译器从绝地址8n+3产生一个中断向量,其中n为中断号。
该向量包含一个到中断函数入口地址的绝对跳转。
(因为外部中断0从0003H开始,然后每个中断占8个字节)
例1:
定义了一个函数
intfunc(x1,x2,x3);//函数声明
main()
{
inta1=1,a2=2,a3=3,a4;
a4=func(a1,a2,a3);
}
intfunc(x1,x2,x3)
{
intdatap2;
p2=x1+x2+x3;
returnp2;
}
在单片机c语言中,用下面的方法定义更方便:
intfunc(x1,x2,x3)
{
intdatap2;
p2=x1+x2+x3;
returnp2;
}
main()
{
inta1=1,a2=2,a3=3,a4;
a4=func(a1,a2,a3);
}
二、函数与指针
函数在编译时,编译器为每个函数分配一个入口地址,这个入口地址就称为函数的指针。
函数的指针可以赋给函数指针变量,并能通过函数指针变量调用它所指向的函数。
指向函数指针变量的定义格式如下:
存储器类型数据类型(*指针变量名)(参量列表)
存储器类型是定义将指针变量存储的位置。
说明:
1)函数指针变量定义时,两侧的()是必须的。
2)指向函数的指针变量可以指向任何一个格式相同的函数的入口地址。
3)C语言约定,函数名本身就是函数的入口地址。
4)当函数指针变量指向函数时,即可用它来调用所指的函数。
调用格式为(*指针变量名)(实参表)
例1:
函数指针的使用
intadd(inta,intb)
{returna+b;}
intsub(inta,intb)
{returna-b;}
main()
{
int(*pFunc)(int,int);//定义函数指针变量,注意要定义数据类型
intx,y;
pFunc=add;//对函数指针变量赋值
x=(*pFunc)(3,4);
pFunc=sub;//由于两个函数格式相同,故定义了一个函数指针
y=(*pFunc)(5,3);
}
函数的指针类型为普通指针。
(函数指针定义)
三、函数的调用
函数的调用,必须保证被调用函数是已经存在的函数或者是库函数;如果是库函数,必须用#include将所用函数信息包含到程序中。
如#include
对于无参数和无返回值的函数调用很简单,不再重复。
在函数调用中,最关键的是参数的传递与返回值,同普通c语言一样,调用时,可以由实元传递多个参数到函数中,但在返回时,只能返回一个值给函数名。
1、数组作为函数的参数(函数1)
下面调用函数时,实元传递给哑元的是数组名,将数组传递过去,实际是传递地址。
#include
intfunc(x)
intx[3];//注意说明方法,定义了一个数组,在花括号外
{intp2;
p2=x[0]+x[1]+x[2];
returnp2;}
main()
{inta[3]={3,6,9},a2;
a2=func(a);//数组a作为函数的参数,传数组地址
}
将上式的数组,也可以改为指针。
(函数2)
#include
intfunc(x)
int*x;//定义指针,也就是数组的首地址,以下要按指针运算
{chari;
intp2=0;
for(i=0;i<3;i++)
p2+=*(x+i);//*为间接访问运算符
returnp2;}
main()
{
inta[3]={3,6,9},a2;
a2=func(a);
}
2、指针作为函数的参数(函数3)
#include
intfunc(x)
int*x;//必须还要定义相同类型的指针
{
intp2;
p2=2*(*x);//传过来的内容乘2
returnp2;}
main()
{
inta=5,a2;
int*p=&a;//定义指针并赋值
a2=func(p);}//用指针将a的地址传到函数
3、用指针作为返回值(函数4)
由于返回值只有一个,所以要返回多个值时,就要用数组或者指针。
注意指针函数的概念。
#include
int*func()//由于返回值为地址,要定义指针函数
{intp2[3]={2,4,6};
returnp2;}//将数组的首地址返回
main()
{unsignedchari;
inta;
int*a2;//接收的是地址
a2=func();//将传递过来的地址送入a2中
for(i=0;i<3;i++)
a+=*(a2+i);//分别取数组的3个数相加
}
特点:
一次可以返回多个值
四、函数调用时参数的传递规定
为了便于混合编程,在单片机c语言中,对函数调用和返回,参数的传递都有严格的规定,参数的传递途径有:
寄存器、存储器和堆栈(重入函数);其返回参数均通过寄存器传递。
利用寄存器传递参数的规则如下:
参数编号
char
int
Long,float
一般指针
第1个参数
R7
R6(高),R7(低)
R4(高)-R7(低)
R1,R2,R3
第2个参数
R5
R4(高),R5(低)
使用固定地址
存储类型在R3,地址高位在R2,低位在R1
第3个参数
R3
R2(高),R3(低)
例如:
(1)func(chara)
a在R7中传递到函数。
(2)intfunc(inta,intb,char*c)
参数a、b、c分别通过R6、R7;R4、R5;R1、R2、R3传递。
(3)func(floatg,charh)
g在R4、R5、R6、R7中传递,h就不能在R7中传递,只能在堆栈中传递。
返回参数传递规则:
返回类型
使用的寄存器
说明
Bit
Cy
单个位通过进位标志Cy返回
char
R7
单个字节类型通过R7返回
int
R6,R7
高字节在R6,低字节在R7
long
R4-R7
最高字节在R4,最低字节在R7
float
R4-R7
32位IEEE格式
一般指针
R1-R3
存储类型在R3,地址高位在R2,低位在R1
注意:
1)返回值必须用return语句返回,返回值的类型是定义时的类型;
2)调用1次只能返回1个值;
例:
参数传递
#include
charfunc(x1,x2,x3)
{
chardatap2;
p2=x1+x2+x3;
returnp2;
}
main()
{
chara1=1,a2=2,a3=3;
chardataa4;
a4=func(a1,a2,a3);
}(调参数传递)
下面是编译连接后的汇编语言,可以看到参数在寄存器中的传递
C:
0x000002001ELJMPC:
001E
8:
main()
9:
{
10:
chara1=1,a2=2,a3=3;
11:
chardataa4;
C:
0x00037F01MOVR7,#0x01
C:
0x00057D02MOVR5,#0x02
C:
0x00077B03MOVR3,#0x03
【调用前,将参数按规定存入寄存器R7,R5,R3】
12:
a4=func(a1,a2,a3);
C:
0x0009EFMOVA,R7
………
C:
0x001812002ALCALLfunc(C:
002A)
C:
0x001B8F08MOV0x08,R7
【从R7中接收返回值,送堆栈0x08】
13:
}
………
2:
charfunc(x1,x2,x3)
3:
{
4:
chardatap2;
5:
p2=x1+x2+x3;
【注意:
R7,R5,R3中分别为主函数中传过来的数据】
C:
0x002AEFMOVA,R7
C:
0x002B2DADDA,R5
C:
0x002C2BADDA,R3
【函数返回前,将返回值按规定送入R7】
C:
0x002DFFMOVR7,A
6:
returnp2;
C:
0x002E22RET
(调用函数4指针返回)
在下面,函数的参数是数组,按指针传递,传递参数用R1、R2、R3。
其中R1为存储类型,R2、R3为地址。
12:
intdataa[3]={3,6,9},a2;
………
C:
0x016C1200D3LCALLC?
COPY(C:
00D3)
13:
a2=func(a);//a为数组
C:
0x016F7B00MOVR3,#0x00
C:
0x01717A00MOVR2,#0x00
C:
0x01737908MOVR1,#0x08
【普通指针占3字节,由R1,R2,R3传递】
C:
0x017512017DLCALLfunc(C:
017D)
C:
0x01788E0EMOV0x0E,R6
C:
0x017A8F0FMOV0x0F,R7
14:
}
C:
0x017C22RET
以下是指针作为参数传递的例子。
(见例3)
10:
main()
11:
{
12:
inta=5,a2;
C:
0x002E750800MOV0x08,#0x00;//地址
C:
0x0031750905MOV0x09,#0x05
13:
int*p=&a;//将a的地址送入指针
C:
0x00347B00MOVR3,#0x00;指针参数
C:
0x00367A00MOVR2,#0x00
C:
0x00387908MOVR1,#0x08//地址
14:
a2=func(p);//指针作为函数的参数
C:
0x003A12004ELCALLfunc(C:
004E)
C:
0x003D8E0AMOV0x0A,R6;返回值
C:
0x003F8F0BMOV0x0B,R7
15:
}
………
4:
intfunc(x)//要放在前面,对有参数传递的函数说明
5:
int*x;//注意说明位置
6:
{
7:
intp2;
8:
p2=2*(*x);//中间是乘号
C:
0x004E120003LCALLC?
ILDPTR(C:
0003)
C:
0x005125E0ADDA,ACC(0xE0)
C:
0x0053FFMOVR7,A
C:
0x0054E5F0MOVA,B(0xF0)
C:
0x005633RLCA
C:
0x0057FEMOVR6,A
【返回值在R6,R7】
9:
returnp2;}
C:
0x005822RET
下面是函数指针定义的例子中,两个整型参数的传递与返回一个整型参数的过程。
10:
x=(*pFunc)(3,4);//传递3和4
C:
0x0014F582MOVDPL(0x82),A
C:
0x00168A83MOVDPH(0x83),R2
C:
0x00187D04MOVR5,#0x04;第一个低位送R5
C:
0x001A7C00MOVR4,#0x00;高位送R4
C:
0x001C7F03MOVR7,#0x03;第二个低位送R7
C:
0x001E7E00MOVR6,#0x00;高位送R6
C:
0x002012006BLCALLC?
ICALL2(C:
006B)
C:
0x00238E08MOV0x08,R6;返回高位送R6
C:
0x00258F09MOV0x09,R7;返回低位送R7
11:
pFunc=sub;//由于两个函数格式相同,故定义了一个函数指针
C:
0x00277BFFMOVR3,#0xFF;函数指针的存储器类型,程序代码为code,故为0XFF
C:
0x00297A00MOVR2,#0x00;被调用函数的地址高位
C:
0x002B7958MOVR1,#0x58;被调用函数的地址低位
C:
0x002D900000MOVDPTR,#0x0000
C:
0x0030EBMOVA,R3
C:
0x0031F0MOVX@DPTR,A
C:
0x0032A3INCDPTR
C:
0x0033EAMOVA,R2
C:
0x0034F0MOVX@DPTR,A
C:
0x0035A3INCDPTR
C:
0x0036E9MOVA,R1
C:
0x0037F0MOVX@DPTR,A
13:
}
C:
0x004B22RET
C:
0x004C787FMOVR0,#0x7F
C:
0x004EE4CLRA
C:
0x004FF6MOV@R0,A
C:
0x0050D8FDDJNZR0,C:
004F
C:
0x005275810BMOVSP(0x81),#0x0B
C:
0x0055020003LJMPmain(C:
0003)
3:
intsub(inta,intb)//被调用时的参数传递关系,返回为整型
C:
0x0058C3CLRC
………
C:
0x005EFEMOVR6,A;结果送R6
C:
0x005F22RET
2:
{returna+b;}//定义了一个加法函数
C:
0x0060EFMOVA,R7
C:
0x00612DADDA,R5
C:
0x0062FFMOVR7,A
C:
0x0063EEMOVA,R6
C:
0x00643CADDCA,R4
C:
0x0065FEMOVR6,A
C:
0x006622RET
C?
ICALL:
⑴本征库函数(intrinsic routines)和非本征证库函数
本征函数:
指编译时直接将固定的代码插入当前行,不是用ACALL和LCALL语句来实现。
非本征函数:
必须由ACALL及LCALL调用
crol_,_cror_
将char型变量循环向左(右)移动指定位数后返回
iror_,_irol_
将int型变量循环向左(右)移动指定位数后返回
lrol_,_lror_
将long型变量循环向左(右)移动指定位数后返回
nop_
相当于插入NOP
testbit
相当于JBC bitvar测试该位变量并跳转同时清除
_chkfloat_
测试并返回源点数状态
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第六章 函数 第六