[C] 第6章 C51函数
文章目录
- C51函数
- 函数概述
- 从函数定义角度分类
- 从函数有无返回值分类
- 从函数有无参数
- 函数定义的一般形式
- C51无参函数的一般形式
- C51有参函数的一般形式
- 函数的形式参数和实际参数
- 形式参数
- 实际参数
- 函数的返回值
- 一般形式为:
- 函数的形参和实参的特点
- 函数的调用
- 函数的调用一般形式有以下几种:
- 调用函数需要注意的事项
- 函数的嵌套调用
- 数组作为函数参数
- 用数组元素作实参
- 用数组名作实参
- 局部变量和全局变量
- 局部变量
- 局部变量的作用域
- 全局变量
- 使用全局变量的注意事项
- 变量的存储类型
- 静态局部变量
- 静态全局变量
- register变量
- 外部变量
- 中断函数定义和应用
- 中断函数的定义
- 使用中断函数应注意的事项
- 中断函数定义和应用
- 中断函数的定义
- 使用中断函数应注意的事项
C51函数
-
C51源程序由一个或多个函数组成的。
-
函数是C51程序的基本模块,通过对函数模块的调用实现特定的功能。
函数概述
(1)完整的C51程序:不限制,但void main(void)函数只能有1个;
(2)不能嵌套定义,即在一个函数的函数体内,不能再定义另一个函数;
(3)允许嵌套调用,函数之间允许相互调用;
(4)函数还可以自己调用自己,称为递归调用;
(5)main函数是主函数,它可以调用其他函数,而不允许被其他函数调用。
- 举例(递归:阶乘)
int factorial(int n) {if (n == 0) return 1;else return n * factorial(n-1);
}
从函数定义角度分类
- 库函数
由C51编译系统提供,用户无需定义,也不必再程序中作类型说明,在使用时只需要在程序前包含有该函数原型的头文件,即可在程序中直接带调用。如printf函数就是库函数。
- 用户定义函数
(1)由用户按特定需要所编写的函数称为用户自定义函数;
(2)用户定义函数,不仅要在程序中定义函数本身,而且要在主函数模块中还必须要对该被调函数进行类型说明,然后才能使用。
从函数有无返回值分类
- 有返回值函数
(1)此类函数被调用后,将向调用者返回执行结果,称为函数返回值。
(2)用户定义的这种需要返回函数值的函数,必须在函数定义和说明中明确返回值的函数。
- 举例(某单片机系统程序中的矩阵键盘的键值处理函数)
unsigned char KeyPad(unsigned char key) //说明函数返回值类型为unsigned char类型
{unsigned char keyID;switch(key){case 0x7e:keyID = 0;break; //0号按键被按下····省略的的代码}return keyID; //返回的键值
}
- 无返回值函数
(1)此类函数用于完成某项特别的任务,执行完成后不向调用者返回函数值。
(2)由于函数无需返回值,用户定义此类函数时可指定他的返回为“空类型”。空类型说明符为void。
- 举例(1ms的延时函数)
void delaytime(void) //函数返回值函数为空(void)
{unsigned char i = 125;while(i--);
}
从函数有无参数
- 无参函数
(1)无参函数在函数定义、函数说明及函数调用中均不带参数;
(2)主调函数和被调函数之间不进行参数传送;
(3)此函数通常用于完成一组指定的功能,可以返回或不返回函数值。
- 有参函数
(1)有参函数也称带参函数,在函数定义及函数说明时都有参数,称为形式参数(简称形参);
(2)在函数调用时也必须给出参数,称为实际参数(简称实参);
(3)进行函数调用时,主调函数将把实参的值传给形参,供被调函数使用。
- 举例(通过主函数调用延时函数)
void delaytime(unsigned char time); //声明延时函数,并定义其为带入口参数的函数,无返回值
void main(void)
{delaytime(100); //调用延时函数产生100ms的延时//其他程序
}
//延时函数的实际内容为下
void delaytime(unsigned char time) //函数返回值函数为空(void)
{unsigned char i; //定义循环条件控制变量while(time--){for(i=0;i<=125;i++){;} //延时1ms}
}
函数定义的一般形式
不同的函数形式具有不同的定义方式,下面从有无入口参数分类讲解函数的定义形式。
C51无参函数的一般形式
- 无参函数的一般形式如下:
类型说明符 函数名()
{类型说明语句
}
-
说明
- 说明符和函数名称为函数头。其中类型说明符说明了本函数的返回值类型;
- 函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少;
- {}中的内容成为函数体;
- 在函数体中也有类型说明,这是对函数体内部所用到的变量的类型说明;
- 在很多情况下都不要求无参函数有返回值,此时函数类型符可写为void(即无返回值函数)。
-
举例
void PrintfHello(void) //定义为void,无返回值
{printf("Hello ,world\n");
}
C51有参函数的一般形式
- 有参函数的一般形式:
类型说明符 函数名(形式参数表)
形式参数类型说明符
{类型说明语句
}
-
说明
- 有参函数比无参函数多了两项内容:形式参数和形式参数类型说明符;
- 在形式参数表中给出的参数称为形式参数,它可以是各种类型的变量,各参数之间用逗号间隔;
- 在进行函数调用时,主调函数将把实际的值赋予这些形式参数;
- 形式参数既然是变量,就必须给与类型说明。
-
举例(定义一个函数,用于求两个数中的大数)不常用
int max(a,b)
int a,b;
{if(a>b){return a;}else{return b;}
}
- 说明示例
(1)第一行说明max函数是一个整形函数,其返回的函数值是一个整数,形式参数为a,b;
(2)第二行说明a,b均为整数值;
(3)a,b的具体值是由主调函数在调用时传送过的;
(4)在{}中的函数体内,除形式参数外没有使用其他变量,因此只有语句而没有变量类型说明;
(5)上述这种方式称为“传统方式”。这种方式不利于编译器用时发生的数据传输是单向的检查,从而会引起细微而难以跟踪的错误;
(6)ANSI C的新标准把对形式参数的类型说明合并到形式参数表中,称为“现代模式”。
- 举例(上面max函数用现代格式可定义为)常用
int max(int a,int b)
{if(a>b){return a;}else{return b;}
}
- 注意
(1)在C语言程序中,一个函数的定义可以放在任意位置,即可放在主调函数之前,也可放在主调函数之后;
(2)如果放在被调函数之后,需要在程序开头声明次被调函数。
- 举例1(被调函数放在主调函数之前,可省略此被调函数的声明)
//被调函数放在主调函数之前
int max(int a,int b)
{if(a>b){return a;}else{return b;}
}
void main(void)
{int x,y,z;printf("input two numbers:\n");scanf("%d%d",&x,&y);z = max(x,y);printf("maxnum = %d",z);
}
- 举例2(被调函数放在主调函数之后,需要在程序开头声明此被调函数)
int max(int a,int b); //声明该被调函数
void main(void)
{int x,y,z;printf("input two numbers:\n");scanf("%d%d",&x,&y);z = max(x,y);printf("maxnum = %d",z);
}
//被调函数放在主调函数之后
int max(int a,int b)
{if(a>b){return a;}else{return b;}
}
函数的形式参数和实际参数
形式参数和实际参数都是函数的入口参数。
形式参数
(1)形式参数是在函数定义时,将定义一个或多个变量作为函数的入口参数;
(2)形式参数时概念上的定义,并不传递实际的值,缺为实际参数传入提供接口。
实际参数
实际参数是函数在被其他函数调用时,从其他传入的实际变量值。
函数的返回值
函数的返回值是函数处理后的结果,在调用函数的最后,通过return语句将函数的返回值返回给主调函数。
一般形式为:
return(表达式);
//或
return 表达式;
//或
return;
函数的形参和实参的特点
函数的形参和实参的特点:
(1)形式参数只有在被调用时才分配内存单元,在调用结束时,即可释放所分配的内存单元;
(1.1)形参只有在函数内部有效。函数调用结束并返回主调函数后,则不能再使用该形参变量。
(2)实参可以是常数、变量、表达式、函数等,无论实参时何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传给形参;
(2.1)实参应预先赋值、输入等办法使实参获得确定值。
(3)实参和形参在数量、类型、顺序上应严格一致,否则会发生“类型不匹配”的错误;
(4)函数调用中的数据传递是单向的,即只能把实参的值传给形参,而不能把形参的值反向传给实参;
(4.1)因此在函数调用过程中,形参的值发生改变,而实参中的值不会改变。
函数的调用
函数的调用一般形式有以下几种:
- 函数语句
//直接调用函数,实现某种特定功能:
delaytime();
Function();
printf("Hello,World!\n");
- 函数表达式
//通过函数表达式将函数的返回值赋给相应的变量
m = max(a,b) * 2;
keyvalue = KeyPad(key);
- 函数参数
//函数的实参直接使用被调函数的返回值作为实参:
printf("%d".max(a,b));
m = max(a.max(b,c));
Function(KeyPad(key));
调用函数需要注意的事项
- 先声明,后调用
(1)自定义功能子函数位于主调函数前面时,可以直接调用被调函数,无需声明被调函数;
(2)自定义功能子函数位于主调函数后面,需要声明语句声明子函数。
- 示例
void delay(void); /*声明子函数*/
void light1(void); /*声明子函数*/
void light2(void); /*声明子函数*/
- 函数的连接
当函数中子函数与主函数不在同一个函数文件时,需要连接的方法实现有效的调用。
- 外部声明,程序如下
extern void delay(void); /*声明该函数在其他文件中*/
extern void light1(void); /*声明该函数在其他文件中*/
extern void light2(void); /*声明该函数在其他文件中*/
- 文件包括,程序如下
#include <REG51.H>
#include "user.c"
/*包含文件user,一般情况下,user与主函数程序文件在同一文件夹下,即当前文件夹,与包含头文件也有所不同,是用双引号给出包含文件的文件名*/
函数的嵌套调用
在C51语句中,函数不可以嵌套定义,但可以嵌套调用。
- 示例(求3个数中最大值和最小值的差值)
int dif(int x,int y,int z);
int max(int x,int y,int z);
int min(int x.int y,int z);
/******************主函数**********************/
int main(void)
{int a,b,c,d;scanf("%d%d%d",&a,&b,&c);d = dif(a,b,c); //调用求差值函数printf("Max-Min = %d\n",d) //输出结果
}
/*****************求差值函数*******************/
int dif(int x,int y,int z)
{return max(x,y,z)-min(x,y,z);}
/*******************求最大数函数*****************/
int max(int x,int y,int z)
{int r;r = x>y?x:y;return (r>z?r:z)
}
/*********************求最小数函数*********************/
int min(int x,int y,int z)
{int r;r = x<y?x:y;return (r<z?r:z);
}
数组作为函数参数
用数组元素作实参
(1)用数组元素作实参时,只需要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量;
(2)换句话说,对数组元素的处理是按普通变量对待的;
(3)在普通变量下或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元;
(4)在函数调用时发生的值传送是把实参变量的值赋予形参变量。
- 示例(某单片机系统程序中数码管的显示程序)
#include “reg51.h”
void delay(int time); //声明延时函数
sfr LED = 0XA0; //定义LED为P2口地址
unsigned char code LED_SUM[10] = {0XFC,0X61,0XDA,0XF2,0X66,0XB6,0XBE,0XE0,0XFE,0XF6}//0~9的显示编码存储于LED_SUM中
void Disp(unsigned char LedValue)
{LED = LedValue; //根据i的值将LED_SUM数组中的编码放到P2口显示
}
void delay(int time) //定义延时函数
{unsigned char i; //定义循环条件控制变量while(time--){for(i=0;i<=125;i++){;} //延时1ms}
}
void main(void)
{int i;for(i=0;i<=9;i++){Disp(LED_SUM[i]); //调用Disp函数delay(1000); //延时1ms}
}
用数组名作实参
(1)用数组名作函数参数时,要求形参与相对应的实参都必须是类型相同的数组,都必须有明确的数组说明;
(2)数组名是是数组的首地址;
(3)形参数组和实参数组的长度可以不相同,但操作时需确保不越界,否则会导致内存错误。因为在调用时,只传首地址而不检查形参数组的长度。当形参和实参数组长度不一致时,虽不至于出现语法错误(编译可以通过),但程序执行结果将与实际不符,应予以注意。
局部变量和全局变量
局部变量
(1)局部变量是在函数内做定义说明的。
(2)其作用域:仅限函数内部,离开函数在使用这种变量就是非法的。
- 举例(函数的局部变量定义)
/************a,b,c的作用域仅限于f1函数中*****************/
int f1(int a)
{int b,c;.....
}
/************x,y,z的作用域仅限于f2函数中*****************/
int f2(int x)
{int y,z;.....
}
/************m,n的作用域仅限于main函数中*****************/
void main(void)
{int m,n;
}
局部变量的作用域
注意:
(1)主函数和其他函数是平行关系。即主函数中定义的变量只能在主函数使用,主函数不能使用其他函数定义的变量;
(2)形参变量属于被调函数的局部变量,实参变量属于主调函数的局部变量;
(3)允许在不同函数中使用相同的变量名,他们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆;
(4)在复合语句中也可定义变量,其作用域只在复合语句范围内。
- 举例(复合语句的局部定义)
void main(void)
{int s,a;{int b;a = a+b;//b的作用域}//s,a的作用域
}
- 举例(主函数与复合语句内变量的作用范围)
void main(void)
{int i=3,j=2,k;k = i+j;{int k = 8;if(i == 3)printf("%d\n",k);}printf("%d\n%d\n",i,k);
}输出结果:
8
3
5
全局变量
(1)在函数外部定义的变量;
(2)它不属于哪个函数,它属于一个源程序文件;
(3)作用域:整个源文件;
(4)全局变量的说明符:extern。
- 举例(外部变量定义的方法)
int a,b; /*外部变量*/
void f1() /*函数f1*/
{....
}
float x,y; /*外部变量*/
int fz() /*函数fz*/
{....
}
void main(void) /*主函数*/
{....
} /*全局变量x,y作用域 全局变量a,b作用域*/
使用全局变量的注意事项
- (1)
extern
用于声明外部变量,而非定义
其一般形式为:
[extern] 类型说明符 变量名,变量名... //其中方括号里的extern可以省去不写
- 举例(外部变量的定义方法)
int a,b;
- 等效于
extern int a,b;
-
(2)尽量不要使用全局变量,函数的独立性降低(相反,但加强了函数模块间的数据联系);
-
(3)在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。
-
举例(局部变量和全局变量重名)
int vs(int length,int width)
{extern int height;int v;v = length * width * height;return v;
}
void main(void)
{extern int width,height;int length = 5;printf("v = %d",vs(length,width));
}
int length = 3,width = 4,height = 5; //定义外部变量输出结果:
100
变量的存储类型
变量的存储类型是指变量占用空间的方式,也称存储方式。
静态局部变量
(1)静态函数变量在函数内定义,它的生存周期为整个源程序,而不像自动变量那样,当调用时就存在,退出函数时就消失;
(2)作用域:只能在定义该变量的函数内使用。退出该函数后,尽管该变量还继续存在(它仍占用一定的空间),但不能使用它;
(3)允许对构造类静态局部变量赋初值;
(4)基本类型的静态局部变量,若在说明时未赋以初值,则系统自动赋值0值。若自动变量不赋初值,则其值是不确定的;
(5)静态局部变量适用于多次调用一个函数时且要求在调用过程中保留某些变量的值时。
- 举例(自动变量使用示例)
void main(void)
{int i;void fun(); /*函数声明*/for(i=1;i<=5;i++){fun(); /*函数调用*/}while(1);
}
void fun() /*函数定义*/
{auto int j = 0;++j;printf("%d\n",j);
}
输出结果:
1
1
1
1
1
- 举例(静态局部变量使用)
void main(void)
{int i;void fun();for(i=1;i<=5;i++)fun();while(1);
}
void fun(void)
{static int j = 0;++j;printf("%d\n",j);
}输出结果:
1
2
3
4
5
静态全局变量
register变量
介绍:
(1)前面各类变量都存放在存储器中,因此当对一个变量频繁读写时,必须反复访问存储器,从而花费大量的存取时间;
(2)为此,C51语言提供了另一种变量,即寄存器变量,这种变量存放在CPU的寄存器中,不需要访问内存,而直接从寄存器中读写,这样可以提高效率;
(3)寄存器变量的说明符时register。形式举例:
register a,b = 0; //说明变量a,b为寄存器类型
外部变量
- 外部变量的类型说明符为extern。
特点(补充前面):
(1)外部变量和全局变量是对同一类变量的两种角度的提法。全集变量是根据他的作用域提出的,外部变量是根据它的存储方式提出的,表示了它的生存期;
(2)当一个源程序由若干源文件组成时,在一个源文件中定义的外部变量在其他文件中也有效。
- 举例(外部变量使用)
如有一个源程序由源文件Fun1.c和Fun2.c组成。/*************Fun1.c的内容如下:****************/
int a,b; /*外部变量定义*/
char c;
void main(void) /*外部变量定义*/
{....
}/*************Fun2.c的内容如下:****************/
extern int a,b; /*外部变量声明*/
extern char c; /*外部变量声明*/
void func(int x,y)
{....
}解释说明:
(1)Fun1.c和Fun2.c这两个文件都是用使用了a,b,c三个变量;
(2)在Fun1.c文件中把a、b、c都定义为外部变量;
(3)在Fun2.c文件中用extern把3个变量说明为外部变量,表示这些变量已经在其他文件中定义,编译系统不再为它们分配内存空间。
中断函数定义和应用
- 中断函数是C51语言定义的用于响应51单片机中断的函数,中断函数通过使用interrupt关键字和中断号n(n = 0~31)来实现。
中断函数的定义
- 定义方式
void 函数名() interrupt n [using m]特点:
(1)中断号n和中断向量取决于单片机的型号,编译器从8n+3处产生中断向量;
(2)using m是一个可选项,用于指定中断函数所使用的寄存器组;
(3)m取值为 0~3,对应 8051 的 4 组工作寄存器(0 为默认组)。
使用中断函数应注意的事项
(1)中断函数不能进行参数传递;
(2)中断函数没有返回值;
(3)任何情况下都不能调用中断函数,否则会产生编译错误;
(4)如果中断函数中用到浮点运算,必须保存浮点寄存器的状态,当没有其他程序执行浮点运算时可以不保存;
(5)由于中断产生的随机性,中断函数对其他函数可能形成违规调用,需要时可能被中断函数所调用的其他函数定义为可重入函数。
- 举例
如有一个源程序由源文件Fun1.c和Fun2.c组成。/*************Fun1.c的内容如下:****************/
int a,b; /*外部变量定义*/
char c;
void main(void) /*外部变量定义*/
{....
}/*************Fun2.c的内容如下:****************/
extern int a,b; /*外部变量声明*/
extern char c; /*外部变量声明*/
void func(int x,y)
{....
}解释说明:
(1)Fun1.c和Fun2.c这两个文件都是用使用了a,b,c三个变量;
(2)在Fun1.c文件中把a、b、c都定义为外部变量;
(3)在Fun2.c文件中用extern把3个变量说明为外部变量,表示这些变量已经在其他文件中定义,编译系统不再为它们分配内存空间。
中断函数定义和应用
- 中断函数是C51语言定义的用于响应51单片机中断的函数,中断函数通过使用interrupt关键字和中断号n(n = 0~31)来实现。
中断函数的定义
- 定义方式
void 函数名() interrupt n [using m]特点:
(1)中断号n和中断向量取决于单片机的型号,编译器从8n+3处产生中断向量;
(2)using m是一个可选项,用于指定中断函数所使用的寄存器组;
(3)m取值为 0~3,对应 8051 的 4 组工作寄存器(0 为默认组)。
使用中断函数应注意的事项
(1)中断函数不能进行参数传递;
(2)中断函数没有返回值;
(3)任何情况下都不能调用中断函数,否则会产生编译错误;
(4)如果中断函数中用到浮点运算,必须保存浮点寄存器的状态,当没有其他程序执行浮点运算时可以不保存;
(5)由于中断产生的随机性,中断函数对其他函数可能形成违规调用,需要时可能被中断函数所调用的其他函数定义为可重入函数。