第 6章 函 数第 6 章 函数
6.1 概述
6.2 函数的定义和调用
6.3 函数的嵌套调用
6.4 函数的递归调用
6.5 变量的作用域和存储类别
6.6 小 结第 6章 函 数
6.1 概述
C程序是由一组或是变量或是函数的外部对象组成的。 函数是一个自我包含的完成一定相关功能的执行代码段。它完成一个特定的任务并可选择是否将一个值返回调用程序。在C语言中,子程序被称为函数,一个C程序一般由多个函数组成,其中必须有且仅有一个名为 main的主函数,其余为被 main函数或其它函数调用的函数,
无论 main函数位于程序中什么位置,C程序总是从 main函数开始执行。 main函数可调用其它函数来实现所需的功能.被 main函数调用的函数分为两类:一类是由系统提供的标准库函数,例如标准输出函数( scanf,printf,… )数学计算函数( sin,fabs,sqrt,… )数据格式转换函数( atoi,atof,sscabf,sprintf,… )字符串处理函数( strlen,strcpy,strcmp,… )和文件读写函数( fread,fwrite,
fopen,… )等。这类函数可以由用户程序直接调用;另类是用户在自己的程序中定义中定义的函数,即需要由用户自己编写的函数。
第 6章 函 数
C程序中的函数具有以下特性:
1.C语言中,程序是由函数来实现的。
函数可分为( 1):标准库函数( 2)用户自定义函数。
2.在 C语言中,至少要有一个 main ( )函数,程序的执行是从 main( )函数开始的。
函数的调用过程如图,f11( )
{…}
f21( )
{…}
f31( )
{…}
f1( )
{…
f11( );
…}
f2( )
{…
f11( );
f22( );
…}
main( )
{…
f1( );

f2( );
…}
第 6章 函 数
3.每个函数都有唯一的名字(如 square),用这个名字,
程序可以转去执行该函数所包括的语句,这种操作称为调用函数。一个函数能被另一个函数调用,如在 main函数中用 square函数,但 main函数不可被其它任何函数调用。
4.一个函数执行一个特定的任务,此任务是程序必须完成的全部操作中一个独立的操作行为,如将一行正文件传送到打印机,将一个数组按数值顺序排列,求一个立方根等等。
5.函数的定义是独立的、封闭的。一个函数的定义应该不受程序其它部分的干预也应不干预其它部分而完成自身的任务。
6.函数能给调用程序返回一个值。在程序调用一个函数时,此函数所包含的语句很会执行,必要时,还能将信息传回给调用程序。
第 6章 函 数由此可以得出:模块化程序设计有如下特点:
( 1)模块相对独立,功能单一,可混合编写也可独立编写调试。
( 2)可集体开发,缩短开发周期。
( 3)开发出的模块,可在不同的应用程序中多次使用,减少重复劳动,提高开发效率。
( 4)测试、更新以模块为单位进行而不会影响其他模块,
一、函数的 定义 与 声明
1、定义,[存储类型 ] [数据类型 ] 函数名(带类型的形参名表)
{ 内部变量说明;
语句;
}
第 6章 函 数
6.2 函数的定义和调用
6.2.1函数定义的一般形式
1.无参函数的定义形式
类型标识符 函数名()
{
说明部分
语句
}
2.有参函数定义的一般形式
类型标识符 函数名(形式参数列表)
{
说明部分
语句
}
3.可以有“空函数”,它的形式为:
类型说明符 函数名()
{ }
第 6章 函 数
【 例 6.1】,
main()
{
printstar(); /*调用 printstar函数 */
print_message(); /*调用 print-message函数 */
printstar(); /*调用 printstar函数 */
}
printstar() /*printstar函数 */
{
printf(" * * * * * * * * * * * * * \n");
}
print_message() /*print-message函数 */
{
printf( "how do you do !\n ");
}
第 6章 函 数
运行结果如下:
* * * * * * * * * * * * *
how do you do !
* * * * * * * * * * * *
该例中的 printstar和 print _message函数都是无参函数。用“类型标识符”指定函数值的类型,既函数回来的值的类型。无参函数一般不需要带回函数值,因此可以不写类型标识符。
第 6章 函 数
【 例 6.2】,求两者中的最大者。
main()
{
int a,b,c;
scanf("%d,%d",&a,&b);
c=max(a,b); /*调用 max函数 */
printf("min=%d\n",c);
}
int max(int x,int y) /*max函数 */
{
int z;
z=x>y?x:y;
return(z);
}
第 6章 函 数
该例是一个求 x和 y二者中大者的函数,x和 y为形式参数,从主调函数把实际参数 a和 b的参数值传递给被调用函数中的形式参数 x和 y。第八行,int x,int y”是对形式参数作类型说明,指定 x和 y为整形。花括弧内是函数体,
它包括说明部分和语句部分。请注意,,int z”必须写在话括弧内,而不能写在花括弧外,也不能将第八、九行合并写成,int x,y,z;”。形式参数的说明应在函数体外。
在函数体的语句中求出 z的值(为 x和 y中大者),return
语句的作用是将 z的值作为函数值带回到主调函数中。
Return后面的括弧中的值作为函数带回的值(或成函数返回值)。在函数定义时已指定 max函数为整形,在函数体中定义 z为整形,二者是一致的,将 z作为函数 max的值带回调用函数。
第 6章 函 数
【 例 6.3】,空函数:
dummy() { }
调用此函数时,什么工作也不做,没有任何实际作用。在主调函数中写上,dummy();”表明“这里调用一个函数”,
而现在这个函数没有起作用,等以后扩充函数功能时补充上。
在程序设计中往往根据需要确定若干模块,分别由一些函数来实现。而在第一阶段只设计最基本的模块,其它一些次要功能或锦上添花的功能则在以后补上。在编写程序的开始阶段,可以在将来准备扩充功能的地方写上一个空函数,函数名取将来 采用的实际函数名(如 merge(),matproduct()、
concatrnate(),shell()等,分别代表合并、矩阵相乘、字符串连接、希尔法排序等),只是这些函数未编好,先占一个位置,以后用一个编好的函数代替它。这样做,程序的结构清楚,可读性好,以后扩充新功能方便,对程序结构影响不大。 空函数在程序设计中常常是有用的。
第 6章 函 数
6.2.2 函数参数和函数的值
1.函数的参数在 调用函数时,大多数情况下,主调用函数和被调用函数之间有数据传递关系。这就是前面提到的有参函数。函数的参数分为形式参数和实际参数,在定义函数时函数后面括弧中的变量名称为“形式参 数” (简称“形参” ),在调用函数时,函数名后面括弧的表达式称为
“实际参数”(简称“实参”。
【 例 6.4】
main( )
{int a,b,c ;
scanf ("%d,%d",&a,&b);
c=max (a,b);
printf ("Max is %d ",c); }
max (int x,int y) / * 定义有参函数 * /
{int z ;
z=x>y?x:y ;
return (z) ; }
第 6章 函 数
a 和 b是 mian函数中定义的变量,函数调用时作为实际参数,而 x和 y是函数 max中的形式参数变量。通过函数调用,使两个函数中的数据发生联系。
关于形参和实参的说明:
1、形参只有当调用时才临时分配存储单元。
2、实参一定要有确定的值,可以是表达式。
3、实参和形参的类型应相同或赋值相容。
参数的传递是通过调用来完成的,
分为按值传递和按址传递。
第 6章 函 数
【 例 6.4】
main( )
{ int a=3,b=5;
void swap( int,int );
swap (a,b);
printf(“a=%d,b=%d\n”,a,
b);
}
void swap (int x,int y)
{ int temp;
temp=x; x=y; y=temp;
printf(“x=%d,y=%d \n”,
x,y);
}
是按值传递的按址传递放在指针里面讲。
Main()函数:
调用 Swap函数传递值
3
5
a
b
3
5
x
y
3
temp
第 6章 函 数
6.2.2 函数参数和函数的值
2.函数的值函数的值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。对函数的值 (或称函数返回值 )有以下一些说明:
1.函数的值只能通过 return语句返回主调函数。 return 语句的一般形式为:
return 表达式;
或者为:
return (表达式 );
2.函数值的类型和函数定义中函数的类型应保持一致。 如果两者不一致,则以函数类型为准,自动进行类型转换。
3.如函数值为整型,在函数定义时可以省去类型说明。
4.不返回函数值的函数,可以明确定义为“空类型”,类型说明符为,void”。
例如:
void s(int n)
{
……
}
第 6章 函 数二,return 语句
return 返回值表达式;
return后面表达式的类型必须和函数定义时函数名前的类型保持一致。
三、函数的调用函数名 ( [实参表 ]);( 如果是有返回值的函数,
则可将调用结果赋值给别的变量,若是无值函数,则不能赋值,只能单独调用;(例 4—1打印若干三角形)
五、形参、实参、参数的传递形参:定义函数的参数。
实参:调用函数时有具体值的参数。
第 6章 函 数
【 例 6.5】,,计算表达式的值
x 2- x+1 (x<0)
x3+x+3 (x>0)
float y (float x )
{ if (x<0) return (x*x- x+1);
else return (x*x*x+x+3);
}
注意:当有多个 return语句时,每个 return语句后面的表达式的类型应相同。
如,func (int n)
{ if (n>10) return (2*x+3);
else return;}
编译时,由于第二个 return语句而给出警告。
说明,1)存储类型(后面讲)
2)数据类型:指函数返回值的类型。 若缺省函数类型,一律按整形处理。
3)函数是平等的兄弟般的关系,它不能嵌套定义即不能在一个函数体内再定义另一个函数。
4)只有自定义函数而没有 main()函数的程序是没有意义的。
y={
第 6章 函 数
2、函数的 声明
[数据类型 ] 函数名 (类型标记符 [形参 ],… );
注意后面的,;,不要丢了。它与定义不同,一个函数一般要经过声明才能使用(就好象变量一样必须先声明才能使用),除非它在调用它的前面定义。 只有当返回的数据类型为 int,数据类型才可省略。
如,main( )
{…
double new_style ( int,double ); /*函数声明 */
… }
Double new_style (int a,double x) /*函数定义 */
{… }
第 6章 函 数函数调用函数声明函数定义,其位置可以在 main()前,
也可以在 main()后
Main()
{ float x,y;
float y(float);
scanf(“%f”,&x);
Printf(“y=%8.2f\n”,y(x));
}
float y (float x )
{ if (x<0)
return (x*x- x+1);
else
return (x*x*x+x+3);
}
第 6章 函 数
6.2.3 函数的调用
c语言中调用函数时直接使用函数名和实参的方法,也就是将要赋给被调用函数的参量,按该函数说明的参数形式传递过去,然后进入子函数运行,运行结束后再按子函数规定的数据类型返回一个值给调用函数。
1.函数调用的一般形式
函数调用的一般形式为:
函数名(实参表列);
如果是调用无参函数,则实参表列可以没有,但括弧不能省略;如果实参表列包含多个实参则各参数间用逗号隔开。实参与形参的个数应相等,类型应一致。形参与实参按顺序对应,一一传递数据。但实参表求值的顺序并不是确定的,有的系统按自左向右顺序求实参的值,有的系统按自右向左的顺序求实参的值。
第 6章 函 数
【 例 6.6】
main()
{ int i=2,p; p=f(i,++i);
printf("%d",p); }
int f(int a,int b)
{int c;
if(a>b) c=1;
else f(a==b) c=0;
else c=-1;
return ( c ) ; }
程序运行结果如下,0
如果按自左向右顺序求实参的值,则函数调用相当于 f( 2,3),程序运行应得结果为,-1”。因此为了避免这种不同理解情况,按自左向右顺序求实参值的,可以改写为:
j=i ; k=++i ; p=f(j,k) ;
而按自右向左顺序求实参值的,可以改写为:
j=++i ; p=(j,j) ;
第 6章 函 数
二、函数调用的方式和条件
函数调用的一般形式为:函数名 (实参 1,实参 2,…,实参 n)
( )部分称为实参表,实参可以常量、变量或表达试,有多个实参时,相应间用逗号隔开。实参和形参应在数目、次序和类型上一 致。对于无参数的函数,调用时实参表为空,但( )不能省。
函数调用在程序中起一个表达式或一个语句的作用。对于有返回值的函数,函数调用一般作为表达式出现,即凡程序中允许出现表达式的位置上均可出现函数调用;也可以作为语句(即表达式语 句)出现。对于无返回值函数的调用,只能以语句形式出现。
例如:
( 1) getch( ); getch 函数调用作为语句出现。
( 2) c=getchar( ); getchar函数调用组表达式出现(赋值表达式的右操作数)。
( 3) while((c=getche( ))!='?' ) ; getche函数调用作为 putchar函数调用的实参(表达式)出现,putchar函数调用作为关系表达式的左操作数(表达式)出现。
( 4) while((c=getche( ))!='?' )putchar(c);
putchar函数调用作为 while语句的循环体(语句)出现。
第 6章 函 数
函数调用的一般过程为:
( 1)主调函数在执行过程中,一旦遇到好书调用,系统首先计算实参表达式的值并为每个形参分配存贮单元,然后把实参值复制到(送到或存入)对应形参的存贮单元中。实参与形参按位置一一对应。
( 2)将控制转移到被调用的函数,执行其函数体内的语句。
( 3)当执行 return语句或到达函数体末尾时,控制返回到调用处。如果有返回值,同时回送一个值。然后从函数调用点继续执行主调函数后面的操作。
3.形参于实参的数值传递
函数调用时将实参传给形参称为参数传递。 C语言中,参数的传递方式是“单向值传递”,形参和实参变量各自有不同的存贮单元,被低哦啊用函数中形参变量值的变化不会影响实参变量的值。
第 6章 函 数
【 例 6.7】 形参于实参的数值传递 。
void swap(int x,int y)
{int z;
z=x; x=y; y=z; }
main( )
{int a,b;
a=8; b=10;
swap(a,b);
printf("a=%d\tb=%d\n"a,b); }
运行结果:
a=8 b=10
8
8 10
10
a
x
b
y
10
8 10
8
a
x
b
y
z
第 6章 函 数
在参数传递时有三个问题需要注意:
1.一致性,实参的数目和类型应该与形参保持一致。
2,C语言中可以定义参数数目可变的函数。
例如,printf函数调用的一般形式为:
printf(格式字符串,参数 1,参数 2,…… );
其中第一个参数(格式字符串)是必须的,调用时系统根据第一个参数中的格式说明项的树木和格式说明字符来确定其余 参数的树木和类型,例如:
printf("x=%d y=%f",x,y);
因为第一个参数中有两个格式说明项,分别为 %d和 %f,因此确定 printf在本次调用中还有两个输出参数,分别为整数和浮点数。
3.函数调用时,每一实参为一表达式,实参与实参间的逗号是分隔符,不是顺序求值运算符,它不保证参数的求值顺序按从左至右进行,参数的求值顺序由具体系统确定。多数编译程序在 计算参数值时按从右至左的顺序进行。
第 6章 函 数
【 例 6.8】 参数的求值顺序:
void main(void)
{ int x=0;
printf("x=%d \n",x);
printf("x++=%d x++=%d",x++,x++);
printf("x=%d\n",x); }
执行时输出:
x=0
x++=1 x++=0
x=2
在该程序的第二个 printf语句中,右边的 x++先求值,
左边的 x++后求值,因此右边的 x++的输出为 0,左边的为 1。
第 6章 函 数
6.3 函数的嵌套调用
C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,
不存在上一级函数和下一级函数的问题。 但是
C语言允许在一个函数的定义中出现对另一个函数的调用。 这样就出现了函数的嵌套调用。
即在被调函数中又调用其它函数。 这与其它语言的子程序嵌套的情形是类似的。函数的嵌套调用是指,在执行被调用函数时,被调用函数又调用了其它函数。
第 6章 函 数
【 例 6.9】 计算 s=1k+2k+3k+……+N k
#define K 4
#define N 5
long f1(int n,int k) /*计算 n的 k次方 */
{long power=n; int i;
for(i=1; i<k; i++) power *= n;
return power; }
long f2(int n,int k) /*计算 1到 n的 k次方之累加和 */
{ long sum=0; int i;
for(i=1; i<=n; i++) sum += f1(i,k);
return sum; }
main()
{ printf("Sum of %d powers of integers from 1 to %d = ",K,N);
printf("%d\n",f2(N,K)); getch(); }
第 6章 函 数
6.4 函数的递归调用
一个函数在它的函数体内调用它自身称为递归调用。
这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身。 每调用一次就进入新的一层。
【 例 6.8】 求 n!的值,
#include,stdio.h”
long factor ( int );
main( )
{ int i;
scanf(“%d,,&i);
printf(“%d != % ld \n”,i,factor(i));
}
第 6章 函 数
long factor (int x)
{ if (x==1 | | x==0) return (1);
else
return ( x*factor( x- 1));
}
调用 factor(4)
Main()函数 Factor(4)
求 4*factor(3)
Factor(3)
求 3*factor(2)
Factor(2)
求 2*factor(1)
Factor(1)
……..
返回值 1
返回值 2返回值 6返回值 24输出Factor(5)=24
第 6章 函 数
6.5变量的作用域和存储类别
在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量,C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。 C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量
6.5.1局部变量和全局变量第 6章 函 数
a,b的有效范围
C的有效范围全局变量和局部变量
1、全局变量:
是在函数之外定义的变量,它的有效范围是从定义它的地方开始,到整个程序结束的任何地方。
2、局部变量:
在一个函数内定义的变量,只在本函数内有效,这种变量就是局部变量。
Main()
{int a,b;
… …
{int c;
c=a+b;
… …
}
… …
}
第 6章 函 数所以在 main()里不能对变量 C进行引用。
变量 C是再函数 fun
内部定义的,它就只再该函数内部有效。
【 例 6.9 】
fun(int a,int b)
{int c;
c=a>b?a:b;
return c;
}
main()
{int a=7,b=10;
fun(a,b);
printf("max=%d\n",c);
}
int c;
c=fun(a,b);
第 6章 函 数在函数 fun()中,虽然没有定义变量 a,b,但由于它们定义在程序的最前面,
是全程变量,凡是在定义该变量的后面定义的函数均可以引用它们。
#include,stdio.h”
int a=3,b=5; /*在函数体外定义的变量 */
main( )
{ void fun( );
printf(,a= %d,b=%d \n,,a,b);
fun ( );
printf(,a= %d,b=%d \n,,a,b);
}
void fun (void)
{
int c ;
c=a;
a=b;
b=c;
}
【 例 6.1 0】 交换两个变量的值第 6章 函 数用 extern声明全局变量,用以扩充外部变量的作用域。
在一个文件内声明外部变量:
main( )

void gx( )

int x,y;
void gy( )

还可用它在不同的程序中使用,如 在 file1.c 中引用 file2.c
中定义的变量 x,y。
extern int x,y;
x,y原作用域
x,y新的作用域第 6章 函 数形参实参
#define N 10
main()
{float score[N],avg;
float average(float arr[N]);
int i;
printf("Please input %d score:\n",N);
for(i=0;i<N;i++)
scanf("%f",&score[i]);
avg=average(score);
printf("Average score is %7.2f\n",avg);
}
float average(float arr[N])
{int i;
float sum=0.0,aver;
for(i=0;i<N;i++)
sum=sum+arr[i];
aver=sum/N;
return(aver);
}
形参和实参都用数组名第 6章 函 数
Register变量二,register变量(寄存器变量)
与 auto变量完全相同,只是速度快,存储在 CPU的寄存器中。
#include,stdio.h”
void m_table(void)
{ register int i,j;
for (i=1 ; i<=9 ; i++)
for (j=1 ; j<=i ; j++)
{ printf(“%d * %d =%d,,j,i,j*i);
putchar (( I==j) \n?,? \t? );
}
}
Main()
{ void m_table( );
m_table( );
}
第 6章 函 数 Static变量三,static 变量(静态变量)
auto变量是在程序运行过程中建立的,是动态建立、动态撤消。而 静态变量是在程序一开始就建立的,不撤消直到程序结束。因而其值具有可继承性,但它只能在本 程序 内使用。
结果为,1,2,3
一般需要保留函数上一次的调用结果时,就定义成静态变量。
Void incr(void)
{
static int x=0;
x++;
printf(“%d \n,,x );
}
例 L4_6_1.c
main( )
{ void incr(void);
incr( );
incr( );
incr( );
}
第 6章 函 数上一次的结果,即 n-1的阶乘对静态局部变量赋初值时在编译时进行的,也就是整个程序的运行过程中,它已经有初值,
以后每次调用函数时不再重新赋初值而保留了上一次的运行结果。
Static变量
例 4_7.c:打印 1到 5的阶乘值
int fac(int n)
{static int f=1;
f=f*n;
return(f);
}
main()
{int i;
for(i=1;i<=5;i++)
printf("%d!=%d\n",i,fac(i));
}
第 6章 函 数
#include "stdio.h"
int x,y;
one()
{int a,b;
a=25,b=10;
x=a-b;y=a+b;
return;}
main()
{int a,b;
a=9,b=5; x=a+b;
y=a-b;
one();
printf("%d,%d\n",x,y);}
#include "stdio.h"
int x,y;
two()
{int a=25,b=10;
int x,y;
a=25,b=10;
x=a-b;y=a+b;
return;}
main()
{int a=9,b=5;
x=a+b;y=a-b;
two();
printf("%d,%d\n",x,y);}
第 6章 函 数
#include "stdio.h"
three()
{extern int x,y;
int a=25,b=10;
x=a-b; y=a+b;
return;}
int x,y;
main()
{int a=9,b=5;
x=a+b;y=a-b;
three();
printf("%d,%d\n",x,y);}
第 6章 函 数
Extern变量
Extern变量(全局变量)
1,外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生期。
2,当一个源程序由若干个源文件组成时,在一个源文件中定义的外部变量在其它的源文件中也有效。
int a,b; /*外部变量定义 */
char c; /*外部变量定义 */
main()
{
……
}
F2.C
extern int a,b; /*外部变量说明 */
extern char c; /*外部变量说明 */
func (int x,y)
{
……
}
第 6章 函 数
6.6 小 结
1.C程序中函数的数目实际上是不限的,如果说有什么限制的话,那就是,一个 C 程序中必须至少有一个函数,
而且其中必须有一个并且仅有一个以 main为名,这个函数称为主函数,整个程序从这个主函数开始执行。
2.C 语言程序一般是由大量的小函数而不是由少量大函数构成的,即所谓“小函数构成大程序”。
3.C语言的一个主要特点是可以建立库函数。