第4章 函数与C程序结构_第1页
第4章 函数与C程序结构_第2页
第4章 函数与C程序结构_第3页
第4章 函数与C程序结构_第4页
第4章 函数与C程序结构_第5页
已阅读5页,还剩83页未读 继续免费阅读

下载本文档

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

文档简介

程序设计技术,C语言数据描述和C程序设计初步结构化程序设计基础和C语言的控制结构数组及其应用函数与C程序结构指针与函数指针与数组字符串及其应用结构体类型和联合体类型C语言的文件处理及其应用位运算与枚举类型,函数与程序结构,函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法,函数与程序结构,模块化程序设计技术就是通过开发和维护一些小的程序块(即模块)的方法构建一个大型程序,是人类解决较大的复杂问题所采用的一种“分而治之”的策略。本章主要讨论C语言实现模块化程序设计技术的手段以及在模块化实现过程中所遇到的一系列问题。,4.1函数的定义和调用,C程序的一般结构,4.1.1函数的定义和声明,函数定义就是编写完成某种具有一定功能的程序模块。1)现代风格的函数定义形式:返回类型符函数名(形式参数表及其说明)/函数头变量定义和说明及函数执行语句/函数体函数体:由变量定义与函数执行语句组成。二者全无则是空函数,先占位置,以后补上。,/*现代风格*/longfac(intn)/函数头longy=1;inti;for(i=1;i=n;i+)y=y*i;return(y);,4.1.1函数的定义和声明,函数体,long为函数返回值类型,return语句将n!返回调用函数,调用函数处获取n的值,函数的定义内容如下:函数返回值类型可是任何有效类型,void表示函数无返回值。函数名用户自定义标识符,不能重名。形式参数(简称形参)的数目、类型为函数接受外来数据提供变量名、类型和数目。return语句函数执行结果(按函数定义的返回类型)返回给主调函数。如果函数定义时返回类型为void,可缺省return语句。,4.1.1函数的定义和声明,注意:C语言中的每一个函数都是一个独立的代码块。一个函数的代码块是隐藏于函数内部的,不能被任何其它函数中的任何语句(除调用它的语句之外)所访问。它既不能影响程序其它部分,也不受其它部分的影响。在一个函数的内部不能定义其他函数(即函数不能嵌套定义)。这个规定保证了每个函数都是一个相对独立的程序模块。由多个函数组成的C程序中,各个函数的定义顺序是任意的,它不影响C程序运行时函数的执行顺序,4.1.1函数的定义和声明,2)函数的声明在主调函数中,要对本函数将要调用的函数事先进行必要的声明。所谓“声明”是指向编译系统提供必要的信息:函数名,函数的返回值类型,函数参数的个数、类型及排列次序,以便编译系统对函数的调用进行检查。例如,检查形参与实参类型是否一致,函数返回值的类型是否正确。C语言的函数分为标准库函数和用户自定义函数两大类。下面分别介绍他们的声明方法。,4.1.1函数的定义和声明,标准库函数的声明使用标准库函数时,由于系统提供的标准库函数的说明都分门别类集中在一些称为“头文件”的文本文件中,所以在程序中如果要调用系统标准库函数,也要在程序的适当位置使用编译预处理语句来进行声明。例如:#include或#inlcude“stdio.h”作用:将调用有关库函数的必要信息包含到本源文件中来。,4.1.1函数的定义和声明,用户自定义函数的声明对于用户自定义函数,如果被调用函数(简称被调函数)与调用它的函数(简称主调函数)在同一源文件中,在函数调用之前,需要对被调函数进行声明。被调函数声明的一般形式:返回类型符函数名(形式参数表及其说明);,4.1.1函数的定义和声明,#includevoidmain()longfactorial(intn);intnum;printf(Inputthenum:);scanf(%d,函数声明告诉编译系统factorial是返回类型long,只有一个int参数的函数,程序演示,4.1.1函数的定义和声明,在函数声明中,形参变量名字无关紧要(可与函数定义不同或缺省)factorial的声明语句可写成如下两种形式:longfactorial(int);/*缺省形参名*/longfactorial(intx);/*形参名与定义不同*/注意:在下列情况下可以不对被调函数进行声明:被调函数的定义出现在主调函数之前,不必进行说明,其原因是编译系统此时已经知道了被调函数的返回类型。,程序演示,4.1.1函数的定义和声明,函数调用一个函数调用另外一个函数以完成某一特定的功能称之为函数调用。调用者称为主调函数被调者称为被调函数函数调用的一般形式:函数名(实参表);调用时填入的参数,称为实际参数,简称实参。实参的个数、类型和顺序,应该与被调函数的形参个数、类型和顺序一致。,4.1.2值参数传递的函数调用,三种调用函数的方式函数语句(p119):将函数调用作为一个单独的C语句,此种方式主要对应于返回值为空类型(void)的函数调用。函数表达式:函数作为一项,出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。如:c=add(a,b);函数实参:函数调用作为另外一个函数调用的实际参数出现。此时要求函数被调用后必须要返回一个确定值以作为其外层函数调用的实际参数。如:if(prime(reverse(n),4.1.2值参数传递的函数调用,C程序由若干个相对独立的函数组成。在程序运行过程中,当被调函数是有参函数时,函数调用必然伴随着参数传递。当一个函数调用另一个函数时,实参的值传递到形参变量中就实现了主调函数到被调函数间的数据传递。函数间参数传递的两种方式传值方式传地址方式,4.1.2值参数传递的函数调用,函数调用时实参的种类实参是值参数变量常数表达式实参是地址值参数指针变量变量的地址(voidswap(intx,inty);swap(a,b);/*函数调用语句*/printf(“swap调用后:a=%d,b=%dn”,a,b);voidswap(intx,inty)inttemp;temp=x,x=y,y=temp;printf(“swap调用中:x=%d,y=%d”,x,y);,函数定义:一个函数实体,函数声明语句,4.1.2值参数传递的函数调用,为被调函数的形参变量(局部变量)分配存储;实际参数拷贝到对应形式参数(拷贝完成后形参与实参无关);控制转到被调函数执行;程序控制流程从被调函数返回主调函数(系统自动撤消为被调函数建立的形参局部变量);,函数调用执行过程与参数传递,swap函数的功能是交换两个参数的值。但运行结果表示,它只交换了两个形参变量x和y的值,而没有交换main()中两个实参a与b的值。,程序运行结果:swap调用前:a=10,b=5swap调用中:x=5,y=10swap调用后:a=10,b=5,4.1.2值参数传递的函数调用,例4.3传值调用,重要结论:函数的传值调用方式是一种数据复制方式。在这种方式下,实参值通过复制的方式传递给形参变量,实参与形参各自占用内存不同的存储单元,当数据传递结束后,它们互不相干。因此,在被调函数中无论形参怎样变化,都不会影响主调函数中实参的值。函数受外界影响减小到最小限度,从而保证了函数的独立性。,4.1.2值参数传递的函数调用,4.1.3指针与地址值参数传递的函数调用,如果要在被调函数中对主调函数的实参单元进行操作,则需要将主调函数中实参的内存地址值传递给被调函数对应的形参单元。在传地址方式下,被调函数中用于接收对应地址值的形参必须是指针变量或数组。本小节主要介绍指针变量的基本用法和地址值参数传递的函数调用。,1)指针与指针变量的概念指针(一个变量的地址)指针就是地址。在程序设计中,系统根据变量定义的数据类型为其分配一定长度的内存单元。一旦分配完成,变量与内存单元的首地址就建立了一种对应关系(见图1)。指针变量(用来存放另一变量的地址)内存单元地址是用有序整型数(无符号整型数)进行编址。为了能够操作这些地址量,有必要构造一种变量来存储它们,这种变量称为“指针变量”(见图2)。,4.1.3指针与地址值参数传递的函数调用,变量和地址的对应关系(图1),房间变量名房客变量值房间号地址,shorti,j;charch;floatf;i=5;j=3;ch=H;f=3.14;,编译或函数调用时为变量分配内存单元,内存中每个字节有一个编号地址,变量是对程序中数据存储空间的抽象,指针与指针变量的概念(图2),2000,指针,指针变量,变量的内容,变量的地址,指针变量的定义,数据类型符*指针变量名1,*指针变量名2;,inti,j,*p1,*p2;floatf;float*pf;,合法标识符,表示定义指针变量不是*运算符,注意:int*p1,*p2;指针变量名是p1,p2,不是*p1,*p2。指针变量只能指向定义时所规定类型的变量。指针变量定义后,变量值不确定,使用前必须先赋值。,指针变量所指向目标变量的数据类型,2)指针变量的赋值与初始化,intx,*y=void*p=y=,指针变量赋值的一般形式,初始化方式为指针变量赋值数据类型符*指针变量名=初始地址值;指针变量赋值的一般形式:指针变量名=地址值;其中地址值表示为:p=NULL;p=0;,为指针变量p赋空值,t=*x;t=a;*x=*y;a=b;*y=t;b=t;,/*普通变量作参数*/voidswap(intx,inty)intt;t=x;x=y;y=t;,4)地址值参数传递调用,例4.3值传递,例4.5地址值传递,当函数调用传递的是地址值时,能否在被调函数中改变主调函数中实参的值取决与在被调函数中对参数的操作方式:在被调函数中若操作形参指针变量所指向的对象,则可以在被调函数中改变主调函数中的实参值。在被调函数中若操作的是形参指针变量本身,则仍然实现的是传值调用,并不能改变主调函数中的实参值。,5)指针变量与被指针指向变量的区别,/*被调函数中操作指针指向的对象*/voidswap(int*x,int*y)intt;t=*x;*x=*y;*y=t;操作指针变量指向的对象,因而可以改变主调函数中实际参数的值。,/*被调函数中操作指针变量本身*/voidswap(int*x,int*y)int*t;t=x;x=y;y=t;操作指针变量本身,因而不能改变主调函数中实际参数的值。,5)指针变量与被指针指向变量的区别,例4.6程序演示,操作指针变量指向的对象交换或改变了实参的值。,操作指针变量本身,交换了实参的地址,不改变实参值,5)指针变量与被指针指向变量的区别,4.1.4数组参数传递的函数调用,在C程序中,既可以用数组元素作为函数的参数,也可将数组整体作为函数的参数。使用数组元素作为参数传递,是实现函数间的传值调用。将数组整体作为参数传递时,用数组名或某数组元素的地址作为函数的实参,实现的是函数间的传地址值调用。,4.1.4数组参数传递的函数调用,/*ex04-07.cpp*/#include#include#include#defineN5voidmain()voidmyprint(intx);intaN,bNN,i,j;srand(time(NULL);printf(数组a.n);for(i=0;iN;i+)ai=rand()%100;myprint(ai);,printf(数组b.n);for(i=0;iN;i+)for(j=0;jN;j+)bij=rand()%100;myprint(bij);printf(n);voidmyprint(intx)printf(%4d,x);,例4.7数组元素作值传递,23,40,一维数组作函数的参数数组名或某元素地址作为函数的实参,实参数组将它的全部或部分存储区域提供给形参数组共享,即形参数组与实参数组是同一存储区域或是实参数组存储区域的一部分。当函数调用结束后,形参数组消失,主调函数的数组就保存了形参数组操作的结果。使用数组名或数组第一个元素的地址是把整个实参数组传递给被调函数。使用某个数组元素的地址作为主调函数的实参可以实现传递部分数组元素到被调函数。,4.1.4数组参数传递的函数调用,4.1.4数组参数传递函数调用,例4.8编制求和函数并通过该函数求数组的各元素和。数组名a作实参数组v为形参数组a和v共享全部存储单元。,#include#defineN10voidmain()intsum(intv,intn);inttotal;intaN=1,2,3,4,5,6,7,8,9,10;total=sum(a,N);printf(total=%ldn,total);intsum(intv,intn)inti,s=0;for(i=0;in;i+)s+=vi;returns;,例4.8数组名作地址传递,4.1.4数组参数传递函数调用,例4.9编制求和函数并通过该函数求数组自某一元素后的所有元素和,起始点元素序号从键盘输入。,/*Name:ex04-09.cpp*/#include#defineN10voidmain()intsum(intv,intn);inttotal,pos;intaN=1,2,3,4,5,6,7,8,9,10;printf(请输入求和起始元素序号:);scanf(%d,intsum(intv,intn)inti,s=0;for(i=0;in;i+)s+=vi;returns;,部分元素的地址传递,inti;for(i=1;ik;i+)power*=n;returnpower;longf1(intn,intk)longsum=0;inti;for(i=1;i1)递归结束条件:n!=1(n1递归公式,例4.15程序演示,4.2.2函数的递归调用,通过三个示例的分析,递归方式的实现也是基于C语言的条件控制结构。递归函数设计的基本框架是相对固定的。其一般形式描述如下:if递归结束条件成立Return已知结果else将问题转化为同性质的较简单子问题;以递归方式求解子问题(递归公式);,函数与程序结构,函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法,一个变量的性质可以从两方面进行分析:变量的作用域:变量能够起作用的空间范围。变量的生存期:其变量值存在的时间范围。它们的基本意义如下:一个变量在某个函数、某个源程序文件或某几个源程序文件范围内是有效的,则称其有效的范围为该变量的作用域,在此范围内可以访问或引用该变量。一个变量的值在某一时刻是存在的,则认为这一时刻属于该变量的“生存期”,或称其在此时刻“存在”。,4.3变量的作用域和生存期,为了能够有效地确定变量的作用域和生存期两项属性,C语言用存储类别对变量进行限定。变量定义的完整形式为:存储类别符变量表;其中:数据类型说明变量的取值范围及允许的操作;存储类别用于指定变量在内存中的存放方法。变量的存储类别有四种自动型(auto)寄存器型(register)静态型(static)外部参照型(extern),4.3变量的作用域和生存期,可在程序的三个位置定义变量:函数内部、函数的参数定义和函数外部。按变量的作用域分类全局变量(外部变量):指定义在所有函数外的变量,其作用范围从定义处开始到源文件结束为止。全局变量的定义形式:extern变量表;局部变量(自动变量):有三个位置可定义函数形参;函数体内部;复合语句内部。其作用域限定在定义范围内。局部变量的定义形式:auto变量表;,4.3.1变量的作用域,全局变量与局部变量的关系,4.3.1变量的作用域,例4.18全局变量的作用域,4.3.1变量的作用域,局部变量(自动变量)的作用域局部变量的建立和撤消由系统自动进行。如在某函数中定义了自动变量,只有该函数被调用时,系统才为函数范围内的局部变量分配存储单元;当函数执行完毕,自动变量被系统自动撤销。在变量定义中省略auto的均为自动变量。自动变量的值在一个函数的两次调用之间不会保留。不同函数中定义的同名局部变量之间是毫无关系的。,4.19程序演示,4.20程序演示,4.3.1变量的作用域,同名全局变量与局部变量作用域重叠问题即在某些特定的情况下,出现全局变量、函数内定义的局部变量、复合语句中定义的局部变量名字相同的现象。即在全局变量与局部变量作用域重叠的情况下,C语言规定按“定义就近原则”来引用变量。如果定义的全局变量与函数中局部变量同名,程序进入函数内,使用定义的同名局部变量。在复合语句中如果定义有与较大范围(函数局部或全局)变量同名的变量,则使用该小局部范围内定义的同名局部变量;,同名全局变量与局部变量作用域重叠问题,4.21程序演示,0,x=20,x=10,x=0,用户使用内存存储空间的情况,用户区,所有的全局(外部)变量及静态局部变量都存放在静态存储区,程序执行完毕才释放。,函数的形参局部(自动)变量,4.3.2变量的生存期,程序运行中,不同存储类别的变量,占用的存储区域不同,分配的存储时间(生存期)也不同。按变量的生存期分类局部变量(自动变量)的生存期:这类变量存储于内存的动态存储区,它在程序运行中使用到该变量的时间段存在。即程序进入该函数或复合语句时才分配存储空间,当该函数或复合语句执行完后存储空间被撤销。全局变量或静态变量(全局或局部)的生存期:这类变量存储于内存的静态存储区,它在编译时分配存储空间,在程序运行的整个期间都存在。,4.3.2变量的生存期,在C程序设计中,为了合理选择变量的存储类别,有必要对不同存储类别的变量在程序中的作用分两方面进行讨论。全局变量的存储类别对于全局变量而言,能够起作用的存储类别为extern和static。局部变量的存储类别对局部变量能够起作用的存储类别为auto和static。,1)全局变量的存储类别,extern扩展作用域static限制作用域,File1.cpp源文件扩展x,File2.cpp源文件扩展x,使用extern声明,可扩充全局变量在一个源程序的作用域,例4.22程序演示,1)全局变量的存储类别,X原作用域,X被扩充后的作用域,100,110,130,自动变量的生存期与其所在函数被调用运行的时间相同,并且自动变量的值在函数的多次调用中都不会保留。为满足在函数的多次调用中,局部变量仍能在保持原来值基础上继续使用,C语言提供了静态存储类别(static)。静态局部变量的定义形式:static数据类型符变量表;,2)局部变量的存储类别,静态局部变量具有如下特点:静态局部变量的存储空间在程序的整个运行期间是固定的。系统在编译时就为它分配存储空间,它的生存期是整个程序的运行期间。静态局部变量的初始化是在程序编译时进行的。如果在定义时没有对它进行初始化,那么系统将它自动初始化为0(整型)、或0(字符型)。静态局部变量的值在函数多次调用中具有可继承性。静态局部变量的值只能在定义它的局部范围内使用。在它的作用域范围之外,该静态局部变量虽然存在,但不能对它进行访问。,2)局部变量的存储类别,例4.23静态局部变量与自动变量的比较1/*Name:ex04-23.cpp*/2#include3voidmain()4voidf1();5f1();6f1();78voidf1()9inta=10;10staticintb=10;11a+=100;12b+=100;13printf(a=%d,b=%dn,a,b);14,例4.23程序演示,2)局部变量的存储类别,110,110,a=110,b=110,110,210,a=110,b=210,b+;,函数与程序结构,函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法,编译预处理概念编译预处理是C编译系统在对C源程序进行编译之前对它进行的一些预加工,然后再将处理的结果和源程序一起进行编译,以得到目标代码。恰当地使用C语言的编译预处理功能可以有效地提高程序开发效率。编译预处理语句以#号开头、占用一个单独的书写行、语句的结尾不需要用“;”作为结束符。编译预处理语句可以出现在C源程序的任何位置,其作用范围是从出现点开始到源程序末尾。编译预处理语句常有三种形式宏定义文件包含条件编译,4.4编译预处理,宏定义分为代参数的宏定义和不代参数的宏定义不代参数的宏定义定义形式:#define宏标识符字符串调用形式:宏标识符(直接用在表达式中)宏定义的作用:在宏定义的作用范围之内,将所有的宏标识符用指定的字符串替换。字符串:可以是字符串常量、已定义的宏、表达式或语句组成的字符串。注意:使用#define宏标识符语句可撤消宏定义。,4.4.1宏定义,4.4.1宏定义,例4.24宏定义预处理/*Name:ex04-24.cpp*/#include#definePI3.1415926#defineR2.0voidmain()doublecircum();doublearea();printf(“circum=%fn,circum();printf(area=%fn,area();,doublecircum()return2.0*PI*R;doublearea()returnPI*R*R;,例4.24程序演示,不进行宏代换的情况宏名出现在一个标识符中例:#defineloc12345intlocal;int12345al;(不会进行这种替换)宏名出现在字符串常量中例:#definePI3.14语句printf(“ThevalueofPIis:%fn”,PI);结果是:ThevalueofPIis:3.140000,4.4.1宏定义,此处不替换,4.4.1宏定义,例4.25宏调用替换问题的理解/*Name:ex04-25.cpp*/#include#defineN2#defineMN+2#defineMN2*Mvoidmain()intx=MN;printf(x=%dn,x);,错误理解:N2M4(2+2)MN8(2*4)输出结果:x=8正确理解:MN2*N+2MN2*2+2正确结果:x=6,程序演示,带参数的宏定义定义形式:#define宏标识符(形参表)表达式调用形式:宏标识符(实参表)宏调用的作用:将所有的宏标识符用指定的表达式替换并且用实际参数代替表达式中的形式参数。注意:为了避免当实际参数是表达式时引起的宏调用错误,最好将宏定义中表达式样式字符串的形式参数用圆括号括起来。,4.4.1宏定义,4.4.1宏定义,例4.26代参数宏定义使用示例/*Name:ex04-26.cpp*/#include#definePI3.14159#defineS(r)PI*r*r#defineS(r)PI*(r)*(r)voidmain()doublea,b,area1,area2;a=3.3;b=3.2;area1=S(a);area2=3.14159*(a+b)*(a+b)area2=S(a+b);错误展开:area2=3.14159*a+b*a+bprintf(area1=%fnarea2=%fn,area1,area2);,程序演示,修改后展开,带参数的宏与函数的主要区别:函数的调用需要进行控制的转移,而带参数的宏仅仅是表达式的替换。带参数的宏没有一个确定的类型。在宏中随着带入的实参类型的不同,其结果的类型随之而变。函数调用时,对实参有数据类型的限制,要求与定义类型一致;带参数的宏进行调用时没有实参数据类型的限制,实参可以是任意类型。函数调用存在从实参向形参传递数据的过程,而带参数的宏调用中不存在传递过程,因而宏调用一般比函数调用具有较高的时间效率。,4.4.1宏定义,4.4.1宏定义,例4.27宏调用

温馨提示

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

评论

0/150

提交评论