4- 利用函数实现模块化程序设计_第1页
4- 利用函数实现模块化程序设计_第2页
4- 利用函数实现模块化程序设计_第3页
4- 利用函数实现模块化程序设计_第4页
4- 利用函数实现模块化程序设计_第5页
已阅读5页,还剩31页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

教学内容: 第1章:C语言语法基础第2章:C程序设计基础第3章:利用数组处理同类型的批量数据第4章:利用函数实现模块化程序设计第5章:灵活使用指针第6章:结构体、共用体、枚举类型及其应用第7章:常用的三种线性数据结构第8章:利用文件进行数据管理第9章:嵌入式技术基础实践方法C语言程序设计【学习内容】4.1函数概述

4.2定义函数的方法

4.3函数的调用

4.4数组作为函数参数

4.5变量的类型

4.6内部函数和外部函数4.7嵌入式中断服务函数第4章利用函数实现模块化程序设计【学习目标】1.掌握定义函数的方法;

2.理解函数参数传递及函数返回值的概念;

3.掌握函数的3种调用方法;

4.掌握普通变量和数组作为函数参数的方法;

5.理解并区别变量的类型;

6.掌握内部函数和外部函数的概念;

7.熟悉中断和嵌入式中断服务函数的概念。4.1函数概述

一个复杂的C程序由若干个程序模块组成,每个程序模块作为一个源程序文件(.c文件、.h文件等),而每个源程序文件由预处理命令、数据声明以及若干函数组成。

一个C程序必须有且只能有一个主函数,C程序总是从主函数开始执行(不论位置如何),最后在主函数中结束整个程序的运行。需要注意:在主函数中可以调用其他函数,而不允许被其他函数调用。

在实际应用的C程序中,可将主函数设计得简单些,主要负责调用各个功能函数,依次实现各项功能——模块化程序设计。4.1函数概述

从不同的角度对函数进行分类。1.从定义函数的角度

(1)库函数:由编译系统提供,只要在源文件中包含其对应的头文件,即可在程序中直接调用【见附录D】。例如,“stdio.h”头文件中的printf、scanf等函数,“math.h”头文件中的sin、cos等函数。

(2)用户自定义函数:用户根据需要,将实现某个功能的代码编写成相对独立的函数。2.从有无返回值的角度

(1)有返回值函数:向调用者返回一个值。定义函数时须指明函数的类型(函数返回值的类型)。

(2)无返回值函数:不向调用者返回函数值。定义函数时应指定函数的类型为void。3.从数据传递的角度

(1)无参函数:调用时,主调函数和被调函数之间不进行参数传送。此类函数可以返回或不返回函数值。

(2)有参函数:调用时,主调函数将实参的值传递给形参(虚参)。4.2定义函数的方法4.2.1定义无参函数定义形式:类型标识符函数名(void){

声明部分 执行部分}函数首部函数体函数的类型(函数返回值的类型)表示函数没有参数,也可省略不写例如:intfun(void){ inti,j;

intsum=0; i=2;j=3; sum=i+j; retrun(sum);}

声明执行说明:(1)书写函数体时,一般先写声明部分,后写执行部分。若将前3行写成:inti,j;//声明语句i=2;j=3;//执行语句intsum=0;//声明语句系统编译不通过!(2)若函数无需返回值,则函数类型应定义为void类型。例如:

voidHello(){ printf("Helloworld\n");}函数返回值(变量sum)的类型4.2定义函数的方法4.2.2定义有参函数定义形式:

类型标识符函数名(形参列表){

声明部分 执行部分}

形参可以是各种类型的变量。若有多个形参,则各形参之间要用逗号分隔。在函数调用时,主调函数将实参的值传递给形参。注意:必须在形参列表中给出形参的类型标识符。例如:

intmax(intx,inty)

{

intz;

if(x>y)z=x;

return(z);

}

有返回值的函数中至少应有一个return语句。形参:整型变量x、y,其具体值由主调函数调用时传递过来将z的值作为函数值返回给主调函数函数返回值(变量z)的类型4.3函数的调用4.3.1函数的一般调用

函数的一般调用流程:

函数被定义之后,即可被其他函数调用。本节将介绍函数的一般调用、嵌套调用和递归调用3种调用方式。1、函数的一般调用形式

(2)有参函数的调用形式:函数名(实参列表)

(1)无参函数的调用形式:函数名()

例如,4.2.1节中的Hello函数调用语句可写为:Hello();

调用有参函数时,主调函数将“实参”的值传递给被调函数的“形参”,从而实现信息传递。如果有多个实参,则各参数之间要用逗号隔开。实参与形参的个数应相等,类型应匹配,按顺序对应,一一传递信息。4.3函数的调用4.3.1函数的一般调用1、函数的一般调用形式

【例4.1】有参函数的一般调用:求两个数的最大值。c=max(a,b);

(main函数)intmax(int

x,int

y)(max函数){

intz; if(x>y)z=x; elsez=y;

return(z);

}#include<stdio.h>intmax(intx,inty);

//对max函数进行声明intmain(void){ inta,b,c;printf("请输入2个整数:");scanf("%d%d",&a,&b);c=max(a,b);//调用max函数printf("a=%d,b=%d,max=%d\n",a,b,c);}intmax(intx,inty)

//定义有参函数{

intz;if(x>y)z=x;elsez=y;

return(z);//向主调函数返回z的值

}4.3函数的调用2、关于函数调用时“参数传递”

的几点说明4.3.1函数的一般调用

(1)形参变量只有在发生函数调用时才被临时分配内存单元。在调用结束后,形参所占用的内存单元也被释放。实参与形参占用不同的存储空间。

(2)只能由实参传递给形参,而不能由形参传递给实参,即“单向信息传递”。在执行一个被调函数时,形参的值如果发生改变,并不会改变主调函数的实参值。

(3)当形参为普通变量(基本类型的变量)时,实参可以是常量、变量或表达式,但必须有确定的值。形参:x函数调用时被调函数执行时实参:a1b3y1324【例4.2】函数参数传递。#include<stdio.h>voidfun(intx,inty);

//对fun函数进行声明intmain(void){ inta=1,b=3;

fun(a,

b);

//调用fun函数printf("a=%d,b=%d\n",a,b);}voidfun(intx,inty)

//定义有参函数{ x=x+1;

y=y+1;printf("x=%d,y=%d\n",x,y);}4.3函数的调用3、函数的值(3)没有返回值的函数,函数的类型应当明确定义为void类型。(1)函数的值只能通过return语句获得。

return语句的一般形式:return表达式;

或return(表达式);

(2)函数返回值的类型和定义函数时指定的函数类型应保持一致。定义函数时若不指定函数类型,C编译系统则默认为整型。

说明:在函数中可有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。return语句也可以不含表达式,此时必须将函数定义为void类型,其作用只是使流程返回到主调函数。4.3.1函数的一般调用练习:【例4.3】输入一个非负整数,输出它的倒序数。4.3函数的调用4、对被调函数的声明

在例4.1和例4.2的主调函数(主函数)的前面,都对被调函数进行了声明。若不进行声明,编译系统对程序从上到下进行编译的过程中,遇到被调函数名时,就会认为是一个“陌生人”而报告错误。解决办法有两个。

(1)在主调函数的函数体的开始,或者在源文件中所有函数的前面,对被调函数进行声明。提前向编译系统“打招呼”,让编译系统“提前认识”被调函数。

(2)若在主调函数前面定义被调函数,则无须额外对被调函数进行声明。或:

类型标识符函数名(形参类型1,形参类型2,…);

函数声明(也称为函数原型)的一般形式:

类型标识符函数名(形参类型1形参名1,形参类型2形参名2,

…);

在函数首部的基础上加一分号4.3.1函数的一般调用4.3函数的调用4、对被调函数的声明

4.3.1函数的一般调用提倡使用说明:调用库函数时,无需对其进行声明,但要把该函数对应的头文件用#include命令包含在源文件中。4.3函数的调用4.3.2函数的嵌套调用

在C语言中,所有函数(包括主函数)都是相互平行、相互独立的。在一个函数内不能再定义另一个函数(函数不能嵌套定义)。但允许在调用一个函数的过程中,又调用另一个函数,即函数的嵌套调用。【例4.4】函数的嵌套调用:输入4个整数,输出其中的最大值。

#include<stdio.h>intmax_4(intb0,intb1,intb2,intb3);

//函数声明intmax(intx,inty);

//函数声明intmain(void){inta0,a1,a2,a3;

printf("请输入4个整数:");scanf("%d%d%d%d",&a0,&a1,&a2,&a3);

printf("最大值=%d\n",max_4(a0,a1,a2,a3));}intmax_4(intb0,intb1,intb2,intb3){

intm;

m=max(b0,b1);

m=max(m,b2);

m=max(m,b3);

return(m);}intmax(intx,inty){

return(x>y?x:y);}思考与练习:

输出其中的最小值。4.3函数的调用4.3.3函数的递归调用

在调用一个函数的过程中,又出现直接或间接地调用该函数本身,称为函数的递归调用。直接递归调用间接递归调用4.3函数的调用4.3.3函数的递归调用【例4.5】有5个人坐在一起,问第5个人多少岁,他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2个人大2岁。问第2个人,说比第1个人大2岁。最后问第1个人,他说他10岁。请问第5个人多大?显然,这是一个递归问题,分回推和递推两个过程:上述过程,可用数学公式表述:

程序中需要具有一个结束递归过程的条件。age(1)=10,就是使递归结束的条件。#include<stdio.h>intage(intn)

//求年龄的递归函数,函数参数:变量n{

intc;if(n==1)c=10;elsec=age(n-1)+2;//函数递归调用return(c);}intmain(void){ printf("第5个人的年龄:%d\n",

age(5));//调用age函数

}4.3函数的调用4.3.3函数的递归调用练习:求1!+2!+3!+…+n!的值(其中n的值由键盘输入)。【例4.6】用递归方法计算n的阶乘n!公式表示:#include<stdio.h>#include<stdlib.h>longlong

jc(intn)//求阶乘的递归函数{

longlongx;if(n<0){ printf("n<0,输入错误!\n");

exit(0);

//终止程序运行}else{ if(n==0||n==1)x=1;else

x=jc(n-1)*n;//函数递归调用return(x); }}intmain(void){intn;

longlongy;printf("请输入一个正整数:");scanf("%d",&n);y=jc(n);printf("%d!=%I64d\n",

n,

y);}用于输出长长整型数据4.4数组作为函数参数

4.4.1数组元素作为函数实参

在函数调用时,将实参(数组元素,相当于普通变量)的值传递给形参(变量),实现“单向的值传递”。

数组用作函数参数有两种形式:数组元素作为函数的实参;数组名作为函数的实参和形参。【例4.7】数组元素作为函数实参:根据学生课程成绩,判断考试结果。#include<stdio.h>voidtest(intx);

//函数声明intmain(void){inta[5]={62,57,70,48,85},i;//将课程成绩存入数组a中for(i=0;i<5;i++){printf("a[%d]=%d:",i,a[i]);test(a[i]);

//调用成绩测试函数,数组元素a[i]作为实参

}}voidtest(intx)

//成绩测试函数,函数参数:变量x{if(x>=60)printf("Pass!\n");

//通过else

printf("Fail!\n");

//不及格

}4.4数组作为函数参数

4.4.2数组名作为函数参数

数组名作为函数参数时,实参向形参传递的信息是数组的首地址,即“单向的地址传递”。

数组名作为函数的实参和形参时,应在主调函数和被调函数中分别定义实参数组和形参数组,并且类型要一致,其中形参数组在定义时可以不指定大小。【例4.8】数组名作为函数的实参和形参。

函数调用时,是将实参数组a的首地址传递给形参数组名b,使形参数组名获得了实参数组的首地址,因此形参数组与实参数组为同一数组。

显然,a[i]与b[i]共占同一存储单元,因此当形参数组各元素的值发生变化时,实参数组元素的值也随之变化。#include<stdio.h>voidchange(intb[],intn);

//函数声明intmain(void){ inta[5]={1,3,5,7,9},i;printf("函数调用前:");for(i=0;i<5;i++)printf("a[%d]=%d",i,a[i]);printf("\n");

change(a,

5);//调用change函数,实参:数组名a、数值5printf("函数调用后:");for(i=0;i<5;i++)printf("a[%d]=%d",i,a[i]);printf("\n");}voidchange(intb[],intn)//形参:数组名b、变量n{ inti;for(i=0;i<n;i++)

b[i]++;}

可见,在函数调用之后,实参数组a元素的值发生了变化。其奥秘何在?4.4数组作为函数参数

4.4.2数组名作为函数参数

数组名作函数参数,在函数执行过程中,当形参数组各元素的值发生变化时,实参数组元素的值也随之变化。在程序设计中,可以利用这一特点改变实参数组元素的值。【例4.10】数组名作为函数的参数,编写冒泡法排序程序(由小到大)。voidMPSort(intb[],intn)//冒泡排序函数,形参:数组名b、变量n{ inti,j,t,swap_flag;for(i=1;i<n;i++)

//n个数,共需比较n-1轮{swap_flag=0;

//交换标志:0-无交换,1-有交换for(j=0;j<n-i;j++)//第i轮需要比较n-i次{if(b[j]>b[j+1])

//依次比较两个相邻的数,将大数放后面{ t=b[j];b[j]=b[j+1];b[j+1]=t;swap_flag=1;//交换

}}if(swap_flag==0)break;//若本轮无交换,则结束比较}}#defineN5

//宏定义参与排序的数据个数voidMPSort(intb[],intn);

//函数声明intmain(void){ inta[N],i;

//数组a存放待排序的N个数

...//依次输入N个数据给数组a...//依次输出数组a的各元素值(排序前)

MPSort(a,N);//调用排序函数,实参:数组名a、数值N...//依次输出数组a的各元素值(排序后)}4.4数组作为函数参数

4.4.2数组名作为函数参数练习:以数组名作为函数参数:

(1)编写选择法排序函数。

(2)例4.9:输入若干个整数,输出其中的最大值和最小值。(3)例4.11:编写字符串长度测试函数和字符串连接函数。

(4)例4.12:输出3×4矩阵中值最大的那个元素值及其所在的行号和列号。(4)输入两个矩阵的数值,输出这两个矩阵的乘积。4.5变量的类型

4.5.1局部变量和全局变量

从变量值存在的时间(生存期)角度,变量有静态存储和动态存储两种存储方式。从变量的作用域(作用范围)角度,变量可分为局部变量和全局变量。1、局部变量

在函数或复合语句的内部定义的变量是内部变量,也称为“局部变量”,只在本函数或复合语句范围内有效。函数的形参是局部变量。

主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用,并且主函数也不能使用其他函数中定义的变量。

允许在不同的函数中使用相同的局部变量名,它们代表不同的对象,分配不同的内存单元,互不干扰,也不会发生混淆。4.5变量的类型

2、全局变量

在函数外部定义的变量是外部变量,也称“全局变量”。全局变量的有效范围是从定义变量的位置开始到本源文件结束。4.5.1局部变量和全局变量4.5变量的类型

(1)打通函数之间数据联系的通道,使多个函数共享全局变量的值;并且通过函数调用可以得到一个以上的值。2、全局变量4.5.1局部变量和全局变量【例4.13】输入正方体的棱长,输出其表面积和体积的大小。#include<stdio.h>floatS,V;

//定义全局变量S和V,分别存放表面积和体积voidsv(floatx)//求正方体的表面积和体积函数{

S=6*x*x;

//计算表面积

V=x*x*x;

//计算体积

}intmain(void){ floata;

//定义变量a,存放正方体的棱长printf("请输入正方体的棱长:");scanf("%f",

&a);

sv(a);

//调用求表面积和体积函数printf("棱长=%6.2f,表面积=%6.2f,体积=%6.2f\n",

a,

S,

V);}4.5变量的类型

(2)在同一个源文件中,若全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被“屏蔽”失效。2、全局变量4.5.1局部变量和全局变量【例4.14】外部变量与局部变量同名。

说明:尽管使用全局变量有时会带来一些便利,但建议不是非常必要的情况下,尽量不要使用全局变量。为了实现程序的模块化设计(函数化),提倡通过“实参-形参”的方式实现函数之间的信息传递,例如数组名作为函数参数。#include<stdio.h>inta=1,b=2;

//a、b为全局变量intadd(inta,intb)//a、b为局部变量{ intc;c=a+b;return(c);}intmain(void){

inta=3;

//a为局部变量printf("%d\n",

add(a,

b));}4.5变量的类型

(3)若定义全局变量时不赋初值,系统会自动赋初值数值0或空字符'\0'。2、全局变量4.5.1局部变量和全局变量【例4.15】考察全局变量和局部变量的系统默认初值。若定义局部变量时不赋初值,则系统会为其赋一个随机值。#include<stdio.h>inta;

//定义全局变量charb;

//定义全局变量intmain(void){

inti;

//定义局部变量

charj;

//定义局部变量printf("a=%d,b=%c,i=%d,j=%c\n",a,

b,

i,

j);}4.5变量的类型

4.5.2变量的存储方式

从变量值存在的时间(生存期)角度,变量有静态存储和动态存储两种存储方式(存储类别)。变量有两个属性:存储类型和数据类型。定义变量的完整格式:

存储类别数据类型变量名;

动态存储:在程序运行期间根据需要(如调用函数时)临时分配存储空间。

静态存储:在程序运行期间分配固定的存储空间,即变量在程序整个运行时间内都存在。

全局变量使用静态存储,而局部变量使用静态存储或动态存储。

有4个存储类别标识符:自动的(auto)、静态的(static)、寄存器的(register)和外部的(extern)。

4.5变量的类型

1、局部变量的存储类别声明4.5.2变量的存储方式(1)用auto声明动态局部变量

程序中大多数局部变量以及函数的形参变量都是自动局部变量,关键字“auto”通常省略不写。例如autointa,b;通常简写成:inta,b;

在调用该函数时,系统临时为局部变量x、a、b分配存储空间,在函数调用结束时系统自动释放这些存储空间。此类局部变量称为自动局部变量,也称为动态局部变量。intf(intx)

//定义f函数,x为形参变量{

autointa,

b;//定义a、b为自动局部变量...}4.5变量的类型

4.5.2变量的存储方式(2)用static声明静态局部变量1、局部变量的存储类别声明

有时希望函数中的局部变量在函数调用结束后,其占用的存储单元不被释放,其值不消失而继续被保留。需要用static将该局部变量声明为静态存储类型。【例4.16】考察动态局部变量和静态局部变量的值。

变量a、b在三次函数调用时的初值和函数调用结束时的值的变化情况:第几次调用

函数调用时的初值函数调用结束时的值abab第1次22第2次23第3次24123111练习:

【例4.17】考察静态局部变量和静态局部数组的系统默认初值。#include<stdio.h>voidlv();

//函数声明intmain(void){inti;for(i=1;i<=3;i++){ printf("第%d次调用lv函数后:",i);

lv();

//调用lv函数

}}voidlv()

//局部变量函数{

autointa=1;//定义动态局部变量astaticintb=1;//定义静态局部变量b

a++; b++;printf("a=%db=%d\n",

a,

b);}4.5变量的类型

4.5.2变量的存储方式(2)用static声明静态局部变量1、局部变量的存储类别声明

说明:虽然静态局部变量在函数调用结束后其值仍被保留,但仅限本函数(或复合语句)使用,而其他函数不能引用它。4.5变量的类型

静态局部变量的常用场合:需要保留上次函数调用结束时的值【例4.18】利用静态局部变量实现:输出1到5的阶乘。

计算(n+1)!,要用n!

的结果。因此计算n!后,要保留其结果,供计算(n+1)!时使用。问题分析:

1!=1 2!=2×1!3!=3×2!4!=4×3!5!=5×4!(n+1)!=(n+1)×n!4.5.2变量的存储方式(2)用static声明静态局部变量1、局部变量的存储类别声明#include<stdio.h>intjc(intn);

//函数声明intmain(void){ inti;for(i=1;i<=5;i++)printf("%d!=%d\n",i,jc(i));}intjc(intn)//计算阶乘函数{

staticintf=1;

//定义静态局部变量f,存放阶乘结果f=f*n;return(f);}4.5变量的类型

静态局部变量的常用场合:需要保留上次函数调用结束时的值【例4.19】嵌入式应用:定时时间到(如每隔100ms),CPU执行一次fun函数。在fun函数中,利用静态局部变量进行定时计数,计数达到预定值时(如每隔500ms)将执行一次对应的功能程序。4.5.2变量的存储方式(2)用static声明静态局部变量1、局部变量的存储类别声明4.5变量的类型

(3)用register声明寄存器变量1、局部变量的存储类别声明4.5.2变量的存储方式【例4.20】使用寄存器变量,输出1+2+3+…+1000的值。

说明:

1)只有动态局部变量才可定义为寄存器变量,而全局变量和静态局部变量都不能定义为寄存器变量。2)寄存器变量只能用于整型变量和字符型变量。3)很多编译系统会自动将读写频繁的内存变量优化为CPU寄存器变量(不需要程序设计者指定),以提高程序执行效率。若不希望被优化为寄存器变量时,需在

定义内存变量时使用关键字“volatile”进行限定,如:volatileinti;

当一个内存变量被频繁读写时,需要反复访问内存,花费大量的存取时间。为此,可用register将变量声明为“寄存器变量”,直接从CPU的寄存器中对变量进行读写,以提高程序执行效率。#include<stdio.h>intmain(void){

registerlongi,s=0;//定义寄存器变量i、sumfor(i=1;i<=100

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论