《模块7应用函数程序设计电子课件 C语言程序设计案例教程.pptx》由会员分享,可在线阅读,更多相关《模块7应用函数程序设计电子课件 C语言程序设计案例教程.pptx(132页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C语言程序设计案例教程作者:模块7 应用函数程序设计假设学生成绩表如表7-1所示。(1)编写函数fn1,对n个学生,通过键盘输入建立所有课程的成绩。(2)编写函数fn2,对n个学生,计算每位学生本学期所有课程的总分成绩。任务描述模块7 应用函数程序设计(3)编写函数fn3,对n个学生,根据总分成绩降序排列。(4)编写函数fn4,输出n个学生成绩排序。(5)编写主函数,设计友好的界面。通过键盘输入用户的选择,用户输入1时调用fn1输入n个学生本学期各门课的成绩;输入2时调用fn2统计每位学生所有课程的总分;输入3时调用fn3,按学生总分排序,并调用fn4输出成绩排序后的结果;输入4时返回。另外,
2、可以根据自己对问题需求的分析设计实用程序,如模块1中的案例,补充mainMenu(),增加系统的功能模块。任务描述模块7 应用函数程序设计(1)能够熟练掌握函数的基本概念和基本应用方法。(2)能够根据程序需要进行函数的定义和调用。(3)能够合理使用参数的设计。(4)能够明确函数调用时的数据传递。(5)能够对实际问题进行模块化程序设计。#include#include#include 任务目标源代码展示模块7 应用函数程序设计#define M 2#define N 6void fn1(float aN,char m8)int i,j;printf(nt成绩的录入:n);for(i=0;iM;i
3、+)printf(t请输入第%d位学生姓名:,i+1);源代码展示模块7 应用函数程序设计scanf(%s,mi);printf(t请输入%s%d门课成绩:,mi,N);for(j=0;jN;j+)scanf(%f,&aij);printf(t录入完成!);void fn2(float aN,float s)源代码展示模块7 应用函数程序设计int i,j;float sum;for(i=0;iM;i+)sum=0;for(j=0;jN;j+)sum+=aij;si=sum;源代码展示模块7 应用函数程序设计void fn3(float s,char m8)int i,j,k;float t;
4、char z8;for(i=0;iM-1;i+)k=i;源代码展示模块7 应用函数程序设计for(j=i+1;jsk)k=j;if(i!=k)t=si;si=sk;sk=t;strcpy(z,mi);strcpy(mi,mk);strcpy(mk,z);void fn4(char m8,float s)int i;源代码展示模块7 应用函数程序设计printf(nnttt*n);printf(ttt排名 姓名总分nn);for(i=0;iM;i+)printf(ttt%4d%10s%10.1fn,i+1,mi,si);printf(ttt*n);main()float scoreMN,sumM
5、;源代码展示模块7 应用函数程序设计int k;har nameM8;while(1)system(cls);printf(ttt-欢迎使用简易成绩管理程序-nn);printf(tttt1.成绩的录入n);printf(tttt2.成绩的统计n);源代码展示模块7 应用函数程序设计printf(tttt3.成绩排序n);printf(tttt4.退出n);printf(nttt请输入您的选择(1-4):);scanf(%d,&k);switch(k)case 1:fn1(score,name);break;case 2:fn2(score,sum);break;case 3:fn3(sum,
6、name);fn4(name,sum);break;源代码展示模块7 应用函数程序设计case 4:printf(ntttt感谢使用!);return;getch();程序运行结果如图7-1、图7-2所示。源代码展示运行结果模块7 应用函数程序设计本程序由4个函数组成。函数fn1用来输入数据,学生的成绩和姓名借助两个二维数组参数传递,函数返回值类型为void;函数fn2实现求每位学生所有课程的总分,总分借助一维数组参数程序分析模块7 应用函数程序设计传递;函数fn3实现按照总分排序,排序过程中数据交换的同时,姓名也交换;函数fn4用来输出排序后的结果。main函数中定义一个while循环语句,
7、条件总是1,但在循环体中一旦选择4就跳出循环,否则执行某种操作后,系统继续显示提示菜单,供用户选择操作。在程序设计时,除了编写能够处理用户需求的程序外,还要编写友好的界面,让使用者一目了然。程序分析模块7 应用函数程序设计前面模块介绍的所有程序都是由一个主函数main()组成的,程序的所有操作都是在主函数中完成的。事实上,一个实用的C程序总是由一个main()函数和若干其他函数组成的。函数中可以调用C语言提供的库函数,也可以调用由用户自己或他人编写的函数。库函数由系统提供,编程者直接调用;用户自定义函数需要编程者自己编制。C语言允许对函数单独进行编译,所以利用函数可以实现模块化程序设计。程序分
8、析7.1 函数的定义和返回值C语言虽然提供了丰富的库函数,但这些函数是面向所有用户的,不可能满足每个用户的各种特殊需要,因此大量的函数必须由用户自己编写。下面就介绍如何定义和调用用户自定义的函数。7.1.1 函数的定义函数定义的一般语法格式如下。函数返回值类型 函数名(类型名 形参1,类型名 形参2,)说明部分语句部分函数体需要说明如下几点。(1)函数名和形参名都是用户命名的标识符。在同一个程序中,函数名必须唯一,形参只要在同一个函数中唯一即可。(2)定义函数后,形式参数并没有具体的值,只有在被调用时才7.1.1 函数的定义分配内存单元。在调用结束后,立刻释放所有分配的内存单元。因此,形参只在
9、函数内部有效。函数可以没有形参,但括号不能省略。(3)在C程序中,一个函数的位置任意,但函数的定义必须放在函数的外部,即不能在函数内定义函数,即无论是主函数还是子函数,其内部都不能再定义另一个函数,也就是不能嵌套定义。(4)若函数的首部省略了函数返回值的类型定义,则默认函数返回值的类型为int。(5)函数返回值类型在函数名前声明,若无返回值,类型定义为void(空的)。(6)除了返回值为int类型的函数外,函数必须先定义后调用。7.1.2 函数的返回值除了空类型(void)外,所有函数的函数值都是通过返回语句(return)返回的。返回语句有以下两种形式。return 表达式;/*或retur
10、n(表达式);*/return;需要说明如下几点。(1)表达式值的类型必须与函数定义类型一致。若不一致,则以函数值的类型为准,对return语句中的表达式自动进行类型转换。(2)一个函数可以有多个return语句,一旦其中某一个被执行,则立即结束本函数的执行,返回主调函数,所以函数最多只能返回一7.1.2 函数的返回值个值。(3)当函数内没有return语句时,函数没有返回值,函数类型应当说明为void,则程序执行到函数末尾“”处,返回主调函数。【例7-1】用户自定义函数,求s=1+2+3+n。程序代码如下。int mysum(int n)int i,sum=0;for(i=1;i=n;i+)
11、sum=sum+i;return sum;7.2 函数的调用函数定义好了以后,就可以被主调函数调用了。主调函数可以是main函数,也可以是其他函数,甚至是它本身。通常main()函数调用其他函数,但不能被其他函数调用。如果不考虑函数的功能和逻辑,其他函数没有从属关系,可以相互调用。调用自定义函数的方式和调用系统函数的方式相同。7.2.1 函数调用的一般形式函数调用的语法格式如下。函数名(实参表列)实参的个数多于一个时,各实参之间用逗号隔开。实参的个数必须与所调函数中的形参相同,类型一一对应匹配。1.函数调用出现在表达式中当所调用的函数用于求某个值时,函数的调用一般作为表达式出现在允许表达式出现
12、的任何地方。【例7-2】调用函数求n和n!。程序代码如下。#include#include 7.2.1 函数调用的一般形式float myfac(int n)/*定义,形参n*/int i;float fac=1;for(i=1;i=n;i+)fac=fac*i;return fac;main()int n;7.2.1 函数调用的一般形式double x;float y;printf(input data:);scanf(%d,&n);x=sqrt(n);y=myfac(n);/*调用,实参n*/printf(%d的平方根是%1fn,n,x);printf(%d的阶乘是%.0fn,n,y);程
13、序运行结果如下。input data:87.2.1 函数调用的一般形式8的平方根是2.8284278的阶乘是403202.函数调用作为独立语句当某些函数仅进行某些操作而不返回函数值时,函数调用一般作为一条独立的语句。【例7-3】无返回值函数的应用。程序代码如下。#include void myprint()int i;7.2.1 函数调用的一般形式for(i=1;i=10;i+)printf(*);printf(n);main()int i;for(i=1;i=5;i+)myprint();程序运行结果如下。*7.2.2 函数调用时的语法要求函数调用时有以下语法要求。(1)调用函数时,函数名必
14、须与所调用函数的名称完全一致。(2)实参的个数必须与形参的个数一致。实参可以是表达式,在类型上应按位置与形参一一对应匹配。如果类型不匹配,C编译程序按赋值兼容的规则进行转换。(3)函数必须遵循“先定义,后调用”的原则,但函数类型为int或char的函数除外。【例7-4】调用函数,输出两个数中的较大者。程序代码如下。#include float max(float a,float b)7.2.2 函数调用时的语法要求return ab?a:b;main()float x,y;printf(input data:);scanf(%f%f,&x,&y);printf(max=%fn,max(x,y)
15、;程序运行结果如下。input data:13 16max=16.0000007.2.3 函数的声明在C语言中,除了主函数外,对于用户定义函数遵循“先定义,后使用”的规则。凡是未在调用前定义的函数,C编译程序都默认函数的类型为int。对于返回值为其他类型的函数,若把函数的定义放在调用之后,则应该在调用之前对函数进行声明。函数声明的一般格式如下。函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2,);例如:double max(double a,double b);也可以采用下面的形式。函数类型 函数名(参数类型1,参数类型2,);例如:7.2.3 函数的声明double max(do
16、uble,double);C语言的库函数就是位于其他模块的函数,为了正确调用,C编译系统提供了相应的头文件。头文件内许多都是库函数的函数声明,当源程序要使用库函数时,就应当包含相应的头文件。【例7-5】在【例7-4】中使用函数声明。程序代码如下。#include float max(float,float);/*或float max(float a,float b);*/main()float x,y;7.2.3 函数的声明printf(Input data:);scanf(%f%f,&x,&y);printf(max=%fn,max(x,y);/*先调用,实参x,y*/float max(f
17、loat a,float b)/*后定义,形参a,b*/return ab?a:b;7.2.4 调用函数与被调用函数之间的数据传递在C语言中,调用函数和被调用函数之间的数据传递可以通过以下3种方式进行。(1)在实参和形式参数之间进行数据传递。(2)通过return语句把函数值返回调用函数。(3)通过全局变量,但这不是一种好的方式,通常不提倡使用。后两种传递方式比较好理解,下面重点介绍调用函数和被调函数之间通过实参将数据传递给形参实现数据传递。参数具体的传递方式有以下两种。(1)值传递方式(传值):是将实参单向传递给形参的一种方式。(2)地址传递方式(传址):是将实参地址单向传递给形参的一种方式
18、。需要说明如下几点。7.2.4 调用函数与被调用函数之间的数据传递(1)单向传递:不管是传值,还是传址,C语言都是单向传递数据的,且一定是实参传递给形参。(2)传值、传址只是传递的数据类型不同。传址实际是传值方式的一个特例,本质还是传值,只是传递的是一个地址数据值。(3)系统分配给实参、形参的内存单元是不同的。对于传值,即使函数中修改了形参的值,也不会影响实参的值。对于传址,即使函数中修改了形参的值,也不会影响实参的值,但因为传递的是地址,所以就可能通过实参所指向的空间间接返回数值。(4)在两种参数传递方式中,实参可以是变量、常量、表达式;形参一般是变量,要求两者个数一致、类型匹配、赋值兼容。
19、(5)形参在函数未调用时,并不占用内存中的存储单元,只有在函数7.2.4 调用函数与被调用函数之间的数据传递调用时,函数中的形参才被分配内存单元。在调用结束后,形参所占的内存单元被释放。【例7-6】阅读下面的程序,分析其运行结果。程序代码如下。#include void swap1(int a,int b)int t;printf(2):a=%d,b=%dn,a,b);t=a;a=b;b=t;printf(3):a=%d,b=%dn,a,b);7.2.4 调用函数与被调用函数之间的数据传递main()int x=10,y=20;printf(1):x=%d,y=%dn,x,y);swap1(x
20、,y);printf(4):x=%d,y=%dn,x,y);程序运行结果如下。(1):x=10,y=20(2):a=10,b=207.2.4 调用函数与被调用函数之间的数据传递(3):a=20,b=10(4):x=10,y=20由运行结果可以看到,x和y的值已传送给函数swap1中的对应形参a和b,在函数swap1中,a和b进行了交换,但形参的变化并不影响对应的实参。所以在本程序中,不能通过调用swap1函数实现x和y的数据交换。那么如何通过调用swap1函数来实现两个数据的交换?请关注指针相关内容。7.2.5 数组作为参数1.一维数组名作为参数数组名也可以作为实参,但是因为数组名本身是一个地
21、址值,所以用数组名作为参数时,实参与形参都应用数组名。【例7-7】阅读下面的程序,分析一维数组名作为实参时数据的传递过程。程序代码如下。#include void myout(int p,int n);/*“void myout(int p5,int n);”或“void myout(int p);”也可以*/main()int a5=1,2,3,4,5;7.2.5 数组作为参数int b10=2,4,6,8,10,12,14,16,18,20;myout(a,5);myout(b,10);void myout(int p,int n)int i;for(i=0;in;i+)printf(%4
22、d,pi);printf(n);7.2.5 数组作为参数程序的运行结果如下。1 2 3 4 52 4 6 8101214161820由于数组名就是数组的首地址,因此,在数组名作为函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参。形参取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。2.多维数组名作为参数用多维数组名作为实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明,但不能省略第二维及其他高维的大小说明。7.2.5 数组作为参数【例7-8】二维数组名作为参数,输出二维数组。程序
23、代码如下。#include#define M 3#define N 2void out(int aMN)/*还可以写成 void out(int a N)*/int i,j;for(i=0;iM;i+)7.2.5 数组作为参数for(j=0;jN;j+)printf(%4d,aij);printf(n);main()int aMN=1,2,3,4,5,6;out(a);程序运行结果如下。1 23 45 67.3 函数的嵌套调用和递归调用函数允许嵌套调用和递归调用。递归调用是嵌套调用的特例。7.3.1 函数的嵌套调用函数的嵌套调用是指函数调用中又存在调用。例如,函数1调用函数2,函数2又调用函数
24、3。函数之间没有从属关系,一个函数可以被其他函数调用,同时该函数也可以调用其他函数。【例7-9】编写两个函数,分别求两个整数的最大公约数和最小公倍数。在主函数中调用并输出结果。程序代码如下。#include int gcd(int a,int b)int r,temp;if(ab)7.3.1 函数的嵌套调用temp=a;a=b;b=temp;while(b!=0)/*利用辗转相除法,直到b为0为止*/temp=a%b;a=b;b=temp;7.3.1 函数的嵌套调用return a;int lcm(int a,int b)int r;r=gcd(a,b);return(a*b/r);main(
25、)int x,y;7.3.1 函数的嵌套调用scanf(%d%d,&x,&y);printf(%dn,gcd(x,y);printf(%dn,lcm(x,y);程序运行结果如下。16 182144程序中,用辗转相除法求最大公约数。主函数首先调用gcd()求最大公约数,然后调用lcm()求最小公倍数,而lcm()又调用gcd(),这就是函数嵌套调用的过程。7.3.2 函数的递归调用函数的递归调用是指函数直接调用或间接调用自己,或调用一个函数的过程中出现直接或间接调用该函数自身。前者称为直接递归调用,后者称为间接递归调用。用递归调用解决问题的必要条件如下。(1)问题是递归的,解决方法是递归的,处理
26、对象有规律地递增或递减。(2)问题可以递归解决。(3)递归有明确的结束条件。递归函数的特点是:先被调用的后被执行完毕,后被调用的先被执行完毕,每次调用时给函数体内各变量重新分配空间,调用完毕返回到前一次调用的调用点,继续前一次调用函数的执行。【例7-10】用递归函数求n!。7.3.2 函数的递归调用函数的递归调用是指函数直接调用或间接调用自己,或调用一个函数的过程中出现直接或间接调用该函数自身。前者称为直接递归调用,后者称为间接递归调用。用递归调用解决问题的必要条件如下。(1)问题是递归的,解决方法是递归的,处理对象有规律地递增或递减。(2)问题可以递归解决。(3)递归有明确的结束条件。递归函
27、数的特点是:先被调用的后被执行完毕,后被调用的先被执行完毕,每次调用时给函数体内各变量重新分配空间,调用完毕返回到前一次调用的调用点,继续前一次调用函数的执行。【例7-10】用递归函数求n!。7.3.2 函数的递归调用程序代码如下。#include float fac(int);/*函数说明*/main()int m;7.3.2 函数的递归调用float y;printf(Input data:);scanf(%d,&m);y=fac(m);/*函数调用*/printf(%d!=%.0fn,m,y);float fac(int n)/*函数定义*/if(n=1)return 1;7.3.2 函
28、数的递归调用elsereturn n*fac(n-1);/*函数递归调用*/程序的运行结果如下。Input data:44!=247.4 局部变量和全局变量从变量的作用域角度来分,变量可以分为局部变量和全局变量。7.4.1 局部变量局部变量是在一定范围内有效的变量,也称为内部变量。C语言中,在以下位置定义的变量属于局部变量。(1)在函数体内定义的变量,在本函数范围内有效,作用域仅限于函数体内。(2)有参函数的形式参数也是局部变量。只在其所在的函数范围内有效。(3)在复合语句内定义的变量,在本复合语句范围内有效,作用域仅限于复合语句内。关于局部变量有以下几点说明。7.4.1 局部变量(1)在主函
29、数main中定义的变量也只是在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。(2)不同函数中和不同的复合语句中可以定义(使用)同名变量。因为它们的作用域不同,程序运行时在内存中占据不同的存储单元,各自代表不同的对象,所以它们互不干预。(3)局部变量所在的函数被调用或执行时,系统临时给相应的局部变量分配存储单元,一旦函数执行结束,系统立即释放这些存储单元。各个函数中的局部变量起作用的时刻是不同的。7.4.2 全局变量全局变量是在函数之外(所有函数前、各个函数间、所有函数后)定义的变量,也称为外部变量。全局变量的作用域是从定义全局变量的位置起到
30、本源程序结束为止。全局变量可以和局部变量同名。若一个全局变量和某个函数中的局部变量同名,则全局变量将在该函数中被屏蔽,即在该函数内局部变量有效,全局变量不起作用。【例7-11】全局变量与局部变量同名。程序代码如下。#include 7.4.2 全局变量int a=3,b=5;max(int a,int b)int c;c=ab?a:b;return c;main()int a=8;printf(%d,max(a,b);7.4.2 全局变量程序执行结果如下。8本程序中有两个全局变量a和b。在main函数中又定义自动变量a=8,在主函数中全局变量a被屏蔽,所以传递给max函数参数a 的值是8,传递
31、给参数b的值为全局变量b的值5,输出的结果为8。7.5 变量的存储类别变量的存储类别指的是数据在内存中的存储方法。从变量值存在的时间(生存期)角度来分,可以分为静态存储方式和动态存储方式。所谓静态存储方式是指在程序运行期间分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。变量的存储类别具体包括4种:auto型(自动型)、register型(寄存器型)、static型(静态型)和extern型(外部型或全局型)。根据变量的存储类别,可以知道变量的作用域和生存期。7.5.1 auto变量auto变量定义的一般格式如下。auto 数据类型变量名=初值,;
32、例如:auto float a;(1)auto变量作用域。从定义位置起到函数体(或复合语句)结束为止。(2)auto变量生存期。由于自动变量属于自动类别,存储单元被分配在内存的动态区,所以进入函数体(或复合语句)时生成,退出函数体(或复合语句)时消失。未赋初值的自动变量,其值不确定。每次进入函数体或复合语句,7.5.1 auto变量就赋一次指定的初值。auto变量的优点是:各函数互不干扰,标识符同名也互不影响。【例7-12】auto变量的使用。程序代码如下。#include f(int a)auto int c=3,b=0;b=b+1;c=c+1;7.5.1 auto变量return a+b+
33、c;main()int a=2,i;for(i=0;i3;i+)printf(%d,f(a);程序运行结果如下。7777.5.1 auto变量在主函数中3次调用f函数,每次调用时都为局部变量a、b和c 分配内存单元,且a的值为实参传过来的2,b和c的值为0和3。调用结束时,局部变量a、b和c占用的内存空间被释放,下一次调用时再重新分配。7.5.2 register变量只有局部自动变量和形式参数可以作为register变量,其他(如全局变量)不可以。在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束释放寄存器。例如:register int x;(1)register变量的作用域。
34、从定义位置起,到函数体(或复合语句)结束为止。(2)register变量的生存期。进入函数体(或复合语句)时生成,退出函数体(或复合语句)时消失。register变量的优点是:程序运行时,访问存于寄存器内的值要比访问内存中的值快得多。因此当程序对速度有较高要求时,把那些频繁引用的少数变量指定为register变量,有助于提高程序的运行速度。7.5.2 register变量只有局部自动变量和形式参数可以作为register变量,其他(如全局变量)不可以。在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束释放寄存器。例如:register int x;(1)register变量的作用
35、域。从定义位置起,到函数体(或复合语句)结束为止。(2)register变量的生存期。进入函数体(或复合语句)时生成,退出函数体(或复合语句)时消失。register变量的优点是:程序运行时,访问存于寄存器内的值要比访问内存中的值快得多。因此当程序对速度有较高要求时,把那些频繁引用的少数变量指定为register变量,有助于提高程序的运行速度。7.5.3 static变量有时希望函数中局部变量的值在函数调用结束后保留原值,以便下次调用时使用上一次调用后的结果,这时就应该指定该局部变量为static型变量。static型变量属于静态类别,存储单元被分配在内存的静态存储区。(1)static变量的
36、作用域。从定义位置起,到函数体(或复合语句)结束为止。(2)static变量的生存期。程序运行期间永久性保存。编译时为static型变量赋初值(只一次),未赋初值默认为0,运行期间不再赋初值(保留上次运行时得到的值)。static型变量的优点是:函数调用之间保留局部变量值。【例7-13】static变量的使用。程序代码如下。7.5.3 static变量#include f()int a=2;static int b,c=3;b=b+1;c=c+1;return a+b+c;main()7.5.3 static变量int i;for(i=0;i3;i+)printf(%d,f());程序运行结果
37、如下。7911f函数中变量b和c都是静态内存变量,在编译时系统为它们开辟存储空间,并赋初值0和3。它们在整个程序执行期间都占用相同的内存空间,整个程序执行完毕后空间才释放。由于每次调用f函数后不释放静态变量,所以变量a和b中的值是前一次调用后的结果。7.5.4 extern变量全局变量只有静态一种类别。对于全局变量可以使用extern和static两种说明符。定义格式如下。externstatic 数据类型 变量名表;没有特别指明存储类别的全局变量,默认为extern型。1.全局变量的作用域我们知道,在函数外部定义的变量为全局变量。全局变量的作用域是从定义变量的位置开始到本程序文件的结束。使用
38、全局变量可以增加各个函数之间的数据传输渠道。由于函数的调用只能带回一个返回值,因此有时利用全局变量增加与函数联系的渠道,从函数得到一个以上的返回值。【例7-14】编写函数,从存放10个学生成绩的数组中求平均分、最高分7.5.4 extern变量和最低分。程序代码如下。#include float Max=0,Min=0;float average(float score,int n)int i;float aver,sum=score0;Max=Min=score0;for(i=1;iMax)Max=scorei;if(scoreiMin)Min=scorei;sum+=scorei;aver
39、=sum/n;return aver;main()7.5.4 extern变量float ave,score10;int i;for(i=0;i10;i+)scanf(%f,&scorei);ave=average(score,10);printf(max=%.1f,min=%.1f,average=%.1fn,Max,Min,ave);程序运行结果如下。78 98 76 67 89 90 92 82 81 85max=98.0,min=67.0,average=83.8虽然使用全局变量有很多好处,但是,还是建议除非特别需要,不要使用7.5.4 extern变量全局变量。程序设计要求做到模块功
40、能单一,一般要把函数设计成单一功能的封闭体,仅通过实参向形参传递参数的措施,使函数与外界发生联系,故要慎用、少用全局变量,以保障程序的模块化和通用性,增强程序的可读性和可维护性。2.全局变量的生存期全局变量的生存期是程序运行期间,在这一期间全局变量将永久性保存。编译时全局变量未赋初值,初值默认为0。全局变量的优点:由于全局变量在整个程序运行期间都占用内存空间,所以它始终保留要使用的数据(除在某个函数中定义了同名局部变量,在这个函数中同名局部变量有效)。【例7-15】全局变量的使用。7.5.4 extern变量程序代码如下。#include int sum;void fun();main()in
41、t sum=10;printf(1)%4d n,sum);fun();printf(2)%4d n,sum);7.5.4 extern变量void fun()sum=20;printf(fun%4d n,sum);程序运行结果如下。(1)10fun20(2)10在引用全局变量时如果使用extern声明全局变量,可以扩大全局变量的作用域。例如,扩大到整个源文件(模块),对于多源文件(模块)可以扩大到其他源文件(模块)。在定义全局变量时如果使用修饰关键词static,表示此全局变量作用域仅限于本源文件(模块)。7.6 编译预处理C源程序除了包含程序命令(语句)外,还可以使用各种编译预处理指令。编译
42、预处理指令是给编译器的工作指令,这些编译预处理指令通知编译器在编译工作开始之前对源程序进行某些处理。编译预处理指令就是以“#”号开头的预处理命令,如包含命令“#include”、宏定义命令“#define”等。在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面。预处理是C语言的一个重要功能,它由预处理程序负责完成。C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。程序合理地使用预处理,便于阅读、修改、移植和调试,也有利于模块化程序设计。7.6.1 宏定义宏定义是用标识符来代表一个字符串(给字符串取个名字)。C语言用“#define”进行宏定义。C编译系统在编译前将这些标识符
43、替换成所定义的字符串。宏定义分为不带参数的宏定义和带参数的宏定义。1.不带参数的宏定义不带参数的宏定义的格式如下。#define 标识符 字符串需要说明如下几点。(1)宏名遵循标识符规定,习惯用大写字母表示,以便区别普通的变量。(2)宏定义字符串不要以分号结束,否则分号也作为字符串的一部分参加展开。(3)宏定义后,宏名的作用范围从定义命令开始直到本源程序结束。7.6.1 宏定义可以通过“#undef”终止宏名的作用域。(4)在宏定义中,可以出现已经定义的宏名,还可以层层置换。例如:#define PI 3.14#define R 3.0#define L 2*PI*R#define S PI*
44、R*Rmain()printf(L=%f,S=%f,L,S);2.带参数的宏定义7.6.1 宏定义带参数的宏定义不只是进行简单的字符串替换,还要进行参数替换。带参数的宏定义的格式如下。#define 宏名(参数表)字符串例如:#define S(a,b)a*b其中S是宏名,a,b是形式参数。程序调用S(3,2)时,把实参3、2分别代替形参a、b。需要说明如下几点。(1)带参数的宏定义的本质还是简单的字符替换(除了参数替换),所以容易发生错误。例如,“#define S(a,b)a*b”,若有语句“area=S(a+b,c+d);”,则经过宏展开后,该语句为“area=a+b*c+d;”,7.6
45、.1 宏定义明显和我们的意图不符。正确的做法是宏定义的字符串中的形参用括号括起来,即“#define S(a,b)(a)*(b)”,则语句“area=S(a+b,c+d);”经过宏展开后为“area=(a+b)*(c+d);”。为了避免出错,建议将宏定义字符串中的所有形参用括号括起来。替换时括号作为一般字符原样照抄,这样用实参替换时,实参就被括号括起来作为整体,不至于发生类似错误。(2)带参数的宏定义在程序中使用时,它的形式及特性与函数相似,但本质完全不同。函数调用在程序运行时,先求表达式的值,然后将值传递给形参,带参宏展开只在编译时进行简单的字符置换。函数调用是在程序运行时处理的,7.6.1
46、 宏定义在堆栈中给形参分配临时的内存单元,宏展开是在编译时进行的,展开时不可能给形参分配内存,也不进行值传递,也没有返回值。函数的形参要定义类型,且要求形参、实参类型一致。宏不存在参数类型问题。许多问题既可以用函数也可以用带参数的宏定义。宏占用的是编译时间,函数调用占用的是运行时间。在多次调用时,宏使得程序变长,而函数调用不明显。7.6.2 文件包含一个C源文件可以使用文件包含命令将另外一个C源文件的全部内容包含进来。文件包含的语法格式如下。#include 文件名或#include 需要说明如下几点。(1)被包含的文件称为头文件(“#include”一般写在模块的开头)。头文件常常以“.h”
47、为扩展名(也可以用其他扩展名,“.h”只是习惯或风格)。(2)一条“#include”只能包含一个头文件,若要包含多个头文件,则使用多条“#include”命令。7.6.2 文件包含(3)被包含的头文件可以用双引号()括起来,也可以用尖括号()括起来。区别在于:用尖括号时,系统到存放C库函数头文件所在的目录中寻找要包含的文件,这称为标准方式;用双引号时,系统先在用户当前目录查找要包含的文件,若找不到,再按标准方式查找。习惯上,系统库函数的头文件一般在系统指定目录下,所以常常用尖括号。用户头文件一般在用户目录下,所以常常用双引号。(4)当包含文件修改后,对包含该文件的源程序必须重新进行编译和连接
48、。(5)在包含文件中还可以包含其他文件。在多模块应用程序的开发上,经常使用头文件组织程序模块。使用头文件成为共享源代码的方法之一。程序员可以将模块中某些公共内容移入头文件,如常量、数据类型定义、全局变量声明、函数声明等,供本模块或其他模块包含使用。7.7 技 能 训 练函数是程序模块化设计最直接的实现方式。使用函数可以控制模块的大小,可以控制变量的作用范围,有利于多人合作开发,可以提高程序的复用率。所以使用函数不仅可以提高程序设计的效率,缩短程序开发周期,还有利于程序的扩充与维护。在实际应用中,分析问题尽可能把大问题化成小问题,用函数实现,这就要求熟练编写与调用函数,多做练习。【例7-16】编
49、写函数isprime(int n),用来判断n是否为素数。若是素数,函数返回1,否则返回0。程序代码如下。#include stdio.h7.7 技 能 训 练int isprime(int x);main()int n;printf(Please input a integer number:);scanf(%d,&n);if(isprime(n)printf(%d is a prime numbern,n);elseprintf(%d is not a prime numbern,n);7.7 技 能 训 练int isprime(int x)int i;for(i=2;i=x/2;i+)
50、if(x%i=0)return 0;return 1;程序运行结果如下。Please input a integer number:5757 is not a prime number7.7 技 能 训 练【例7-17】编写函数,将一个字符串中的大写字母转换成小写字母,输入/输出在主函数中实现。程序代码如下。#include#include void change(char s)int i,n;n=strlen(s);for(i=0;i=A&si=Z)si+=32;main()char str81;gets(str);puts(str);change(str);puts(str);7.7 技