第五章 函数 本章学习重点: 函数的定义与函数声明; 函数的调用; 函数的嵌套调用与递归调用; 数组作为函数参数; 第五章 函数 本章学习重点: 函数的定义与函数声明; 函数的调用; 函数的嵌套调用与递归调用; 数组作为函数参数; 内部变量与外部变量; 变量的作用域及其存储类型; 内部函数与外部函数。
本章目录 第十一讲 函数定义、调用、函数原型 及函数返回语句 第十二讲 函数的嵌套、递归调用及函 数之间的数据传递 第十一讲 函数定义、调用、函数原型 及函数返回语句 第十二讲 函数的嵌套、递归调用及函 数之间的数据传递 第十三讲 变量作用域及存储类型、内 部函数和外部函数 本章小结 结束
第十一讲 函数定义、调用、函数原型及函数返回语句 第十一讲 函数定义、调用、函数原型及函数返回语句 一、函数的定义、调用及函数返回语句 二、函数原型 练一练 返回到本章目录
一、函数的定义、调用及函数返回语句 【思考题5-1】 :定义一个函数,用于求两个数中较大的数。 (一)程序分析 (1)在本程序中定义两个函数:main函数和max函数。 (2)max函数中定义一个变量c,存放两个参数中较大的数,通过return语句把c的值返回调用函数。 (3)主函数中通过调用max函数求两个数中较大的数。 返回到本章目录
double max(double a,double b) { double c; if(a>b) c=a; else c=b; (二)编写程序代码 #include "stdio.h" double max(double a,double b) { double c; if(a>b) c=a; else c=b; return(c); } main() { double x=5,y=8,z; printf("\nx=%f,y=%f",x,y); z=max(x,y); printf("\nmax=%f",z); 程序中第2~9行是一个自定义的函数部分,第2行定义了一个函数名max和指定两个形参a、b及其类型,程序第13行是一个调用函数语句,其中max后面括弧内的x、y是实参。x、y是main函数中定义的变量,而变量a、b是函数max中的形式参数。 返回到本章目录
(三)调试运行结果 程序运行结果如下: 其中,“x=”、“y=”是程序中原样输出的部分,5.000000为程序调用之前x的值,8.000000为程序调用之前y的值,8.000000为程序的运行结果。 返回到本章目录
1. 函数的分类 1)标准库函数与头文件 从函数使用的角度来看,C语言的函数可以分为两类:标准库函数和用户自定义函数。 1. 函数的分类 从函数使用的角度来看,C语言的函数可以分为两类:标准库函数和用户自定义函数。 1)标准库函数与头文件 (1)标准库函数。Turbo C系统提供了400多个标准库函数,按功能可以分为:类型转换函数、字符判别与转换函数、字符串处理函数、标准I/O函数、文件管理函数和数学运算函数等。它们的执行效率高,用户需要时,可在程序中直接进行调用。 (2)头文件。C语言库函数所用到的常量、外部变量、函数类型和参数说明,都在相应的头文件(扩展名为.h)中声明,这些文件通常存放在系统目录“tc\include”中,如: 返回到本章目录
① stdio. h文件:标准输入/输出函数所用的常量、结构、宏定义、函数的类型、参数的个数与类型的描述。 ② math ① stdio.h文件:标准输入/输出函数所用的常量、结构、宏定义、函数的类型、参数的个数与类型的描述。 ② math.h文件:与数学函数有关的常量、结构及相应的函数类型和参数描述。 ③ string.h文件:与字符串操作函数有关的常量、结构以及相应的函数类型和参数描述。 ④ stdlib.h文件:与存储分配、转换、随机数产生等有关的常量、结构以及相应函数的类型和参数描述。 ⑤ ctype.h文件:字符函数有关的常量、宏定义以及相应函数的类型和参数描述。 使用头文件的方法就是在程序开头使用编译预处理的文件包含#include语句,具体内容见第六章。 返回到本章目录
2)用户自定义函数 从函数的形式上看,一个C语言程序必须包含一个且只有一个main函数,由main 函数开始调用其他函数,其他函数也可相互调用,但最终返回主函数结束程序。函数的定义通常分为无参数的函数和有参数的函数两种(特殊的有空函数),如图5-1所示。 图5-1 函数的分类及功能 返回到本章目录
2. 函数的定义 函数的定义格式有三种,无参函数的定义、有参函数的定义及空函数的定义。 1)无参函数定义的一般格式 2. 函数的定义 函数的定义格式有三种,无参函数的定义、有参函数的定义及空函数的定义。 1)无参函数定义的一般格式 “类型标志符”表示函数值的类型,即函数返回值的类型。无参函数一般不需要返回函数值,因此可以不写类型标志符(或写成空类型void。 类型标志符 函数名() { 说明部分(可省略) 执行部分 返回语句(可省略) } 返回到本章目录
例如: printstar() /. 定义一个没有参数和返回值的函数printstar. / { printf(" 例如: printstar() /*定义一个没有参数和返回值的函数printstar*/ { printf("* * * * * * * * * * * * *"); } main() { printstar(); /*调用printstar()函数*/ 程序运行结果如下: 其中,printstar是用户定义的函数名,用来输出一排“*”号。 返回到本章目录
2)有参函数定义的一般格式 有参函数比无参函数多了一个参数表。调用有参函数时,调用函数将赋予这些参数实际的值。为了与调用函数提供的实际参数区别开,将函数定义中的参数表称为形式参数。 类型标志符 函数名(形式参数表) { 说明部分(可省略) 执行部分 返回语句(可省略) } 返回到本章目录
double max(double a,double b) /*函数体的定义*/ { double c; /*函数体中说明部分*/ if(a>b) c=a; else c=b; return(c); /*函数的返回语句*/ } 返回到本章目录
这是一个求a和b二者中较大者的函数,第一行第一个关键字double表示函数值是双精度型的,max为函数名,括号中有两个形式参数a和b,它们都是双精度型浮点型变量。花括号内是函数体,它包括说明部分和执行部分,在说明部分定义所用的变量。在函数体的语句中求出c的值(为a与b中大者),“return(c);”的作用是将c的值作为函数的返回值带回到主调函数中。“return”后面括弧中的值“c”作为函数带回的值(或称函数返回值)。 返回到本章目录
3)空函数定义的一般格式 例如,有以下函数sunny定义: sunny() { } 调用此函数时,什么工作也不做,没有任何实际作用。在主调函数中写上“sunny();”表明“这里要调用一个函数”,而现在这个函数没有起作用,等以后扩充函数功能时补充上。在程序设计中往往根据需要确定若干模块,分别由一些函数来实现。而在第一阶段只设计最基本的模块,其他一些次要功能则在以后需要时陆续补上。 类型标志符 函数名() { } 返回到本章目录
3. 函数的调用 函数名(实际参数表) 函数名() 3. 函数的调用 1)函数调用格式 在【思考题5-1】中,z=max(x,y);为函数的调用语句,max后面括弧内的x,y是实际参数表,我们也把它称为实参。 说明: 实际参数(简称实参)的个数多于一个时,各实参之间用逗号隔开。 若函数无形参,调用格式为: 函数名(实际参数表) 函数名() 返回到本章目录
注意: (1)实参的个数必须与形参的个数一致。在【思考题5-1】中,有两个实参分别为x、y,有两个形参分别为a、b,实参与形参的格式必须一致。 (2)在定义的函数中,必须指定形参的类型,并且实参的类型必须与形参的类型一一对应。如在【思考题5-1】中: double max(double a,double b) {...} 形参a、b的类型定义为double,实参为x、y,定义类型也为double,且x与a对应,y与b对应。如果实参a为整型而形参x为实型,或者相反,则按照不同类型数值的赋值规则进行转换。例如实参x的值为3.5,而形参a为整型,则将实数3.5转换成整数3,然后送到形参a中。 (3)形参只能是变量,实参可以是常量、变量或表达式。如,max(5)实参为常量,max(3,a+b)实参为表达式,但是要求它们有确定的值,在调用时将实参的值赋给形参。 返回到本章目录
(4)实参与形参的类型应相同或赋值兼容。【思考题5-1】中的实参和形参都是double类型,这是合法的。 (5)实参可以和形参同名。 可以将【思考题5-1】中程序的被调用函数max()改为如下所示: double max(double x,double y) { double c; if(x>y) c=x; else c=y; return(c); } 形参与实参变量的名称相同,但是不影响程序的结果。 返回到本章目录
4. 函数的调用方式 (1)作为表达式的函数调用。作为表达式的函数调用,本身就是一个表达式,它的值将参与整个表达式的求值过程,因此被调用函数必须是有返回值的。例如: c=max(a,b); (2)作为语句的函数调用。作为语句的函数调用就是在一个语句中单独使用函数调用,也就是在函数调用后加语句结束符“;”,从而构成表达式语句。例如: printf("hello!"); 返回到本章目录
(3)函数参数。函数调用作为一个函数的实参。例如,有如下函数调用语句: m=max(a,max(b,c)); 其中max(b,c)是一次函数调用,它的值作为max另一次调用的实参。m的值是a、b和c三者中最大的。 函数调用作为函数的参数,实质上也是函数表达式调用的一种,因为函数的参数本来就要求是表达式形式。 返回到本章目录
5. 函数返回语句return (1)函数调用过程中,程序执行将从调用处转移到被调用函数处执行,待被调用函数执行完毕,再返回到调用处的下面继续执行。 (2)C语言的函数根据函数返回值的有无,可分为两类:一类是有返回值的函数,另一类是无返回值的函数。所有函数都可以通过return语句,结束被调用函数的执行,返回调用函数。 (3)return语句的一般格式。 ① 有返回值的return有两种调用格式: 或 return 表达式; return(表达式); 返回到本章目录
有表达式的return语句,在实现函数返回时,将表达式的值同时带回调用函数。例如【思考题5-1】中的语句: return(c); 就是将max子函数中的结果c值返回到主函数中调用语句c=max(a,b);处。 ② 无返回值的return调用格式为: 无表达式的return语句,只将控制权由被调用函数转回调用函数,而不返回任何值。如果子函数没有任何返回值(即子函数的类型为空类型void),则使用这样的语句返回。 return; 返回到本章目录
6. 函数返回语句的几点说明 (1)一个return语句,只能将一个表达式的值带回调用函数。在一个函数中允许有一个或多个return语句,每个return后面的表达式的类型应相同,流程执行到其中一个return语句即返回调用函数。 (2)C语言允许在没有返回值的函数中使用不带表达式的return语句,以便立即结束函数的执行,返回到调用函数。当不带表达式的return语句位于函数的最末时,允许将return语句省去,这时遇到函数体的右花括号也会将流程返回调用函数。 返回到本章目录
(3)如果return语句中表达式值与函数定义的类型不一致,则以函数定义类型为准,并自动将return语句中的表达式的值转换为函数返回值的类型。 int max(float x, float y) /*定义有参函数max*/ { float z; z=x>y?x:y; return(z); } 函数max定义为整型,而return语句中的z为实型,二者不一致,按上述规定,先将z转换为整型,然后max(x,y)带回一个整型值2回主调函数main。如果将main函数中的c定义为实型,用%f格式符输出,也是输出2.000000。 返回到本章目录
二、函数原型 思考题5-2:分析以下程序,观察函数原型的定义与使用。 void print1(int n); /*print1 函数原型*/ main() { print1(5); } void print2(int x,int y) { print1(x); print1(y); void print1(int n) /*print1 函数定义*/ { printf("%d\n",n); 返回到本章目录
(一)程序分析 (1)该程序包含三个函数的定义:main函数、print1函数和print2函数。main函数和print2函数均调用print1函数,而print1函数的定义位于main函数和print2函数之后,所以在main函数和print2函数之前给出print1函数的原型。若将print1函数的原型改放在main函数内,main函数可以调用print1函数,print2函数将不能调用print1函数。所以若将print1函数的原型改放在main函数内,在print2函数内还必须再给出print1函数的原型。 返回到本章目录
(2)若在main函数中还要调用print2函数,则在调用之前,必须对print2函数进行声明,解决这个问题的方法有两个:一是在main函数中给出print2函数原型,二是在程序的首部、main函数之前给出print2函数原型。 (二)调试运行结果 程序运行结果如下: 返回到本章目录
1. 函数原型的定义 编译程序在处理函数调用时,必须从程序中获得完成函数调用所必需的接口信息。函数原型为函数调用提供接口信息,函数原型是一条程序说明语句。 函数原型的基本格式就是在函数定义格式的基础上去掉函数体,定义格式如下: 类型符 函数名(形式参数表); 返回到本章目录
说明: (1)函数原型必须位于对该函数的调用之前。 (2)函数原型中的函数类型、函数名、参数个数、参数类型和参数顺序必须与函数定义中的相同,但是参数名可以省略。 (3)从格式上可以看出,函数原型所能提供的信息,函数定义也能提供。因此,如果函数定义于函数调用之前,则函数定义本身就同时起到了函数原型的作用。在这种情况下,不必给出函数原型。反之,如果函数定义于函数调用之后,或位于别的源文件中,则必须提前给出函数原型。 返回到本章目录
2. 函数定义和函数原型的比较 1)函数定义和函数原型的作用不同 函数定义是指对函数功能的确立,包括函数名、函数返回值、函数形参及其类型、函数体等,它是完整的、独立的函数单位。 函数原型的作用是为函数调用提供接口信息。 返回到本章目录
函数的定义只能出现在其余函数定义的外部,所有函数的定义都是平行、独立的。 2)函数定义和函数原型在程序中出现的位置不同 函数的定义只能出现在其余函数定义的外部,所有函数的定义都是平行、独立的。 函数原型可以位于所有函数的外部,也可以位于调用函数的函数体的说明部分,但必须位于该函数的调用之前。 3)函数定义和函数原型在程序中出现的次数不同 一个函数在同一个程序中只能定义一次,即函数不能重复定义;但是一个函数的函数原型在同一个程序中可以出现一次,也可以出现多次。 返回到本章目录
练一练 【练习5-1】 分析下列程序的输出结果,说明其中的函数定义和函数调用。源程序如下: double fun(int n) { int i; double s=1; for(i=1;i<=n;i++) s*=i; return s; } main() { int i,n; double sum=0; printf("\ninput n:"); scanf("%d",&n); sum+=fun(i); printf("\n1!+2!+…+%d!=%.0lf",n,sum); 解:程序分析如下: (1)该程序包含两个函数的定义:main函数和fun函数。main函数是由系统调用执行的,fun函数是由main函数调用的。 (2)fun函数求自然数n的阶乘值并返回。在main函数中,首先通过键盘输入一个整数给变量n,然后调用fun函数计算1!+2!+ …+n!的值。 返回到本章目录
程序流程图如图5-2所示。程序运行结果如下: 【练习5-1】的程序流程图 返回到本章目录
【练习5-2】求xn。 解:数学上经常碰到求xn的问题,即计算n个x的乘积,当用C语言求解方程时会经常用到。 (1)程序设计思路。计算时根据指数运算的定义,用一个函数实现其运算,根据n的不同采取不同的计算方法:如果n=0,则结果为1;如果n<0,则结果为1/xn;如果n>0,则结果是xn,然后在主函数中调用此函数即可。 (2)算法设计,用自然语言表示的算法如下: ① 接受用户输入的指数和底数; ② 调用函数进行计算; ③ 输出结果。 返回到本章目录
(3)程序设计与运行,源程序如下: #include "stdio. h" double pow(double x,int n) / (3)程序设计与运行,源程序如下: #include "stdio.h" double pow(double x,int n) /*函数的定义在main()之前时不需要声明*/ { double y; int i; y=1; if(n==0) return(1.0); else if(n>0) { for(i=1;i<=n;i++) y=y*x; return(y); } else return(1/y); 返回到本章目录
main() { int x,y; printf("input x:"); scanf("%d",&x); printf("input y:"); scanf("%d",&y); printf("result:%f\n",pow(x,y)); } (4)程序运行结果如下: 返回到本章目录
本 讲 小 结 这一讲我们学习了函数的定义以及函数的调用格式。在对函数进行定义时,有返回值的函数必须用函数返回语句结束函数的运行。函数可以作为表达式调用,也可以作为语句调用。 返回到本章目录
第十二讲 函数的嵌套、递归调用及函数之间的数据传递 第十二讲 函数的嵌套、递归调用及函数之间的数据传递 一、 函数的嵌套调用 二、 递归函数及递归调用 三、实参-形参之间的数据传递(值传递方式) 四、实参-形参之间的数据传递(数组作函数参数) 返回到本章目录
一、函数的嵌套调用 思考题5-3:求1k+2k+3k+…+nk的值。 (一)程序分析 为了解决这个问题,我们设置了三个函数: (1)power()函数用来求mn的值并返回; (2)sum_power()函数用来求1k+2k+3k+…+nk 的值并返回; (3)在main()先由键盘输入变量k和n的值,然后计算并输出1k+2k+3k+…+nk的值。 返回到本章目录
程序流程图 : 返回到本章目录
(二)编写程序代码 double power(int k, int n) /*求nk的值并返回*/ { int i; double pw=1; for(i=1;i<=k;++i) pw*=n; return(pw); } double sum_power(int k, int n) /*求1k + 2k + 3k + …+ nk的值并返回*/ { int i ; double sum=0; for(i=1;i<=n;++i) sum+=power(k,i); return(sum); 返回到本章目录
main() { int k, n; printf("\ninput k:"); scanf("%d",&k); printf("\ninput n:"); scanf("%d",&n); printf("\nsum of %dth powers of integers from 1 to %d=%.0lf\n ",k,n,sum_power(k,n)); } 返回到本章目录
(三)调试运行程序 程序运行结果如下: 3和5为程序输入数据,程序运行结果为225。 返回到本章目录
函数的嵌套调用是指在执行被调用函数时,被调用函数又调用了其他函数。 1.函数的嵌套调用 函数的嵌套调用是指在执行被调用函数时,被调用函数又调用了其他函数。 2.函数嵌套调用时需要注意的地方 (1)C语言程序中的函数定义都是平行、相互独立的。也就是说在一个函数定义的内部,不能定义其他函数,即函数的定义不允许嵌套。 (2)一个函数既可以被其他函数调用,也可以调用其他函数,这就是函数的嵌套调用。 main函数是由编译程序调用执行的,main函数可以调用其他函数,但其他函数不能调用main函数。 返回到本章目录
例如,在main函数中可以调用A函数,在调用A函数的过程中可以调用B函数;当B函数调用结束后返回到A函数,当A函数调用结束后,再返回到main函数,这就是函数的嵌套调用。 函数的嵌套调用过程示意图 返回到本章目录
在图中,①,②,…,⑨,表示执行嵌套调用过程的序号。即从①开始,先执行main函数的函数体中的语句,当遇到调用A函数时,由②转去执行A函数;③是执行A函数的函数体中的语句,当遇到调用B函数时,由④转去执行B函数,⑤是执行B函数的函数体中的所有语句,当B函数调用结束后,通过⑥返回到调用B函数的A函数中;⑦是继续执行A函数体中的剩余语句,当A函数调用结束后,通过⑧返回到调用A函数的main函数中;⑨表示继续执行main函数的函数体中的剩余语句,结束本程序的执行。 返回到本章目录
二、递归函数及递归调用 思考题5-4:设计递归函数fact(n),计算并返回n的阶乘值。 (一)程序分析 一个整数n的阶乘可表示为: 阶乘定义还可以表示为: n*(n-1) (n>1) 现在定义一个函数fact(n)来求n!时,可以使用如下的方式: 1 (n=0或n=1) fact(n)= n*fact(n-1) (n>1) 返回到本章目录
从上面我们可以看到,当n>1时,fact(n)可以转化为n 从上面我们可以看到,当n>1时,fact(n)可以转化为n*fact(n-1),而fact(n-1)与fact(n),只是函数参数由n变成n-1;而fact(n-1)又可以转化为(n-1)*fact(n-2),……,每次转化时,函数参数减1,直到函数参数的值为1时,1!的值为1,递归调用结束。 递归调用的过程如图所示,假设输入n=5: fact函数的递归调用过程示意图 返回到本章目录
使用一个main函数调用fact求5!,如图5-5所示,倾斜箭头表示函数调用,旁边的数字表示传递的参数;下面的箭头表示函数返回,旁边的数字表示函数返回值。fact函数反复调用自身;fact(5)调用fact(4),fact(4)调用fact(3),fact(3)调用fact(2),……,参数逐次减小,当最后调用fact(1)时,结束调用,于是开始逐级完成乘法运算,最后计算出5!结果为120。 返回到本章目录
(二)编写程序代码 double fact(int n) { if( n==0 || n==1) return(1); else return(n*fact(n-1)); } main() { int num; printf("\ninput num:"); scanf("%d",&num); printf("\nfact(%d)=%.0lf",num,fact(num)); 返回到本章目录
(三)调试运行结果 程序运行结果如下: 其中:5↙表示输入的阶乘数,120为5!的运行结果。 返回到本章目录
1.递归函数 (1)递归函数的概念。函数通过其函数体中的语句直接或间接地调用自身,称为递归调用,这样的函数称为递归函数。 (2)直接递归与间接递归。 ① 如果函数f1直接调用它本身,称为直接递归调用,如图(a)所示。 ② 如果函数f1调用函数f2,函数f2调用函数f1,则称为间接递归调用,如图(b)所示。 返回到本章目录
递归调用示意图 返回到本章目录
2.递归算法必须满足的两个条件 无论是直接还是间接递归,两者都是无终止的调用自身。要避免这种情况的发生,使用递归解决的问题应满足两个基本条件。 (1)问题的转化。有些问题不能直接求解或难以求解,但它可以转化为一个新问题,这个新问题相对较原问题简单或更接近解决方法。这个新问题的解决与原问题一样,可以转化为下一个新问题,…… (2)转化的终止条件。原问题到新问题的转化是有条件的,次数是有限的,不能无限次数地转化下去。这个终止条件也称为边界条件,相当于递推关系中的初始条件。 返回到本章目录
采用递归方法在解决问题的时候有很多优点,而递推方法也有自己的特点,现比较如下: 3.递归与递推的优点与缺点 采用递归方法在解决问题的时候有很多优点,而递推方法也有自己的特点,现比较如下: 递归与递推的优缺点比较 返回到本章目录
三、实参-形参之间的数据传递(值传递方式) 【思考题5-5】阅读下面程序,注意观察程序的运行结果。 void swap(int x, int y); main() { int a=5,b=8; printf("\n(1)a=%d,b=%d\n",a,b); swap(a,b); printf("\n(4)a=%d,b=%d\n\n",a,b); } void swap(int x,int y) { int t; printf("\n(2)x=%d,y=%d\n",x,y); t=x; x=y; y=t; printf("\n(3)x=%d,y=%d\n",x,y); 返回到本章目录
(一)程序分析 main函数调用swap函数时,将变量a和变量b的值传递给对应形参x和形参y。在swap函数中,交换了形参x和形参y的值。但由于在C语言中,参数中的数据只能由实参单向传递给形参,形参数据的变化并不影响对应实参的值,因此该程序不能通过swap函数将main函数中的变量a和b的值进行交换。 (二)调试运行程序 程序运行结果如下: 返回到本章目录
1.对函数的形参和实参的几点说明 (1)在调用函数时,函数的实参和形参在个数、类型和次序上要一一对应。 (2)对于非void类型的函数,函数的调用只能通过return语句得到一个返回值;对于void类型的函数,调用函数不能返回值。 (3)形参在函数未被调用时,系统并不给它们分配存储单元,只有在发生函数调用时形参才被分配临时存储单元,但在调用结束后,系统自动释放形参占用的存储单元。 返回到本章目录
(4)实参可以是常量、变量或表达式,但必须有确定的值,以便传递给形参。 (5)形参一定是变量,当然可以是指针类型,如数组名(它代表数组的首地址)和指针变量(见第七章)。 (6)值传递。如果实参是一个非指针类型的变量(包括数组名),此时形参和实参的数值传递是“单向的值传递”,即实参仅把它的值传递给对应的形参,形参的值即使在函数中发生了改变,形参的值也不能传递给实参。 返回到本章目录
四、实参-形参之间的数据传递 (数组作函数参数) 思考题5-6:已知某个学生5门课程的成绩,求其平均成绩。观察数组名作函数参数的使用。 (一)编写程序代码 float aver(float a[5]) /*求平均值函数*/ { int i; float av,s=0; for(i=0;i<5;i++) s+=a[i]; /*循环累加各数组元素的值*/ av=s/5; /*求平均值*/ return av; } 返回到本章目录
main() { float sco[5],av; int i; printf("\ninput 5 scores:\n"); for(i=0;i<5;i++) scanf("%f",&sco[i]); /*输入5门课程的成绩*/ av=aver(sco); /*调用函数,实参为数组名*/ printf("average score is %5.2f\n",av); } 返回到本章目录
(二)程序分析 (1)用数组名作函数参数,应该在主调函数和被调用函数中分别定义数组,【思考题5-6】中sco是实参数组名,a是形参数组名,分别在其所在函数中定义,不能只在一处定义。 (2)实参数组与形参数组类型应一致。 (5)用数组名作函数实参时,不是把数组元素的值传递给形参,而是把实参数组的起始地址传递给形参数组,这样两个数组就共占同一段内存单元。 返回到本章目录
(3)在被调用函数中声明了形参数组的大小为5,但在实际上,指定其大小是不起任何作用的,因为C编译对形参数组大小不做检查,只是将实参数组的首地址传给形参数组。因此,sco[n]和a[n]指的是同一单元。 (4)形参数组也可以不指定大小,在定义数组时在数组名后面跟一个空的方括弧,即float aver(float a[])。有时为了在被调用函数中处理数组元素的需要,可以另设一个参数,传递需要处理的数组元素的个数。 返回到本章目录
(三)调试运行程序 程序运行结果如下: 其中,65、98、56、78、82为输入的成绩,最终输出成绩平均值,并保留2位有效小数。 返回到本章目录
2.数组元素作函数参数 (1)数组元素就是下标变量,它与普通变量并无区别。数组元素只能用做函数实参,其用法与普通变量完全相同:在发生函数调用时,把数组元素的值传送给形参,实现单向值传送。 (2)用数组元素做实参时,只要数组类型和函数的形参类型一致即可,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。 (3)在普通变量或下标变量做函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。 返回到本章目录
3.数组名作函数参数的几点说明 (1)数组名作函数参数时,要求形参和相对应的实参都必须是类型相同的数组(或指向数组的指针变量),都必须有明确的数组说明。 (2)用数组名作函数参数,应该在调用函数和被调用函数中分别定义数组,且数据类型必须一致,否则结果将出错。 (3)C编译系统对形参数组大小不做检查,所以形参数组可以指定大小或不指定大小。例如,形参数组a[5]可写成a[]。 返回到本章目录
4.用多维数组名作函数参数 多维数组元素可以作为实参,这点与前述相同。可以用多维数组名作为实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。如int arr[3][5];或int arr[ ][5];二者都合法而且等价。但是不能把第二维以及其他高维的大小说明省略,如int arr[3][ ];或int arr[ ][ ]; 均不合法。 返回到本章目录
练一练 【练习5-3】设计递归函数gcd(x,y),求x和y的最大公约数。 解:程序分析:如果x%y==0,x和y的最大公约数就是y;否则求x和y的最大公约数等价于求y与x%y的最大公约数。这时可以把y 当做新的x,x%y当做新的y,问题又变成了求新的x与y的最大公约数。它又等价于求新的y与x%y的最大公约数,……,如此继续,直到新的x%y==0时,其最大公约数就是新的y。 例如,求48与36的最大公约数,等价于求36与48%36的最大公约数,即求36与12的最大公约数。此时36%12==0,最大公约数就是12。 求x和y的最大公约数,用函数gcd(x,y)表示如下: y (x%y==0) gcd(x,y)= gcd(y,x%y) (x%y!=0) 返回到本章目录
其中,“input x”和“input y”是提示信息,分别为x、y输入48和36两个数,12为两数的最大公约数,即程序的运行结果。 程序源代码如下: int gcd(int x,int y) { if(x%y==0) return(y); else return(gcd(y,x%y)); } main() { int x,y; printf("\ninput x:"); scanf("%d",&x); printf("\ninput y:"); scanf("%d",&y); printf("\ngcd(%d,%d)=%d",x,y,gcd(x,y)); 程序运行结果如下: 其中,“input x”和“input y”是提示信息,分别为x、y输入48和36两个数,12为两数的最大公约数,即程序的运行结果。 返回到本章目录
【练习5-4】任意输入3个整数,利用函数的嵌套调用求出3个数中的最小值。 解:程序设计思路如下:在main()函数中接受用户输入的3个数,再通过函数调用来求出最小值。在main()外先定义函数int mintwo(int,int) 求出两个整数中的最小值,然后定义int minthree(int,int,int)函数,并在此函数中调用mintwo(int,int)函数,求出3个数中最小值。 返回到本章目录
程序源代码如下: #include <stdio 程序源代码如下: #include <stdio.h> main() { int x,y,z,min; int mintwo(int,int),minthree(int,int,int); printf("\nplease input three integers:"); scanf("%d%d%d",&x,&y,&z); min=minthree(x,y,z); /*函数调用语句,3个实参进行传递*/ printf("min is %d",min); } 返回到本章目录
int mintwo(int a,int b) /*函数定义*/ { return(a<b?a:b); /*使用条件语句,将结果返回*/ } int minthree(int a,int b,int c) { int z; z=mintwo(a,b); /*函数内嵌套调用函数mintwo()*/ z=mintwo(z,c); return(z); 程序运行结果如下: 返回到本章目录
【练习5-5】 有一个3×4的矩阵,求所有元素中的最大值。 解: fmax(int arr[ ][4]) / 【练习5-5】 有一个3×4的矩阵,求所有元素中的最大值。 解: fmax(int arr[ ][4]) /*定义fmax函数,二维数组arr作形参*/ { int i,j,k,max ; max=arr[0][0]; for( i=0;i<3;i++) for(j=0;j<4;j++) if(arr[i][j]>max) max=arr[i][j]; return(max); } 返回到本章目录
main() { int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}}; /. 定义二维数组a main() { int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}}; /*定义二维数组a*/ printf("max value is %d\n",fmax(a)); /*调用fmax函数,二维数组a作实参*/ } 程序运行结果如下: 返回到本章目录
【练习5-6】设计函数even,验证任意偶数为两个素数之和并输出这两个素数。 解:程序的算法分析如下: (1)在main函数中,首先从键盘输入一个不小于4的偶数n,然后调用函数even将n拆分两个素数的和,并输出这两个素数。 (2)函数prime的功能是判断参数n是否为素数。如果参数n为素数,返回1;否则,返回0。 (3)函数even的功能是将参数n拆分为两个素数的和,并输出两个素数。 返回到本章目录
② 判断i是否是素数。若是,执行步骤③;若不是,执行步骤⑤。 ③ 判断n-i是否是素数。若是,执行步骤④;若不是,执行步骤⑤。 该函数的算法描述如下: ① i初值为2。 ② 判断i是否是素数。若是,执行步骤③;若不是,执行步骤⑤。 ③ 判断n-i是否是素数。若是,执行步骤④;若不是,执行步骤⑤。 ④ 输出结果,返回调用函数。 ⑤ 使i增1。 ⑥ 重复执行步骤②。 返回到本章目录
源程序如下: #include "math.h" int prime(int n); void even(int n ); main() { int n; printf("\ninput n:"); do { scanf("%d",&n); }while(n>=4&&n%2!=0); even(n); } void even (int n) { int i=2; 返回到本章目录
{ if(prime(i)&&prime(n-i)) { printf("\n%d=%d+%d\n",n,i,n-i ); return; /*结束even函数的执行,返回调用函数*/ } i++; }while( i<=n/2 ); int prime(int n) { int i=2,k; k=sqrt(n); do { if(n%i ==0) return 0; /*结束prime函数的执行,返回调用函数*/ }while(i<=k); return 1; /*结束prime函数的执行,返回调用函数*/ 返回到本章目录
返回到本章目录
程序运行结果如下: 返回到本章目录
本 讲 小 结 这一讲我们学习了函数的嵌套、函数的递归调用、函数的值传递方式和以数组作为函数参数。函数的递归调用为直接或间接调用自己。函数调用时通常以传值的方式传递参数,改动形参变量的值不会影响到对应的实参变量。数组作为函数参数有两种方式:一种是把数组元素(又称下标变量)作为实参使用;另一种是把数组名作为函数的形参使用。 返回到本章目录
第十三讲变量作用域及存储类型、内部函数和外部函数 一、 作用域和生存期 二、局部变量的作用域和存储类型 三、全局变量的作用域、存储类型及多文件程序的运行 四、内部函数与外部函数 练一练 返回到本章目录
一、作用域和生存期 【思考题5-7】分析下列程序的输出结果,注意其中的自动变量。 (一)编写程序代码 main() { int x=1; void prt(void); prt(); x的值为3 printf("(2) x=%d\n",x); x的值为1 } printf("(3) x=%d\n",x); void prt(void) { int x=5; printf("(1) x=%d\n", x); x的值为5 返回到本章目录
(1)该程序中定义了3个局部变量,名字均为x。因为它们的作用域互不相同,所以3个同名变量互不干扰。 (二)程序分析 (1)该程序中定义了3个局部变量,名字均为x。因为它们的作用域互不相同,所以3个同名变量互不干扰。 (2)初值为1的变量x和初值为3的变量x的作用域嵌套,在初值为3的变量x的作用域内,初值为1的变量x无效。 (3)初值为5的变量x,只在prt函数中有效,而在main函数中无效。 (三)调试运行程序 程序运行结果如下: 返回到本章目录
在第二章中我们学过,变量定义的一般格式为: 其中类型标识符说明变量的数据类型,如整型、实型等。而C语言的数据有四种存储类型,分别由四个关键字(称为存储类型标识符)表示:auto(自动)、extern(外部)、static(静态)和register(寄存器)。例如,在前面的程序中所使用的变量,它们的存储类型均为auto类型。 存储类型标识符 类型标识符 变量名; 返回到本章目录
1.变量的作用域 在C语言中,由用户名命名的标识符都有一个有效的作用域,所谓标识符的“作用域”是指程序中的某一部分,在这一部分中,该标识符是有定义的,可以被C语言编译程序和连接程序所识别。 C程序中每个变量都有自己作用域。例如,在一个函数内定义的变量,不能在其他函数中使用。变量的作用域与其定义语句在程序中出现的位置有着直接的关系。注意,在同一个作用域内,不允许有同名的变量出现,而在不同的作用域内,允许有同名的变量出现。 返回到本章目录
依据变量作用域的不同,C语言变量可以分为局部变量和全局变量两大类。在函数内部或复合语句内部定义的变量,称为局部变量。函数的形参也属于局部变量。在函数外部定义的变量,称为全局变量。有时将局部变量称为内部变量,全局变量称为外部变量。 返回到本章目录
2.变量的生存期 C程序占用的内存空间通常分为三部分:程序区、静态存储区和动态存储区。其中程序区中存放的是可执行程序的机器指令;静态存储区中存放的是需要占用固定存储单元的数据;动态存储区中存放的是不需要占用固定存储单元的数据。C语言允许程序员在静态存储区、动态存储区、寄存器中开辟变量的存储空间。 返回到本章目录
所谓变量的生存期是指变量值在程序运行过程中的存在时间。C语言变量的生存期可以分为静态生存期和动态生存期。存放在静态存储区的变量具有静态生存期;存放在动态存储区的变量具有动态生存期,即其生存期从变量被定义开始,到所在复合语句运行结束时为止。 返回到本章目录
二、局部变量的作用域和存储类型 思考题5-8:分析下列程序的输出结果,注意其中的静态局部变量和自动变量中值的变化。 void test(); main() { test(); test(); } void test() { int x=0; static int y=0; x++; y++; printf("x=%d,y=%d\n",x,y); 返回到本章目录
(二)程序分析 (1)x是自动变量,每调用一次test函数,x都被动态分配存储空间(调用开始时分配空间,调用结束后回收空间),赋初值均为0,这样,函数test连续调用3次,输出变量x的值均是x=1。 (2)y是静态局部变量,第一次调用test函数时,系统就为变量y分配存储空间,并赋初值为0,调用结束后,变量y的存储空间不被回收。第二次和第三次调用test函数时,变量y的值就可以被传递或继承。这样,函数test连续调用了3次输出的变量y的值分别是y=1,y=2,y=3。 返回到本章目录
(三)调试运行程序 程序运行结果如下: 返回到本章目录
3.局部变量的定义 定义于函数内部或复合语句内部的变量均称为局部变量。其作用域为复合语句作用域,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。 局部变量定义时,可以使用auto、static和register三种存储类型符。 复合语句作用域的范围是从变量定义处开始,到复合语句的结束处为止。 返回到本章目录
int f1(int a) /. 函数f1. / { int b,c; /. a、b、c作用域:仅限于函数f1()中 int f1(int a) /*函数f1*/ { int b,c; /*a、b、c作用域:仅限于函数f1()中*/ } char f2(int x) /*函数f2*/ { int y,z; /*x、y、z作用域:仅限于函数f2()中*/ main() /*主函数main*/ { int m,n; /*m、n作用域:仅限于函数main()中*/ 返回到本章目录
(3)形式参数也是局部变量,其他函数不能调用。 说明: (1)主函数main中定义的变量m、n也只在主函数中有效,而不因为是在主函数中定义的就在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。 (2)不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。例如,在f1函数中定义了变量b和c,倘若在f2中也定义变量b和c,它们在内存中占不同的单元,互不混淆。 (3)形式参数也是局部变量,其他函数不能调用。 (4)在一个函数内部,可以在复合语句中定义变量,这些变量只在复合语句中有效,这些复合语句也可称为“分程序”或“程序块”。 返回到本章目录
注意: 函数体(或复合语句)中局部变量的定义语句必须放在全部可执行语句之前。 使用局部变量需要注意的问题如下: (1)在一个函数内部不能使用其他函数内部定义的局部变量。 (2)在不同函数内(或不同的复合语句内)可以定义同名的局部变量,它们代表不同的对象,互不干扰。 (3)在嵌套的复合语句内,如果内层与外层有同名的局部变量,则在内层范围内只有内层的局部变量有效,外层的局部变量在此范围内无效。 返回到本章目录
4.自动变量(auto) 自动变量:用auto定义的变量称为自动变量,常常缺省auto。如果局部变量未用任何关键字进行存储类型说明,默认为自动变量。定义格式如下: 返回到本章目录
自动变量具有动态生存期,即其生存期从变量被定义开始,到所在复合语句运行结束时为止。 (1)在函数中定义的自动变量只在该函数内有效,函数被调用时分配存储空间,调用结束就释放。在复合语句中定义的自动变量只在该复合语句中有效,退出复合语句后,便不能再使用,否则将引起错误。 (2)定义但并不初始化,则其值是不确定的。如果初始化,则赋初值操作是在调用时进行的,且每次调用都要重新赋一次初值。 返回到本章目录
(3)由于自动变量的作用域和生存期都局限于定义它的个体内(函数或复合语句),因此不同的个体中允许使用同名的变量而不会混淆。即使在函数内定义的自动变量,也可与该函数内部的复合语句中定义的自动变量同名。 注意: 系统不会混淆,并不意味着人也不会混淆,所以应尽量少用同名自动变量! 返回到本章目录
5.寄存器变量(register) 用register定义的变量是一种特殊的自动变量,称为寄存器变量。这种变量建议编译程序将变量中的数据存放在寄存器中,而不像一般的自动变量那样,占用内存单元,可以大大提高变量的存取速度。 一般情况下,变量的值都是存储在内存中的。为提高执行效率,C语言允许将局部变量的值存放到寄存器中,这种变量就称为寄存器变量。 返回到本章目录
定义格式如下: (1)只有局部变量才能定义成寄存器变量,全局变量不能。 (2)对寄存器变量的实际处理随系统而异。例如,计算机上的MSC和TC 将寄存器变量实际当做自动变量处理。 (3)允许使用的寄存器数目是有限的,不能定义任意多个寄存器变量。 register 类型标识符 变量表; 返回到本章目录
注意: 使用register定义局部变量,只是一种请求或建议,当没有足够的寄存器来存放指定的变量,或编译程序认为指定的变量不适合放在寄存器中时,将自动按auto变量来处理。 返回到本章目录
6.静态局部变量(staic) 在函数体(或复合语句)内部,用static来定义一个变量时,可以称该变量为静态局部变量。定义格式如下: static 类型标识符 内部变量表; (1)静态局部变量属于静态存储。在程序执行过程中,即使所在函数调用结束也不释放。换句话说,在程序执行期间,静态局部变量始终存在,但其他函数是不能引用它们的。 static 类型标识符 内部变量表; 返回到本章目录
(2)定义但并不初始化,则自动赋以“0”(整型和实型)或‘\0’(字符型),且每次调用它们所在的函数时,不再重新赋初值,只是保留上次调用结束时的值。 (3)何时使用静态局部变量: ① 需要保留函数上一次调用结束时的值; ② 变量只被引用而不改变其值。 返回到本章目录
三、全局变量的作用域、存储类型及多文件程序的运行 思考题5-9:分析下列程序的输出结果,注意其中的自动变量、静态全局变量和全局变量,并注意多文件程序的编辑及运行方法。 项目文件ff.prj的内容如下: f1.c f2.c f3.c f4.c 返回到本章目录
void fun1(),fun2(),fun3(); main() { a=35; fun1(); 文件f1.c中的内容如下所示: int a; /*全局变量a的定义*/ void fun1(),fun2(),fun3(); main() { a=35; fun1(); printf("main():a=%d\n",a); fun2(); fun3(); } 返回到本章目录
static int a; /*静态全局变量a的定义*/ void fun1() { a=27; 文件f2.c中的内容如下所示: static int a; /*静态全局变量a的定义*/ void fun1() { a=27; printf("fun1():a(static)=%d\n",a); } 文件f3.c中的内容如下所示: void fun2() { int a=16; printf("fun2():a(auto)=%d\n",a); 文件f4.c中的内容如下所示: extern int a; /*全局变量a的说明*/ void fun3() { a=48; printf("fun3():a(extern)=%d\n",a); 返回到本章目录
(2)源文件f2.c中定义的全局变量a,使用了static存储类型符,即为静态全局变量,具有文件作用域,所以程序中的其余源文件不能使用它。 (一)程序分析 (1)源文件f1.c中定义的全局变量a,由于没有使用static存储类型符,可以在源文件f2.c,f3.c,f4.c中使用。该程序的源文件f4.c中要使用全局变量a,所以应在f4.c文件的首部,用extern关键字对全局变量a进行说明。 (2)源文件f2.c中定义的全局变量a,使用了static存储类型符,即为静态全局变量,具有文件作用域,所以程序中的其余源文件不能使用它。 (3)在源文件f3.c中,函数fun2定义的变量a是局部变量,其作用域是从定义处开始,到所在的复合语句结束,即程序中的所有其他函数都不能使用它。 返回到本章目录
(二)调试运行程序 程序运行结果如下: 返回到本章目录
7.使用turbo C集成环境运行多文件程序 【思考题5-9】的程序,应该如何进行呢?它包含4个文件。 (1)先后输入并编辑4个文件,并分别以文件名file1.c、file2.c、file3.c、file4.c存储在磁盘上。 (2)在编译状态下,建立一个“项目文件”,它不包括任何程序语句,而只包括组成程序的所有文件名,即: file1.c file2.c file3.c file4.c (说明:扩展名.c可以省略,4个文件顺序任意,也可以连续写在同一行上。) 返回到本章目录
注意:如果这些源文件不在当前目录下,应指出路径。 (3)将以上内容存盘,文件名自定,但扩展名必须为 注意:如果这些源文件不在当前目录下,应指出路径。 (3)将以上内容存盘,文件名自定,但扩展名必须为.prj(表示为project文件)。【思考题5-9】中项目文件名为ff.prj。在Turbo C主菜单中选择Project菜单,按回车键后出现下拉菜单,找到其中的Project Name项并按回车键,屏幕上会出现一个对话框,询问项目文件名,如图所示。 “Project Name”对话框 返回到本章目录
输入项目文件名ff. prj以代替. prj,此时子菜单中的Project Name后面会显示出项目文件名ff 输入项目文件名ff.prj以代替*.prj,此时子菜单中的Project Name后面会显示出项目文件名ff.prj,表示当前准备编译的是ff.prj中包括的文件。 (4)按F9功能键,进行编译连接,系统先后将4个文件翻译成目标文件,并把它们连接为一个可执行文件ff.exe(文件名主干与项目文件相同)。 (5)按Ctrl+F9快捷键,即可运行可执行文件ff.exe。 返回到本章目录
8.全局变量说明 全局变量是在函数外部任意位置上定义的变量,它的作用域是从变量定义的位置开始,到整个源文件结束。 当一个全局变量定义于文件首部时,该文件中的所有函数对全局变量的操作都是有效的。如果前面的函数改变了全局变量的值,那么后面的函数引用该变量时,得到的就是被改变后的值。 全局变量具有静态生存期,即变量值存在于程序的整个运行期间,如果在定义它的时候未进行初始化,则自动地被初始化为0。 返回到本章目录
9.不用static存储类型符定义的全局变量 如果定义全局变量时没有使用static,则该变量具有程序作用域,即该变量不但可以被其所在的源文件中的函数访问,也可以被同一程序的其他源文件中的函数访问。在后一种情况下,如果程序的某一个源文件中的函数要访问该变量,必须在访问该变量的源文件中提前用extern对该变量进行说明,称为外部说明。 外部说明的作用是声明该变量已在其他源文件中定义,通知编译程序不必再为它开辟存储单元。例如在【思考题5-9】中的文件f1.c中的全局变量定义语句:int a;定义了一个全局变量a,可以在该程序的各位置使用。
10.全局变量的说明语句 如果全局变量和访问它的函数是在同一个文件中定义的,且全局变量定义于要访问它的函数之后,也必须在访问该变量之前用extern对该全局变量进行说明,这时其作用域从extern说明处起,延伸到该函数末尾。所以为了处理的方便,全局变量通常定义于文件首部,位于所有函数定义之前。 如果在其他文件中要使用该全局变量,则需要在使用的文件首部说明一下该变量,说明语句的格式为: extern 类型标识符 内部变量表; 返回到本章目录
例如在【思考题5-9】中的文件f1. c中定义的全局变量a,在其他文件中要使用变量a,需要在使用的文件首部说明它为外部变量。在文件f4 例如在【思考题5-9】中的文件f1.c中定义的全局变量a,在其他文件中要使用变量a,需要在使用的文件首部说明它为外部变量。在文件f4.c中首部的全局变量说明语句:extern int a;,就说明a为一个外部全局变量(注意是说明,不是定义),变量a是在其他文件中被定义的。 注意: 外部全局变量说明语句的功能是说明一个在其他文件中已定义的一个全局变量,而不是重新定义一个全局变量。 返回到本章目录
11.使用static定义的静态全局变量 如果定义全局变量时使用static存储类型符,此变量可称做静态全局变量。静态全局变量具有文件作用域,只限于本源文件使用,不能被其他源文件使用,即只允许该变量所在的源文件中的函数访问,而不允许其他源文件中的函数访问。所以【思考题5-9】中文件f2.c中的语句:static int a;就定义了一个静态全局变量a,所以该变量只能在本文件内部使用。 返回到本章目录
四、内部函数与外部函数 思考题5-10:分析下列程序的输出结果,注意其中的外部函数说明语句。 (1)文件mainf.c中代码为: { extern input(char str[50]); /*声明在本函数中将要调用的在其他文件中定义的3个函数*/ extern del(char str[],char ch); extern output(char str[50]); char c; char str[50]; input(str); scanf("%c",&c); del(str,c); output(str); } 返回到本章目录
#include <stdio.h> /*定义外部函数*/ input(char str[50]) { gets(str);} (2)文件subf1.c中代码为: #include <stdio.h> /*定义外部函数*/ input(char str[50]) { gets(str);} (3)文件subf2.c中代码为: del(char str[],char ch) { int i,j; for(i=j=0;str[i]!='\0';i++) if(str[i]!=ch) str[j++]=str[i]; str[j]= '\0'; } (4)文件subf3.c中代码为: output(char str[ ]) { printf("%s",str);} 返回到本章目录
整个程序由4个文件组成,每个文件包含一个函数。 (一)程序分析 整个程序由4个文件组成,每个文件包含一个函数。 主函数是主控函数,除声明部分外,由4个函数调用语句组成。其中scanf是库函数,另外3个是用户自己定义的函数。函数del的作用是根据给定的字符串str和准备删除的字符ch,对str字符串作删除处理。 算法如下:对str数组的字符逐个检查,如果不是被删除的字符就将它存放在数组中。从str[0]开始逐个检查数组元素值是否等于指定要删除的字符,若不是就留在数组中,若是就不保留。 返回到本章目录
应该使str[0]赋给str[0],str[1]赋给str[1],str[2]赋给str[2],str[3]赋给str[3],str[5]赋给str[4],……请注意分析如何控制i和j的变化,以便使被删除的字符不保留在原数组中。我们只用一个数组,只把不被删除的字符保留下来,由于i总是大于或等于j,因此最后保留下来的字符不会覆盖未被检测处理的字符,最后将字符'\0'也复制到被保留的字符后面。 程序中3个函数都定义为外部函数。在main函数中用extern声明在main函数中用到的input、del、output是在其他文件中定义的外部函数。 返回到本章目录
abcdef (abcdef为输入的字符串) d (d为要删除的字符) abcefg (输出已删去指定字符的字符串的结果) (二)调试运行程序 程序运行结果如下: 其中: abcdef (abcdef为输入的字符串) d (d为要删除的字符) abcefg (输出已删去指定字符的字符串的结果) 返回到本章目录
12.内部函数 定义一个函数时,若在函数类型前加上存储类型符static时,则称此函数为内部函数或静态函数。内部函数定义一般格式: 内部函数只限于本文件的其他函数调用它,而不允许其他文件中的函数对它进行调用,所以内部函数的作用域是文件级的。 static 类型标识符 函数名(函数参数表) {…} 返回到本章目录
static int max(int x,int y) {return(x>=y?x:y;);} 例如: static int max(int x,int y) {return(x>=y?x:y;);} 则max函数只允许被同一源文件中的函数调用。在多人分工合作下,这种对函数作用域的限制很有用。同一系统中的由不同人员设计的文件中即使存在着同名函数,也互不干扰,因此设计人员可以放心地为函数取自己喜欢的名字。 返回到本章目录
13.外部函数 函数在本质上都具有外部性质,除了内部函数(静态函数)之外,其余的函数都可以被同一程序的其他源文件调用。当定义一个函数时,若在函数返回值的类型前加上存储类型符extern时,称此函数为外部函数。关键字extern可以省略。 外部函数定义的一般格式如下: [extern] 类型标识符 函数名(函数参数表) {…} 返回到本章目录
使用extern声明就能够在一个文件中调用其他文件中定义的函数,或者说把该函数的作用域扩展到本文件。例如【思考题5-10】中的语句: extern input(char str[50]); extern del(char str[],char ch); extern output(char str[50]); 就是说明这3个函数为其他文件中定义的外部函数,在该文件中要使用这3个函数,所以就要在主函数开始处说明它们。 返回到本章目录
练一练 【练习5-7】分析下列程序运行结果: 源程序如下: int n=1; main() { static int x=5; int y; y=n; printf("MAIN:x=%2d y=%2d n=%2d\n",x,y,n); func(); 返回到本章目录
printf("MAIN:x=%2d y=%2d n=%2d\n",x,y,n); func(); } func() { static int x=4; int y=10; x=x+2; n=n+10; y=y+n; printf("FUNC:x=%2d y=%2d n=%2d\n",x,y,n); 返回到本章目录
解:程序说明:首先,main函数中的静态变量x被赋初值为5,y赋值为全局变量n的值1。输出3个变量,结果为:x=5,y=1,n=1。 然后调用func()函数,在该函数中,局部静态变量x被赋值为4;y被赋值为10,然后x加上2变为6;n为全局变量,在主函数中为1,则现在加上10变成11;y又被加上n(为11)后值为21。再输出3个变量,结果为:x=6,y=21,n=11。 返回到主函数中,输出主函数中的3个变量,上次主函数中的3个值为x=5,y=1,n=1。而x为局部静态变量,则还为5;而y是局部变量,也是原来值1;但n是全局变量,在func()函数中变成11,则回到主函数中也为11。所以输出结果为:x=5,y=1,n=11。 返回到本章目录
再次调用func()函数,局部静态变量x上次的值是6;y不是静态变量,所以重新被赋值为10;全局变量n值还是主函数中的11。让x的值加上2变成了8,n的值加上10变成了21,y的值加上n值为10+21等于31。所以输出结果为:x=8,y=31,n=21,程序运行结束。程序运行的结果如下: 返回到本章目录
本讲小结 这一讲我们学习了变量的作用域和生存期,局部变量、全局变量的定义及其存储类型;函数的作用域,即用static修饰的函数只允许被本文件中的函数调用,而没有用static修饰的函数则允许同一程序任何文件中的函数使用;以及如何使用Turbo C集成环境运行多个C语言源程序。 返回到本章目录
本章小结 本章着重介绍了函数的定义、函数的调用和函数原型;递归函数的定义及调用;函数与函数之间的数据传递。同时还介绍了模块化的程序设计及C源程序基本结构;变量及函数的作用域和存储类型。 C语言函数有两种,一种是由系统提供的标准函数,这种函数用户可以直接使用;另一种是用户自定义的函数,这种函数用户必须先定义后使用。在对函数进行定义时,有返回值的函数必须用“return表达式;”结束函数的运行;若函数是以“return;”结束运行的,说明该函数是无返回值函数。 函数原型是提供函数调用接口信息的说明形式,其格式就是在函数定义格式的基础上去掉了函数体,可见函数定义涵盖函数原型,同样能提供有关的接口信息。 返回到本章目录
本章小结 函数可以作为表达式调用,也可以作为语句调用。函数调用时通常以传值的方式传递参数,改动形参变量的值不会影响对应实参变量。定义于函数外的变量称为全局变量,用static修饰的全局变量只允许被本文件中的函数访问,而没有用static修饰的全局变量则允许同一程序任何文件中的函数访问。定义于函数内的变量称为局部变量,只允许定义该变量的复合语句内的语句使用。用static修饰的局部变量称为静态局部变量,可用于在本次调用与下次调用之间传递数据。 用static修饰的函数只允许被本文件中的函数调用,而没有用static修饰的函数则允许同一程序任何文件中的函数调用。 返回到本章目录