第九章 函数第九章 函数
9.1 模块化程序设计与函数
在 C程序设计中,子程序的作用是用函数来完成的。一个 C程序一般由多个函数组成,其中必须有一个且仅有一个名为 main的主函数,其余为被 main函数或其它函数调用的函数,无论
main函数位于程序中什么位置,C程序总是从
main函数开始执行。 main函数可调用其它函数来实现所需的功能。
第九章 函数
[例 9.1]定义一个函数,用于求两个数中的大数。
main()
{ int max(int n1,int n2); /*声明 max()函数 */
int num,num1,num2;
printf("Input two integer numbers:\n");
scanf("%d,%d",&num1,&num2);
num=max(num1,num2); /*调用 max()函数 */
printf("max=%d\n",num);
}
int max(int n1,int n2) /*定义 max()函数 */
{ return (n1>n2?n1:n2);
}
程序运行情况如下,Input two integer numbers:
12,34↙
max=34
9.1 模块化程序设计与函数第九章 函数
[例 9.2]输出一个文本信息框。
void fun1 ( ) /*定义 fun1()函数 */
{ printf("* * * * * * * * * * * * * *\n");
}
void fun2 ( ) /*定义 fun2()函数 */
{ printf("* How do you do! *\n");
}
main()
{ fun1(); /*调用 fun1()函数 */
fun2(); /*调用 fun2()函数 */
fun1(); /*再次调用 fun1()函数 */
}
程序运行结果如下:
* * * * * * * * * * * * * *
* How do you do! *
* * * * * * * * * * * * * *
9.1 模块化程序设计与函数第九章 函数
9.2 函数的定义与调用一、函数的定义
1.无参函数定义的一般形式:
类型名 函数名 ( )
{ 声明部分;
执行部分;
}
无参函数没有参数,因此,函数首部的
“参数定义表”可以缺省 (但括号不能缺省 )。
第九章 函数
2.有参函数定义的一般形式:
类型名 函数名 (类型名 参数名 1,类型名 参数名 2,...,)
{ 说明部分;
执行部分;
}
有参函数在其参数定义表中定义了所需的每一个参数的类型和名称。每一个参数单独定义,参数定义之间用逗号隔开。
9.2 函数的定义与调用一、函数的定义第九章 函数
自定义函数说明部分定义的参数称为形式参数,简称形参。
主调函数提供的参数称为实际参数,简称实参。
函数若无返回值,则其首部的类型标识符用,void”表示。
9.2 函数的定义与调用一、函数的定义第九章 函数说明:
(1) 除 main()函数外,函数名和形参名都是由用户命名的标识符,要求符合标识符的命名规则。
(2) 函数定义不允许嵌套。在C语言中,所有函数 (包括主函数 main( ))都是平行的。在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。
(3) 可以定义空函数。所谓空函数,是指既无参数又无函数体的函数。
9.2 函数的定义与调用一、函数的定义第九章 函数其一般形式为,void 函数名 ( )
{ }
(4) 在旧版本的C语言中,参数定义表允许放在函数首部的第二行单独指定。
(5) 当一个C源程序由多个函数构成时,必须有一个唯一的 main()函数。 main()函数在源程序中的位置可以任意,程序的执行总是从 main()
函数开始,最终从 main()函数结束。
9.2 函数的定义与调用一、函数的定义第九章 函数
1.形参与实参的参数传递形参在函数首部定义,必须是变量形式,
只能在该函数体内使用。
实参在函数调用表达式中提供,可以是表达式形式。
函数调用时,主调函数把实参的值复制一份,传送给被调用函数的形参变量,从而实现主调函数向被调用函数的参数传递。
9.2 函数的定义与调用二、函数的参数与函数的返回值第九章 函数
9.2 函数的定义与调用二、函数的参数与函数的返回值
[例 9.3]求两数中值较大的一个数。
int max(int x,int y)
{ if(x>y) return x;
else return y;
}
main( )
{ int a,b,c;
scanf(“%d%d”,&a,&b);
c=max(a,b); printf(“max=%d\n”,c);
}
运行结果:
23 45
max=45
第九章 函数关于形参与实参的说明:
(1) 实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此,应预先用赋值、输入等方法,使实参获得确定的值。
(2) 形参变量只有在被调用时,才分配存储单元,调用结束后,即刻释放所分配的存储单元。因此,形参只有在该函数内有效。调用结束,返回主调函数后,则不能再使用该形参变量。
(3) 实参对形参的数据传送是单向的值传递,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。
9.2 函数的定义与调用二、函数的参数与函数的返回值第九章 函数
[例 9.4]分析以下程序能否交换主函数中 a 与 b 的值。
void swap(int x,int y)
{ int t;
t=x; x=y; y=t; }
main()
{ int a=10; b=20;
swap(a,b); printf("a=%d,b=%d\n",a,b);
}
程序运行结果如下:
a=10,b=20
9.2 函数的定义与调用二、函数的参数与函数的返回值第九章 函数
2.函数的返回值与函数类型函数的返回值就是调用函数求得的函数值。
1)函数返回值与 return语句函数的返回值是通过函数中的 return语句来获得的。
格式,return 表达式; 或 return (表达式 );
或 return ;
功能:返回主调函数,并将,表达式,的值带回给主调函数。
9.2 函数的定义与调用二、函数的参数与函数的返回值第九章 函数
2)函数类型函数类型就是函数定义首部的类型名所定义的类型,也就是函数返回值的类型。因此,在定义函数时,无返回值函数的类型定义为 void,有返回值函数的类型应与 return语句中返回值表达式的类型一致。
当有返回值函数的类型定义与 ruturn语句中表达式的类型不一致时,则以函数类型定义为准。
对于数值型数据,系统能自动进行类型转换,否则,按出错处理。如果缺省函数类型定义,则系统一律按整型处理。
9.2 函数的定义与调用二、函数的参数与函数的返回值第九章 函数
1.函数调用的一般形式函数调用的一般形式为:
函数名 ( 实际参数表 )
调用无参函数时,缺省实际参数表,但圆括号不能省略。
实际参数表中的参数可以是常量、变量或表达式。如果实参不止一个,则相邻实参之间用逗号分隔。实参的个数、类型和顺序,应该与被调用函数的形参所要求的个数、类型和顺序一致。
9.2 函数的定义与调用三、函数的调用第九章 函数
2.函数调用的方式
( 1)当为 void类型时,其调用只能单独作为一个语句出现;
( 2)当函数类型不为 void型时,函数的调用可以出现在同类型常量可以出现的任何地方,如:表达式,输出表,实参表中等。但不能出现在输入表和形参表中。
9.2 函数的定义与调用三、函数的调用第九章 函数
[例 9.5]求两数中较大数。
int max(int x,int y)
{ if(x>y) return x;
else return y;
}
main( )
{ int a,b,c;
scanf(“%d%d”,&a,&b);
c=max(a,b); /*printf(“max=%d\n”,max(a,b));
*/ /*调用语句在表达式,或输出表中 */
printf(“max=%d\n”,c);
}
9.2 函数的定义与调用三、函数的调用第九章 函数
[例 9.6]求三个数中较大数。
int max(int x,int y)
{ if(x>y) return x;
else return y;
}
main( )
{ int a,b,c,d;
scanf(“%d%d%d”,&a,&b,&c);
d=max(max(a,b),c); /*调用语句在实参表中 */
printf(“max=%d\n”,d);
}
9.2 函数的定义与调用三、函数的调用第九章 函数
3.对被调用函数的声明
1)对被调用函数的声明
C程序中的一个函数要调用另一个函数时必须具备以下两个条件:
a)被调用函数已经存在。若是库函数,系统已经定义,否则需要用户自己定义。
b)在主调函数中对被调用函数先声明,然后才能调用。
9.2 函数的定义与调用三、函数的调用第九章 函数
2)函数的声明方式类型声明的一般形式为:
类型名 函数名 (类型名,类型名 … );
类型名 函数名 (类型名 参数名,类型名 参数名 2… );
其中,参数的,参数名,可以缺省。
函数原型与函数定义的首部是一致的,各参数的顺序也必须与函数首部定义的一致。但函数声明是单独作为一条说明语句,因此其末尾必须有分号 (; )。
9.2 函数的定义与调用三、函数的调用第九章 函数说明:
(1)C语言规定,在以下三种情况下,可以省去对被调用函数的声明:
如果函数的值是整型或字符型,可以不必声明。
当对被调用函数的定义出现在主调函数之前时,
可以缺省对被调用函数的声明。
如果在所有函数定义之前,在函数外部 (例如源文件开始处 )预先对各个被调用函数进行了声明,则在主调函数中可缺省对被调用函数的声明。
9.2 函数的定义与调用三、函数的调用第九章 函数
(2) 函数定义和函数声明是两个不同的概念。
函数定义是对函数功能的确立,包括定义函数名、函数值的类型、函数参数及其函数体等,
它是一个完整的、独立的函数单位。在一个程序中,一个函数只能被定义一次,而且是在其他任何函数之外进行的。
函数声明则是把函数的名称、函数值的类型、
参数的个数、类型和顺序通知编译系统,以便在调用该函数时系统对函数名称正确与否、参数的类型、个数及顺序是否一致等进行对照检查。
9.2 函数的定义与调用三、函数的调用第九章 函数
(3) 对库函数的调用不需要声明,但必须把该库函数的头文件用 #include 命令包含在源文件开始处。
9.2 函数的定义与调用三、函数的调用第九章 函数
[例 9.7]由键盘输入三个整数,输出绝对值最大者
#include,math.h”
main( )
{ int a,b,c,max;
scanf(“%d,%d,%d”,&a,&b,&c);
max=find(a,b,c); printf(“max=%d\n”,max);
}
find( int x,int y,int z )
{ int max; max=x;
if( abs(max) < abs(y) ) max=y;
if( abs(max) < abs(z) ) max=z;
return max;
}
运行结果:
12,- 34,9
max=- 34
9.2 函数的定义与调用三、函数的调用第九章 函数
[例 9.8]编写程序,计算 1!+ 2!+ 3!+ … + 10!的值。
程序如下:
long fact(int n)
{ int i; long s=1;
for (i=1; i<=n; i++ ) s=s*i;
return s; }
main( )
{ long s=0; int k,n;
printf(“Input a number:”); scanf("%d",&n);
for (k=1; k<=n; k++ ) s=s+ fact(k);
printf("1!+ 2!+ … + 10!=%ld\n",n,s);
}
程序运行情况如下,
Input a number:10↙
1!+ 2!+ … + 10!=4037914
9.2 函数的定义与调用三、函数的调用第九章 函数
[例 9.9]用公式求 π的近似值,直到最后一项的绝对值小于
10- 6为止。
#include,math.h”
main( )
{ float cal(float); float pi;
pi=cal(1e- 6); printf(“pi=%10.6f \n”,pi);
}
float cal( float e )
{ int s; float pi,n,t;
t=1; pi=0; n=1.0; s=1;
while( fabs(t) >= e )
{ pi=pi+ t; n=n+ 2; s=- s; t=s/n; }
return pi*4;
}
运行结果,
pi=3.141594
9.2 函数的定义与调用三、函数的调用第九章 函数
[例 9.10]求 100~200之间的全部素数之和
#include,math.h”
main( )
{ int m,s=0;
for(m=101; m<200; m+ =2) s+ =sum(m);
printf(“s=%d\n”,s);
}
sum(int m )
{ int i,k; k=sqrt(m);
for( i=2; i<=k; i++ )
if(m%i==0) return 0;
return m;
}
运行结果如下:
s=3167
9.2 函数的定义与调用三、函数的调用第九章 函数
1.函数的嵌套调用在一个函数的执行过程中又调用另一个函数的调用方式称为函数的嵌套调用。
2.说明
(1) C语言不允许函数嵌套定义,但允许在一个函数的函数体中出现对另一个函数的调用。
所谓函数的嵌套调用,是指在执行被调用函数时,被调用函数又调用了其他函数。
9.3 函数的嵌套调用与递归调用一、函数的嵌套调用第九章 函数调用 f2()函数后续语句
main( )函数调用 f1()函数后续语句结束
f1函数返回
f2函数返回
9.3 函数的嵌套调用与递归调用一、函数的嵌套调用
(2) 图中表示了两层嵌套的情形。其执行过程是:执行
main()函数中调用 f1()函数的语句时,即转去执行 f1()函数;
在 f1()函数中调用 f2()函数时,又转去执行 f2()函数; f2()函数执行完毕,返回 f1()函数的断点,继续执行其后续语句;
f1()函数执行完毕,返回 main()函数的断点,继续执行后续语句。
第九章 函数
[例 9.11]求 1!+ 2!+ 3!+ 4!+ …,+ 20!
main()
{ float sum(int); float add;
add=sum(20); printf(“add=%e”,add);
}
float sum(int n)
{ float fac(int); int i; float s=0;
for(i=1; i<=n; i++ ) s+ =fact(i);
return s;
}
float fac(int i)
{ float t=1; int n=1;
do
{ t=t*n; n++; }while(n<=i);
return t;
}
运行结果为,s=2.56133e+ 18
9.3 函数的嵌套调用与递归调用一、函数的嵌套调用第九章 函数
[例 9.12]用递归法计算 n!。
分析:用递归法计算 n!,可用下述公式表示:
n! =n*(n- 1)! (n≥2)
1! =1 (n=1)
long fac(int n)
{ long c;
if (n>1) c=fac(n- 1)*n;
else f=1;
return f;
}
main()
{ int n; long y; printf(“Input a integer number:,);
scanf(“%d”,&n); y=fac(n);
printf("%d!=%ld\n",n,y);
}
程序运行情况如下,Input a integer number,5↙
5! =120
9.3 函数的嵌套调用与递归调用二、函数的递归调用第九章 函数说明
(1) 递归函数既是主调函数,同时又是被调用函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。下图中,函数 f()为直接递归函数,函数 f1()和函数 f2()为间接递归函数。
调用 f函数 调用 f2函数
f函数
f1函数 f2函数调用 f1函数
9.3 函数的嵌套调用与递归调用二、函数的递归调用第九章 函数
(2) 如果递归函数无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行,
必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。这个条件称为递归结束条件。
(3) 递归算法一般也可以用,递推,的循环结构来代替。也可以用递推法编程。递推法比递归法更容易理解和实现。但是有些问题只能用递归算法才能实现。
9.3 函数的嵌套调用与递归调用二、函数的递归调用第九章 函数
[例 9.13]用递归法将十进制整数转换为二进制整数。
void fun(int n)
{ if (n/2) fun(n/2);
printf("%d",n%2);
}
main()
{ int a; printf("Input a integer number:");
scanf(“%d”,&a); fun(a);
printf("\n");
}
程序运行情况如下,
Input a integer number:,345↙
101011001
9.3 函数的嵌套调用与递归调用二、函数的递归调用第九章 函数
1.局部变量的概念和定义在一个函数 (包括 main()函数 )内部或复合句内部定义的变量称为局部变量。
函数的形参属于局部变量。局部变量只在该函数范围内或该复合句范围内有效。
2.说明
(1) 主函数 main()中定义的局部变量,只能在主函数中使用,其他函数不能使用。同时,主函数中也不能使用其他函数中定义的局部变量。
因为主函数也是一个函数,与其他函数是平行关系。
9.4 变量的作用域一、局部变量第九章 函数
9.4 变量的作用域一、局部变量
int f1(int x)
{ int y,z;

}
main()
{ int x,y;

{
int z;
z=x+y;

}
...
}
f1内部,x,y,z作用域
z 的作用域
main中 x,y的作用域第九章 函数
(2) 形参变量也是局部变量,属于被调用函数;
实参变量则是主调函数的局部变量。
(3) C语言允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的存储单元,
互不干扰,也不会发生混淆。
(4) 在复合句中也可定义变量,其作用域只在复合句范围内。
9.4 变量的作用域一、局部变量第九章 函数
1.全局变量的概念和定义在函数外部定义的变量称为全局变量。全局变量不属于任何一个函数,其作用域是:从全局变量的定义位置开始,到本源文件结束为止。全局变量可被作用域内的所有函数直接引用。
9.4 变量的作用域二、全局变量
int p=1,q=5;
int f1(int x)
{ int y,z;
……}
char c1,c2;
main()
{ int x,y;
……}
c1,c2的作用域
p,q 的作用域第九章 函数
2.说明
(1) 全局变量可加强函数模块之间的数据联系,
但又使这些函数依赖于这些全局变量,因而使得这些函数的独立性降低。从模块化程序设计的观点来看,这是不利的,因此不是非用不可时,不提倡使用全局变量。
(2) 在同一源文件中,允许全局变量和内部变量同名。同名时在内部变量的作用域内,全局变量将被屏蔽而不起作用。
9.4 变量的作用域二、全局变量第九章 函数
[例 9.14]全局变量与内部变量同名举例。
int a=3,b=5;
int max(int a,int b)
{ int c; c=a>b?a:b;
return c;
}
main()
{ int a=8;
printf(“max=%d\n”,max(a,b));
}
程序运行结果如下:
max=8
9.4 变量的作用域二、全局变量第九章 函数
(3) 全局变量的作用域是从定义点开始到本源文件结束为止的。如果定义点之前的函数需要引用这些全局变量,则需要在函数内对被引用的全局变量进行声明。
全局变量声明的一般形式为
extern 类型名 全局变量 1,全局变量
2… ;
9.4 变量的作用域二、全局变量第九章 函数
存储类型是指变量在内存中存储的方式。各种变量的作用域不同,就其本质来说是因为变量的存储类型不同。
变量的存储类型分为静态存储和动态存储两大类。
静态存储变量通常是在变量定义时就分配存储单元,并一直保持不释放,直至整个程序运行结束才释放。
动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放。
9.5 变量的存储类别一、动态存储与静态存储的概念第九章 函数
对变量的存储类型定义有以下四种:
自动变量 (auto)、寄存器变量 (register)、
全局变量 (extern)和静态内部变量 (static)。
自动变量和寄存器变量属于动态存储方式,全局变量和静态内部变量属于静态存储方式。
9.5 变量的存储类别一、动态存储与静态存储的概念第九章 函数
1.静态存储 ──静态局部变量静态局部变量的定义格式如下:
static 数据类型 局部变量表;
存储特点:
(1) 静态局部变量属于静态存储,是在编译时为其分配存储单元的,其生存期为整个程序执行期间。在程序执行过程中,即使所在函数被调用结束也不释放,一直存在。
但其他函数不能引用它们。
(2) 静态局部变量是在编译时赋初值的,若定义而不初始化,则自动赋以 0。每次调用静态局部变量所在的函数时,不再重新赋初值,只是保留上次调用结束时的值,
如下例所示。
9.5 变量的存储类别二、局部变量的存储方式第九章 函数
[例 9.15]
void f(int c)
{ int a=0;
static int b=0;
a++; b++;
printf(“%d,a=%d,b=%d\n”,c,a,b);
}
main()
{ int i;
for( i=1; i<=2; i++ ) f (i );
}
9.5 变量的存储类别二、局部变量的存储方式第九章 函数说明:
(1)当需要保留函数上一次调用结束时的值时,应使用静态局部变量,
(2)当变量定义并初始化后只被引用而不改变其值时也可以使用静态局部变量,以避免每次调用时重新赋值。
(3) 由于静态局部变量的作用域与生存期不一致,
降低了程序的可读性,因此,除非对程序的执行效率有很大提高外,一般不提倡使用静态变量。
9.5 变量的存储类别二、局部变量的存储方式第九章 函数
2.动态存储 ──自动局部变量 (又称自动变量 )
自动变量的定义格式如下:
auto 数据类型 自动变量表;
存储特点:
(1) 自动变量属于动态存储方式。是在函数被调用时为其分配存储单元的,其生存期为函数被调用期间,调用结束就释放。函数的形参也属于此类变量。
在复合语句中定义的自动变量,其生存期为该复合句被执行期间。
9.5 变量的存储类别二、局部变量的存储方式第九章 函数
(2) 定义而不初始化时,自动变量的值是不确定的。如果定义并初始化,则赋初值操作是在函数被调用时进行的,且每次调用都要重新赋一次初值。
(3) 由于自动变量的作用域和生存期都局限于定义它的个体内 (函数或复合句 ),因此,不同的个体中允许使用同名的变量而不会混淆。即使在函数内定义的自动变量,也可与该函数内部的复合句中定义的自动变量同名。
注意,系统不会混淆,并不意味着人也不会混淆,
所以尽量少用同名自动变量。
9.5 变量的存储类别二、局部变量的存储方式第九章 函数
3.寄存器存储 ──寄存器变量一般情况下,变量的值都是存储在内存中的。
为提高执行效率,C语言允许将内部变量的值存放到寄存器中,这种变量称为寄存器变量。
定义格式如下:
register 数据类型 变量表;
说明:
(1)只有内部变量和形参变量才能定义为寄存器变量,全局变量则不行。
9.5 变量的存储类别二、局部变量的存储方式第九章 函数
(2)对寄存器变量的实际处理,随系统而异。
(3)允许使用的寄存器数目是有限的,不能定义任意多个寄存器变量。一般将函数中使用频率最高的变量定义为寄存器变量,
以此提高程序的执行效率。
9.5 变量的存储类别二、局部变量的存储方式第九章 函数全局变量分为两种:
静态全局变量,只允许被本源文件中的函数引用。
定义格式,static 数据类型 全局变量表;
非静态全局变量,允许被其他源文件中的函数引用。
定义时缺省 static关键字的全局变量,即为非静态全局变量。其他源文件中的函数引用非静态全局变量时,需要在引用函数所在的源文件中进行如下声明:
extern 数据类型 全局变量表;
在函数内的 extern变量声明,表示引用本源文件中的全局变量,而函数外 (通常在文件开头 )的 extern变量声明,表示引用其他源文件中的全局变量。
9.5 变量的存储类别三、全局变量的存储方式第九章 函数
两者的区别在于:非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态全局变量在各个源文件中都是有效的;而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能引用它。
9.5 变量的存储类别三、全局变量的存储方式第九章 函数
静态内部变量和静态全局变量同属静态存储方式的区别:
1)定义的位置不同。静态内部变量在函数内定义,静态全局变量在函数外定义。
2)作用域不同。静态内部变量属于内部变量,
其作用域仅限于定义它的函数内,虽然生存期为整个源程序,但其他函数是不能引用它的;静态全局变量在函数外定义,其作用域为定义它的源文件内,它的生存期也为整个源程序,其他源文件中的函数也是不能引用它的。
9.5 变量的存储类别三、全局变量的存储方式第九章 函数
3)初始化处理不同。静态内部变量仅在第一次调用它所在的函数时被初始化,当再次调用定义它的函数时,不再初始化,
而是保留上一次调用结束时的值。静态全局变量是在函数外定义的,不存在静态内部变量的 "重复 "初始化问题,其当前值由最近一次给它赋值的操作决定。
9.5 变量的存储类别三、全局变量的存储方式第九章 函数
一个函数只能被其所在文件内的函数调用,而不能被其他文件内的函数所调用,称内部函数。
标明一个函数为内部函数的方法:
static类型标识符 函数名(形参表)
内部函数也称静态函数。类似于静态变量,
内部函数不能被其他文件中的函数使用。因此,
不同文件中允许使用相同名字的内部函数,
9.6 内部函数和外部函数一、内部函数第九章 函数可以被其它本源文件中的函数调用的函数称为外部函数。
定义时,可以加 extern 声明如,extern int fun(int a,int b)
{…… }
函数 fun可以为其他文件调用。
如果在定义函数时省略 extern,则隐含为外部函数。
在需要调用此函数的文件中,要用 extern声明此函数的原形(返回值为整形的函数除外)。
9.6 内部函数和外部函数二、外部函数