第八章
本章要点
函数的概念
函数的定义与调用
函数的递归调用
变量的作用域
函数的作用域
主要内容
§ 8.1 概述
§ 8.2函数定义的一般形式
§ 8.3函数参数和函数的值
§ 8.4 函数的调用
§ 8.5 函数的嵌套调用
§ 8.6函数的递归调用
§ 8.7数组作为函数参数
§ 8.8 局部变量和全局变量
§ 8.9变量的存储类别
§ 8.10 内部函数和外部函数
§ 8.1概述 一个较大的程序可分为若干个 程序模块,每一个模块用来实现一个特定的功能。在高级语言中用 子程序 实现模块的功能。子程序由函数来完成。一个C程序可由一个主函数和若干个其他函数构成。
由主函数调用其他函数,其他函数也可以互相调用。
同一个函数可以被一个或多个函数调用任意多次。
函数间的调用关系
# include <stdio.h>
void main()
{
void printstar(); /*对 printstar函数声明 */
void print_message();
/*对 print_message函数声明 */
printstar(); / *调用 printstar函数 */
print_message(); /*调用 print_message函数 */
printstar(); / *调用 printstar函数 */
}
例 8.1先举一个函数调用的简单例子
void printstar() / *定义 printstar函数 */
{
printf("* * * * * * * * * * * * * * * *\n");
}
void print_message() / *定义 print_message函数 */
{
printf("How do you do!\n");
}
运行情况如下:
* * * * * * * * * * * * * * * *
How do you do!
* * * * * * * * * * * * * * * *
说明:
( 1) 一个C程序由一个或多个程序模块组成,
每一个程序模块作为一个源程序文件 。 对较大的程序,一般不希望把所有内容全放在一个文件中,而是将他们分别放在若干个源文件中,再由若干源程序文件组成一个 C程序 。
这样便于分别编写,分别编译,提高调试效率 。 一个源程序文件可以为多个 C程序公用 。
( 2) 一个源程序文件由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成
。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
( 3) C程序的执行是从main函数开始的
,如是在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行 。
( 4) 所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的 。 一个函数并不从属于另一函数,即函数不能嵌套定义 。 函数间可以互相调用,但不能调用main函数 。
main函数是系统调用的 。
( 5) 从用户使用的角度看,函数有两种:
① 标准函数,即库函数 。 这是由系统提供的
,用户不必自己定义这些函数,可以直接使用它们 。 应该说明,不同的 C系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的 。
② 用户自己定义的函数 。 用以解决用户的专门需要 。
( 6) 从函数的形式看,函数分两类:
① 无 参 函 数 。 如例 8.1 中的 printstar 和
print_message就是无参函数 。 在调用无参函数时,主调函数不向被调用函数传递数据 。 无参函数一般用来执行指定的一组操作 。 例如,例 8
,1程序中的 printstar函数 。
② 有参函数 。 在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用 。
§ 8.2函数定义的一般形式
§ 8.2.1,无参函数的定义一般形式定义 无参函数 的一般形式为,
类型标识符 函数名 ()
{
声明部分语句部分
}
在定义函数时要用,类型标识符,
指定函数值的类型,即函数带回来的值的类型。
例 8.1中的
printstar和
print_message函数为 void类型,
表示不需要带回函数值。
§ 8.2.2,有参函数定义的一般形式定义 有参函数 的一般形式为,
类型标识符 函数名 (形式参数表列)
{
声明部分语句部分
} 例如:
int max( int x,int y)
{int z; / *函数体中的声明部分 */
z=x>y?x ∶ y;
return(z);
}
§ 8.2.3 空函数定义 空函数 的一般形式为,
类型标识符 函数名 ()
{ }
例如:
dummy ( )
{ }
调用此函数时,什么工作也不做,没有任何实际作用。在主调函数中写上,dum
my();,表明
,这里要调用一个函数,,而现在这个函数没有起作用,等以后扩充函数功能时补充上。
§ 8.3函数参数和函数的值
§ 8.3,1形式参数和实际参数在前面提到的有参函数中,在定义函数时函数名后面括弧中的变量名称为,形式参数,
(简称,形参,),在主调函数中调用一个函数时,函数名后面括弧中的参数 (可以是一个表达式 )称为,实际参数,(简称,实参,
)。 return后面的括弧中的值 ()作为函数带回的值(称 函数返回值 )。
在不同的函数之间传递数据,可以使用的法:
◆ 参数:通过形式参数和实际参数
◆ 返回值:用 return语句返回计算结果
◆ 全局变量:外部变量大多数情况下,主调函数和被调用函数之间有数据传递的关系。
#include <stdio.h>
void main ( )
{ int max(int x,int y );
/* 对max函数的声明 */
int a,b,c;
scanf( " % d,% d",&a,&b ) ;
c=max ( a,b ) ;
printf( "Max is % d",c ) ;
}
例 8.2调用函数时的数据传递
int max(int x,int y )/ *定义有参函数 max */
{
int z;
z=x>y? x ∶ y;
return( z ) ;
}
运行情况如下:
7,8 ↙
Max is 8
通过函数调用,使两个函数中的数据发生联系关于形参与实参的说明:
(1) 在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。
(2) 实参可以是常量、变量或表达式,如:
max(3,a+b);
但要求它们有确定的值。在调用时将实参的值赋给形参。
(3) 在被定义的函数中,必须指定形参的类型(见例 8.2程序中的,c=max(a,
b);” )。
(4) 实参与形参的类型应相同或赋值兼容。
例 8.2中实参和形参都是整型。如果实参为整型而形参 x为实型,或者相反,则按第 3章介绍的不同类型数值的赋值规则进行转换。
例如实参值 a为 3.5,而形参 x为整型,则将实数 3.5转换成整数 3,然后送到形参 b。字符型与整型可以互相通用。
(5) 在C语言中,实参向对形参的数据传递是,值传递,,单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,
实参单元与形参单元是不同的单元。
在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。因此,
在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。例如,
若在执行函数过程中x和y的值变为10和1
5,而a和b仍为2和3。
§ 8.3.2 函数的返回值通常,希望通过函数调用使主调函数能得到一个确定的值,这就是 函数的返回值 。例如,例 8.2中,
max(2,3)的值是3,max(5,2)的值是 5。赋值语句将这个函数值赋给变量c。
关于函数返回值的一些说明:
(1)函数的返回值是通过函数中的 return语句获得的。
如果需要从被调用函数带回一个函数值供主调函数使用,被调用函数中必须包含 return语句。如果不需要从被调用函数带回函数值可以不要 return语句。
一个函数中可以有一个以上的 return语句,执行到哪一个 return语句,哪一个语句起作用。 return语句后面的括弧也可以不要,
如,,return z ;” 等价于,return (z);,
return语句将被调用函数中的一个确定值带回主调函数中去。见图 8.2中从 return语句返回的箭头。
return后面的值可以是一个表达式。
例如,例 8.2中的函数max可以改写成:
max( int x,int y)
{
return ( x>y?x ∶ y);
}
(2)函数的返回值应当属于某一个确定的类型,
在定义函数时指定函数返回值的类型 。
例如,下面是 3个函数的首行:
int max( float x,float y) /* 函数值为整型 */
char letter( char c1,char c2) /* 函数值为字符型 */
double min( int x,int y) /* 函数值为双精度型 */
在C语言中,凡不加类型说明的函数,自动按整型处理。例 8.2中的max函数首行的函数类型 int可以省写,用 Turbo C 2.0编译程序时能通过,但用 Turbo C
++ 3.0编译程序时不能通过,因为 C++要求所有函数都必须指定函数类型。因此,建议在定义时对所有函数都指定函数类型。
( 3) 在定义函数时指定的函数类型一般应该和
return语句中的表达式类型一致。
如果函数值的类型和 return语句中表达式的值不一致,
则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。
( 4) 对于不带回值的函数,应当用,void”定义函数为,无类型,(或称,空类型,)。这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。此时在函数体中不得出现 return语句。
例 8.3 返回值类型与函数类型不同
# include <stdio.h>
void main()
{ int max( float x,float y) ;
float a,b;
int c;
scanf("%f,%f,",&a,&b);
c=max(a,b);
printf("Max is %d\n",c);
}
int max( float x,float y)
{ float z; /* z为实型变量 */
z=x>y?x ∶ y;
return(z);
}
运行情况如下:
1.5,2.5 ↙
Max is 2
§ 8.4 函数的调用
§ 8.4,1 函数调用的一般形式函数调用的一般形式为,函数名 (实参表列)
如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。
如果是调用无参函数,则,实参表列,
可以没有,但括弧不能省略。
如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。许多C版本是按自右而左的顺序求值,例如 Tubro C ++。
例 8.4 实参求值的顺序
#include <stdio.h>
void main()
{
int f(int a,int b); /* 函数声明 */
int i=2,p;
p=f(i,++i); /* 函数调用 */
printf("%d\n",p);
}
int f(int a,int b) /* 函数定义 */
{
int c;
if(a>b) c=1;
else if(a==b) c=0;
else c=-1;
return(c);
}
如果按自左至右顺序求实参的值,则函数调用相当于f(2,3)
如果按自左至右顺序求实参的值,则函数调用相当于f( 3,3)
对于函数调用
int i=2,p;
p=f(i,++i);
§ 8.4,2函数调用的方式
1.函数语句把函数调用作为一个语句。如例 8.1中的 printstar(),这时不要求函数带回值,只要求函数完成一定的操作。
2.函数表达式函数出现在一个表达式中,这种表达式称为 函数表达式 。这时要求函数带回一个确定的值以参加表达式的运算。例如,c=2 *max(a,b);
3.函数参数函数调用作为一个函数的实参。例如,
m = max (a,max ( b,c ) ) ;
其中 max ( b,c )是一次函数调用,它的值作为 max另一次调用的实参。 m的值是 a,b,c三者中的最大者。
又如,printf ("%d",max (a,b));也是把 max ( a,b )
作为 printf函数的一个参数。
函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。
§ 8.4,3对被调用函数的声明和函数原型
(1) 首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。
但光有这一条件还不够。
§ 4 3对被调用函数的声明和函数原型
(3) 如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)
的后面(在同一个文件中),应该在主调函数中 对被调用的函数作声明 。
(2) 如果使用库函数,还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。
函数原型的一般形式为
(1) 函数类型 函数名 (参数类型 1,参数类型 2) ;
(2) 函数类型 函数名 (参数类型 1,参数名 1,参数类型 2,参数名 2) ;
,声明” 一词的原文是 delaration,过去在许多书中把它译为“说明”。 声明的作用 是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。 (例如函数名是否正确,
实参与形参的类型和个数是否一致)。
注意,函数的“定义”和“声明”不是一回事 。 函数的定义 是指对函数功能的确立,
包括指定函数名,函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。而 函数的声明 的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
# include <stdio.h>
void main()
{ float add( float x,float y);
/ *对被调用函数 add的声明 */
float a,b,c;
scanf("% f,% f",& a,& b);
c= add( a,b) ;
printf(" sum is % f \n",c);
}
float add( float x,float y) / *函数首部 */
{ float z; /* 函数体 */
z=x+y;
return( z);
}
例 8.5 对被调用的函数作声明如果 被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义函数的有关情况,
会根据函数首部提供的信息对函数的调用作正确性检查。
被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义函数的有关情况,
会根据函数首部提供的信息对函数的调用作正确性检查。
改写例 8.5
# include <stdio.h>
float add( float x,float y) / *函数首部 */
{ float z; /* 函数体 */
z=x+y;
return( z);
}
void main()
{
float a,b,c;
scanf("% f,% f",& a,& b);
c= add( a,b) ;
printf(" sum is % f \n",c);
}
§ 8.5 函数的嵌套调用嵌套定义就是在定义一个函数时,其函数体内又包含另一个函数的完整定义 。
C语言不能嵌套定义函数,但可以嵌套调用函数,也就是说,在调用一个函数的过程中,
又调用另一个函数。
例 8.6 用弦截法求方程
f(x)=x3-5x2+16x-80=0 的根
(1) 取两个不同点 x1,x2,如果 f(x1)和 f(x2)符号相反,则 (x1,x2)区间内必有一个根。如果 f(x1)与
f(x2)同符号,则应改变 x1,x2,直到 f(x1),f(x2)异号为止。注意 x1,x2的值不应差太大,以保证
(x1,x2)区间内只有一个根。
(2) 连接 (x1,f(x1))和 (x2,f(x2))两点,此线 (即弦 )交 x轴于 x。
方法:
(3) 若 f(x)与 f(x1)同符号,则根必在 (x,x2)区间内,此时将 x作为新的 x1。如果 f(x)与 f(x2)同符号,则表示根在 (x1,x)区间内,将 x作为新的 x2。
(4) 重复步骤 (2) 和 (3),直到 | f(x)|<
ε 为止,ε 为一个很小的数,例如 10-6\,此时认为 f(x)≈0
N-S流程图分别用几个函数来实现各部分功能,
(1) 用函数 f(x)代表 x的函数,x3-5x2+16x-80.
(2) 用函数调用 xpoint (x1,x2)来求 (x1,f(x1))和
(x2,f(x2))的连线与 x轴的交点 x的坐标。
(3) 用函数调用 root (x1,x2)来求 (x1,x2)区间的那个实根。显然,执行 root函数过程中要用到函数 xpoint,而执行 xpoint函数过程中要用到 f函数。
#include <stdio.h >
#include <math.h >
float f(float x) / * 定义f函数,以实现 f(x) = x3-5x2+16x-80 */
{float y;
y =((x -5,0 )*x +16,0 )*x -80,0;
return(y);
}
float xpoint(float x1,float x2)
/ *定义 xpoint函数,求出弦与 x轴交点 */
{float y;
y =(x1 *f(x2) -x2 *f(x1))
/f(x2) -f(x1));
return(y);
}
float root( float x1,float x2)
/* 定义 root函数,求近似根 */
{float x,y,y1;
y1=f(x1);
do
{ x=xpoint(x1,x2);
y=f(x);
if(y *y1>0) /*f (x )与f (x1 )同符号 */
{ y1=y;
x1=x;}
else
x2=x;
} while(fabs(y)>=0,0001);
return(x);
}
void main()
{ float x1,x2,f1,f2,x;
do
{ printf("input x1,x2:\n");
scanf("%f,%f",&x1,&x2);
f1=f(x1);
f2=f(x2);
} while(f1 *f2>=0);
x=root(x1,x2);
printf("A root of equation is %8,4f \n",x );
} 运行情况如下:
input x1,x2:
2,6
A root of equation is 5.0000
§ 8.6函数的递归调用在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用 。 C语言的特点之一就在于允许函数的递归调用 。 例如:
int f( int x)
{
int y,z;
z=f(y);
return(2 *z);
}
例 8.7 有5个人坐在一起,问第5个人多少岁?
他说比第4个人大2岁。问第4个人岁数,他说比第
3个人大2岁。问第3个人,又说比第2个人大2岁。
问第2个人,说比第1个人大2岁。最后问第1个人,
他说是10岁。请问第5个人多大。
age(5)=age(4)+2
age(4)=age(3)+2
age(3)=age(2)+2
age(2)=age(1)+2
age(1)=10
可以用数学公式表述如下:
age(n)=10 (n=1)
age(n-1)+2 (n>1 )
可以用一个函数来描述上述递归过程:
int age( int n) / * */
{ int c; / * c用作存放函数的返回值的变量 */
if(n==1) c=10;
else c=age(n-1)+2;
return(c);
}
用一个主函数调用 age函数,求得第 5人的年龄。
#include <stdio.h>
void main()
{
printf( ″%d ″,age(5));
}
运行结果如下:
18
例 8.8用递归方法求n!
求n!也可以用递归方法,即5!等于
4! × 5,而4!=3! × 4 … 1!=
1。可用下面的递归公式表示:
n!=1 (n=0,1)
n ·(n-1)! (n>1)
例 8.9 Hanoi(汉诺)塔问题 。这是一个古典的数学问题,是一个用递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有 3个座 A,B,C,开始时A座上有6
4个盘子,盘子大小不等,大的在下,小的在上(见图 8.13)。有一个老和尚想把这6
4个盘子从A座移到C座,但每次只允许移动一个盘,且在移动过程中在 3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求编程序打印出移动的步骤。
为便于理解,我们先分析将A座上3个盘子移到
C座上的过程:
(1) 将A座上2个盘子移到B座上(借助C);
(2) 将A座上1个盘子移到C座上;
(3) 将B座上2个盘子移到C座上(借助A)。
其中第 (2 )步可以直接实现。第1步又可用递归方法分解为:
1.1 将A上1个盘子从A移到C;
1.2 将A上1个盘子从A移到B;
1.3 将C上1个盘子从C移到B。
第 (3 )步可以分解为:
3.1 将B上1个盘子从B移到A上;
3.2 将B上1个盘子从B移到C上;
3.3 将A上1个盘子从A移到C上。
将以上综合起来,可得到移动 3个盘子的步骤为
A → C,A → B,C → B,A → C,B → A,B →
C,A → C。
由上面的分析可知:将n个盘子从A座移到C座可以分解为以下 3个步骤:
(1) 将A上n-1个盘借助C座先移到B座上。
(2) 把A座上剩下的一个盘移到C座上。
(3) 将n-1个盘从B座借助于A座移到C
座上。
程序如下:
#include <stdio.h>
void main()
{
void hanoi(int n,char one,char two,char three);
/* 对 hanoi函数的声明 */
int m;
printf("input the number of diskes:");
scanf(“%d”,&m);
printf("The step to moveing %d diskes:\n",m);
hanoi(m,'A','B','C');
}
void hanoi(int n,char one,char two,char three)
/* 定义 hanoi函数,将n个盘从 one座借助 two座,移到
three座 */
{
void move(char x,char y); /* 对 move函数的声明 */
if(n==1) move(one,three);
else
{ hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three); }
}
void move(char x,char y) /* 定义 move函数 */
{
printf(“%c-->%c\n",x,y);
}
运行情况如下:
input the number of diskes:3↙
The steps to noving 3 diskes:
A-->C
A-->B
C-->B
A-->C
B-->A
B-->C
A-->C
§ 8.7数组作为函数参数
§ 8.7.1 数组元素作函数实参由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素当然可以作为函数的实参,与用变量作实参一样,是单向传递,即,值传送,方式 。
例 8.10 有两个数组a和b,各有10个元素,将它们对应地逐个相比 ( 即a [ 0 ] 与b [ 0 ] 比,
a [ 1 ] 与b [ 1 ] 比 …… ) 。 如果a数组中的元素大于b数组中的相应元素的数目多于 b数组中元素大于 a数组中相应元素的数目 (例如,a[ i] >b[ i] 6
次,b[ i] >a[ i] 3次,其中 i每次为不同的值 ),则认为 a数组大于 b数组,并分别统计出两个数组相应元素大于,等于,小于的次数 。
#include <stdio.h>
void main()
{ int large(int x,int y); /* 函数声明 */
int a [10],b [10],i,n=0,m=0,k=0;
printf( ″enter array a∶ \n ″);
for(i=0;i<10;i++= )
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
printf( ″ enter arrayb ∶ \n ″);
for(i=0;i<10;i++= )
scanf ( ″%d ″,&b[i]);
printf( ″\n ″);
for(i=0;i<10;i++)
{ if( large (a [i],b [i] )== 1) n=n+1;
else if( large (a [i],b [i] )==0) m =m +1;
else k=k+1; }
printf("a[i]>b[i] %d times\na[i]=b[i] %d
times\na[i]<b[i] %d times\n",n,m,k);
if(n>k) printf("array a is larger than array b\ n");
else if (n<k) printf("array a is smaller than array b\n");
else printf("array a is equal to array b\ n");
}
large( int x,int y)
{ int flag;
if(x>y)flag=1;
else if(x<y) flag=-1;
else flag=0;
return( flag);
}
运行情况如下:
enter array a:
1 3 5 7 9 8 6 4 2 0 ↙
enter array b ∶
5 3 8 9 –1 –3 5 6 0 4↙
a[i]>b[i] 4 times
a[i]=b[i] 1 times
a[i]<b[i] 5 times
array a is smaller thann array b
§ 8.7.2 数组名作函数参数可以用数组名作函数参数,此时形参应当用数组名或用指针变量 。
例 8.11 有一个一维数组score,内放
10个学生成绩,求平均成绩。
#include <stdio.h>
void main()
{ float average( float array[ 10]) ; /* 函数声明 */
float score[10],aver;
int i;
printf( ″input 10 scores:\n ″);
for(i=0;i<10;i++=
scanf( ″%f ″,& score[i]);
printf( ″\n ″);
aver= average( score );
printf ( ″ average score is %5,2f \n″,aver);
}
float average ( float array[ 10])
{ int i;
float aver,sum=array[0];
for (i=1;i<10;i++= )
sum=sum+array[i];
aver=sum/10;
return( aver);
}
运行情况如下:
input 10 scores:
100 56 78 98,5 76 87 99 67,5 7
5 97 ↙
average score is 83.40
例 8.12形参数组不定义长度
#include <stdio.h>
void main()
{ float average( float array[ ],int n)
float score_1[5] = {98.5,97,9
1,5,60,55 };
float score_2[ 10] ={ 67.5,89.5,99,69,5,
77,89.5,76.5,54,60,99.5};
printf(“the average of class A is %6.2f\ n”,
average(score_1,5));
printf(“the average of class B is %6.2f\ n”,
average(score_2,10));
}
float average( float array[ ],int n)
{ int i;
float aver,sum=array[0];
for(i=1;i<n;i++=
sum= sum+ array[i];
aver= sum/n;
return(aver);
}
运行结果如下:
the average of class A is 80.40
The average of class B is 78.20
例 8.13 用选择法对数组中 10个整数按由小到大排序。所谓选择法就是先将 10个数中最小的数与 a[ 0]对换 ;再将 a[ 1]
到 a[ 9]中最小的数与 a[ 1]对换 ……
每比较一轮,找出一个未经排序的数中最小的一个。共比较 9轮。
a[0] a[1] a[2] a[3] a[4]
3 6 1 9 4 未排序时的情况
1 6 3 9 4
将 5个数中最小的数 1与 a[ 0]对换
1 3 6 9 4
将余下的 4个数中最小的数 3与 a[ 1]对换
1 3 4 9 6
将余下的 3个数中最小的数 4与 a[ 2]对换
1 3 4 6 9
将余下的 2个数中最小的数 6与 a[ 3]对换,至此完成排序程序实例
#include <stdio.h>
void main()
{ void sort( int array[],int n) ;
int a[10],i;
printf( ″enter the array\n ″);
for(i=0;i<10;i++=
scanf( ″%d ″,&a[i]);
sort(a,10);
printf( ″the sorted array∶ \n ″);
for(i=0;i<10;i++=
printf( ″%d ″,a[i]);
printf( ″\n ″);
}
void sort( int array[],int n)
{ int i,j,k,t;
for(i=0;i<n-1;i++)
{ k=i;
for(j=i+1;j<n;j++)
if( array[j ] < array[k ]=k =j;
t =array[k];
array[k]=array[i];array[i]=t
}
}
§ 8.7.3,多维数组名作函数参数程序如下:
#include <stdio.h>
void main()
{ max_value ( int array[ ][ 4] );
int [3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}};
printf( ″max value is %d\n ″,
max_value(a) );
}
max_value( int array[ ][ 4])
{ int i,j,k,max;
max =array[0][0];
for(i =0;i<3;i++)
for(j=0;j<4;j++=
if( array[i][j]>max)
max = array [i][j];
return(max);
} 运行结果如下:
Max value is 34
§ 8.8局部变量和全局变量
§ 8.8.1局部变量在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。这称为“局部变量”。
float f1( int a) /* 函数 f1 */
{int b,c;
… a,b,c有效
}
char f2(int x,int y) /* 函数 f2 */
{int i,j; x,y,i,j有效
}
void main( ) /* */
{int m,n;
… m,n有效
}
(1) 主函数中定义的变量 (m,n)也只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效。
主函数也不能使用其他函数中定义的变量。
(2) 不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。例如,上面在 f1函数中定义了变量 b和 c,倘若在 f2函数中也定义变量 b和 c,它们在内存中占不同的单元,互不混淆。
(3) 形式参数也是局部变量。例如上面 f1函数中的形参 a,也只在 f1函数中有效。其他函数可以调用 f1函数,
但不能引用 f1函数的形参 a。
(4) 在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为
“分程序”或“程序块”。
说明
void main ( )
{int a,b;
…
{int c;
c=a+b; c在此范围内有效 a,b在此范围内有效
…
}
…
}
§ 8.8.2 全局变量在函数内定义的变量是 局部 变量,而在函数之外定义的变量称为 外部变量,外部变量是全局变量 (也称全程变量 )。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
int p=1,q=5; /* 外部变量 */
float f1(int a) /* 定义函数 f1 */
{int b,c;
…
}
char c1,c2; /* */
char f2 (int x,int y) /* 定义函数 f2 */
{int i,j; 全局变量 p,q的作用范围
… 全局变量 c1,c2的作用范围
}
void main ( ) /* */
{int m,n;
…
}
例 8.15 有一个一维数组,内放10个学生成绩,写一个函数,求出平均分、最高分和最低分。
#include <stdio.h>
float Max=0,Min=0; / * */
void main()
{ float average( float array[ ],int n) ;
float ave,score[10];
int i;
for(i=0;i<10;i++)
scanf( ″%f ″,&score[i]);
ave= average(score,10);
printf(“max=%6.2f\ nmin=%6.2f\ n
average=%6.2f\ n“,Max,Min,ave);
}
float average( float array[ ],int n)
/ * 定义函数,形参为数组 */
{ int i;
float aver,sum=array[0];
Max=Min=array[0];
for(i =1;i<n;i++)
{ if( array[i]> Max) Max= array[i];
else if( array[i]< Min) Min= array[i];
sum=sum+array[i];
}
aver=sum/n;
return(aver);
}
运行情况如下:
99 45 78 97 100 67.5 8
9 92 66 43 ↙
max=100.00
min=43.00
average=77.65
建议不在必要时不要使用全局变量,原因如下:
① 全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
② 使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部变量的值。
在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
③ 它使函数的通用性降低了,因为函数在执行时要依赖于其所在的外部变量。如果将一个函数移到另一个文件中,还要将有关的外部变量及其值一起移过去。但若该外部变量与其他文件的变量同名时,
就会出现问题,降低了程序的可靠性和通用性。一般要求把C程序中的函数做成一个封闭体,除了可以通过“实参 —— 形参”的渠道与外界发生联系外,
没有其他渠道。
例 8.1 6 外部变量与局部变量同名
#include <stdio.h>
int a=3,b=5; /* a,b为外部变量 */ a,b作用范围
void main ( )
{ int a=8; /*a为局部变量 */ 局部变量 a作用范围
printf (″%d″,max (a,b)); 全局变量 b的作用范围
}
max (int a,int b) /*a,b为局部变量 */
{ int c;
c=a> b?a∶ b; 形参 a,b作用范围
return (c);
}
运行结果为
8
§ 8.9 变量的存储类别
§ 8.9,1 动态存储方式与静态存储方式前面已介绍了从变量的作用域(即从空间)角度来分,可以分为 全局变量 和 局部变量 。那么从变量值存在的时间(即生存期)角度来分,又可以分为 静态存储方式 和 动态存储方式 。
所谓静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。这个存储空间可以分为三部分:
程序区
静态存储区
动态存储区在C语言中每一个变量和函数有两个属性,数据类型 和 数据的存储类别 。对数据类型,读者已熟悉(如整型、字符型等)。存储类别指的是数据在内存中存储的方式。存储方式分为两大类:静态存储类和动态存储类。具体包含四种,自动的 (auto),静态的 (stat
ic),寄存器的 (register),外部的 (extern) 。 根据变量的存储类别,
可以知道变量的作用域和生存期。
§ 8.9,2 auto变量函数中的局部变量,如不专门声明为 static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。
函数中的形参和在函数中定义的变量 (包括在复合语句中定义的变量 ),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。例如:
int f( int a) / *定义 f函数,a为形参 */
{ auto int b,c=3; / *定义b、c为自动变量 */
…
}
8.9.3用 static声明局部变量有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,
在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。通过下面简单的例子可以了解它的特点。
例 8.1 7 考察静态局部变量的值。
#include <stdio.h>
void main()
{ int f( int) ;
int a=2,i;
for(i=0;i<3;i++=
printf( ″%d ″,f(a));
}
int f( int a)
{auto int b=0;
static c=3;
b=b+1;
c=c+1;
return(a+b+c);
}
对静态局部变量的说明:
(1) 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放。
(2) 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3)如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)
或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
(4) 虽然静态局部变量在函数调用结束后仍然存在,
但其他函数是不能引用它的。
例 8.1 8 输出1到5的阶乘值。
#include <stdio.h>
void main()
{ int fac( int n) ;
int i;
for(i=1;i<=5;i++)
printf( ″%d! =%d\n ″,i,fac(i));
}
Int fac( int n)
{ static int f=1;
f=f *n;
return(f);
}
8.9.4 register变量一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。 经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
如果有一些变量使用频繁(例如在一个函数中执行
10000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花费不少时间。为提高执行效率,C语言允许将局部变量的值放在 CPU中的寄存器中,需要用时直接从寄存器取出参加运算,
不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做 寄存器变量,用关键字 re
gister 作声明。例如,例 8,19中的程序是输出1到 n的阶乘的值。
例 8,19使用寄存器变量
#include <stdio.h>
void main ( )
{long fac(long);
long i,n;
scanf("%ld",&n);
for(i=1;i<=n;i++)
printf("%ld!=%ld\n",i,fac(i));
}
long fac(long n)
{register long i,f=1; /* */
for (i=1;i<=n;i++)
f=f*i;
return (f);
}
8.9,5用 extern声明外部变量外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。
在此作用域内,全局变量可以为程序中各个函数所引用。编译时将外部变量分配在静态存储区。
有时需要用 extern来声明外部变量,以扩展外部变量的作用城。
1,在一个文件内声明外部变量例 8,20 用 extern声明外部变量,扩展它在程序文件中的作用域。
#include <stdio.h>
void main()
{ int max(int,int); / *外部变量声明 */
extern A,B;
printf("%d\n",max(A,B));
}
int A=13,B=-8; / *定义外部变量 */
int max(int x,int y) / *定义max函数 */
{ int z;
z=x>y?x:y;
return(z);
}
2,在多文件的程序中声明外部变量例 8.21 用 extern将外部变量的作用域扩展到其他文件。 本程序的作用是给定b的值,输入a和m,求
a × b和 am的值。文件 file1.c中的内容为:
#include <stdio.h>
int A; /*定义外部变量 */
void main()
{ int power( int); /*函数声明 */
int b=3,c,d,m;
printf( ″enter the number a and its power m:\n″);
scanf( ″%d,%d ″,& A,&m);
c= A*b;
printf( ″%d *%d=%d\n ″,A,b,c);
d=power(m);
printf( ″%d **%d=%d \n″,A,m,d);
}
文件 file2.c中的内容为:
extern A; /*声明 A为一个已定义的外部变量 */
int power( int n) ;
{ int i,y=1;
for(i=1;i<=n;i++)
y *= A;
return(y);
}
8.9.6用 static声明外部变量有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。
例如:
file1.c file2.c
static int A; extern int A;
void main ( ) void fun (int n)
{ {…
… A=A*n;
}
8.9.7关于变量的声明和定义对变量而言,声明与定义的关系稍微复杂一些。在声明部分出现的变量有两种情况:一种是需要建立存储空间的 (如,int a; ),另一种是不需要建立存储空间的
(如,extern a;)。前者称为,定义性声明,(defining declaration),或简称 定义 ( definition)。
后者称为,引用性声明,(referencing declaration)。广义地说,声明包括定义,但并非所有的声明都是定义。
对,int a;” 而言,它既是声明,又是定义。而对
,extern a;” 而言,它是声明而不是定义。
一般为了叙述方便,把 建立存储空间的声明称定义,而 把不需要建立存储空间的声明称为声明 。
显然这里指的声明是狭义的,即非定义性声明。
例如:
void main()
{extern A; /*是声明不是定义。声明 A是一个已定义的外部变量 */
…
}
int A;
§ 8.9,8存储类别小结
(1) 从 作用域 角度分,有局部变量和全局变量。它们采用的存储类别如下:
局部变量 |自动变量,即动态局部变量
(离开函数,值就消失 )
|静态局部变量 (离开函数,值仍保留 )
|寄存器变量 (离开函数,值就消失 )
|(形式参数可以定义为自动变量或寄存器变量 )
全局变量 |静态外部变量 (只限本文件引用 )
|外部变量
(即非静态的外部变量,允许其他文件引用 )
(2) 从变量存在的时间 (生存期 )来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。
动态存储 |自动变量 (本函数内有效 )
|寄存器变量 (本函数内有效 )
|形式参数 (本函数内有效 )
静态存储 |静态局部变量 (函数内有效 )
|静态外部变量 (本文件内有效 )
|外部变量 (其他文件可引用 )
(3) 从 变量值存放的位置 来区分,可分为,
内存中静态存储区 |静态局部变量
|静态外部变量 (函数外部静态变量 )
|外部变量 (可为其他文件引用 )
内存中动态存储区,自动变量和形式参数
CPU中的寄存器,寄存器变量
(4 ) 关于 作用域 和 生存期 的概念。从前面叙述可以知道,对一个变量的性质可以从两个方面分析,一是变量的作用域,一是变量值存在时间的长短,即生存期。前者是从空间的角度,后者是从时间的角度。
二者有联系但不是同一回事。
(5) static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局变量来说,它使变量局部化 (局部于本文件 ),但仍为静态存储方式。从作用域角度看,凡有 static
声明的,其作用域都是局限的,或者是局限于本函数内
(静态局部变量 ),或者局限于本文件内 (静态外部变量 )。
§ 8.10 内部函数和外部函数函数本质上是全局的,因为一个函数要被另外的函数调用,但是,也可以指定函数不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为 内部函数 和 外部函数 。
§ 8.10.1内部函数如果一个函数 只能被本文件中其他函数所调用,它称为内部函数 。在定义内部函数时,在函数名和函数类型的前面加 static。即
static 类型标识符 函数名 (形参表 )
如 static int fun ( int a,int b )
§ 8.10.2外部函数
(1) 在定义函数时,如果在函数首部的最左端加关键字
extern,则表示此函数是外部函数,可供其他文件调用。如函数首部可以写为 extern int fun (int a,int b)
这样,函数 fun就可以为其他文件调用。 C语言规定,
如果在定义函数时省略 extern,则隐含为外部函数。
本书前面所用的函数都是外部函数。
(2) 在需要调用此函数的文件中,用 extern对函数作声明,表示该函数是在其他文件中定义的外部函数例 8.22 有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现
File.c(文件1)
#include <stdio.h>
void main()
{ extern void enter_string(char str[]);
extern void detele_string(char str[],char ch);
extern void print_string(char str[]);/ *以上 3行声明在本函数中将要调用的在其他文件中定义的 3个函数 */
char c;
char str[80];
scanf("%c",&c);
detele_string(str,c);
print_string(str);
}
file2.c(文件2)
#include <stdio.h>
void enter_string(char str[80]) / * 定义外部函数
enter-string*/
{ gets(str); / *向字符数组输入字符串 */
}
file3.c(文件3)
void delete_string(char str[],char ch) / *定义外部函数
delete_string */
{ int i,j;
for(i=j=0;str[i]!='\0';i++)
if(str[i]!=ch)
str[j++]=str[i];
str[i]='\0';
}
file4.c(文件4)
#include <stdio.h>
void print_string(char str[])
{
printf("%s\n",str);
}
运行情况如下:
abcdefgc ↙ (输入str)
c ↙ (输入要删去的字符)
abdefg (输出已删去指定字符的字符串)
本章要点
函数的概念
函数的定义与调用
函数的递归调用
变量的作用域
函数的作用域
主要内容
§ 8.1 概述
§ 8.2函数定义的一般形式
§ 8.3函数参数和函数的值
§ 8.4 函数的调用
§ 8.5 函数的嵌套调用
§ 8.6函数的递归调用
§ 8.7数组作为函数参数
§ 8.8 局部变量和全局变量
§ 8.9变量的存储类别
§ 8.10 内部函数和外部函数
§ 8.1概述 一个较大的程序可分为若干个 程序模块,每一个模块用来实现一个特定的功能。在高级语言中用 子程序 实现模块的功能。子程序由函数来完成。一个C程序可由一个主函数和若干个其他函数构成。
由主函数调用其他函数,其他函数也可以互相调用。
同一个函数可以被一个或多个函数调用任意多次。
函数间的调用关系
# include <stdio.h>
void main()
{
void printstar(); /*对 printstar函数声明 */
void print_message();
/*对 print_message函数声明 */
printstar(); / *调用 printstar函数 */
print_message(); /*调用 print_message函数 */
printstar(); / *调用 printstar函数 */
}
例 8.1先举一个函数调用的简单例子
void printstar() / *定义 printstar函数 */
{
printf("* * * * * * * * * * * * * * * *\n");
}
void print_message() / *定义 print_message函数 */
{
printf("How do you do!\n");
}
运行情况如下:
* * * * * * * * * * * * * * * *
How do you do!
* * * * * * * * * * * * * * * *
说明:
( 1) 一个C程序由一个或多个程序模块组成,
每一个程序模块作为一个源程序文件 。 对较大的程序,一般不希望把所有内容全放在一个文件中,而是将他们分别放在若干个源文件中,再由若干源程序文件组成一个 C程序 。
这样便于分别编写,分别编译,提高调试效率 。 一个源程序文件可以为多个 C程序公用 。
( 2) 一个源程序文件由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成
。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
( 3) C程序的执行是从main函数开始的
,如是在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行 。
( 4) 所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的 。 一个函数并不从属于另一函数,即函数不能嵌套定义 。 函数间可以互相调用,但不能调用main函数 。
main函数是系统调用的 。
( 5) 从用户使用的角度看,函数有两种:
① 标准函数,即库函数 。 这是由系统提供的
,用户不必自己定义这些函数,可以直接使用它们 。 应该说明,不同的 C系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的 。
② 用户自己定义的函数 。 用以解决用户的专门需要 。
( 6) 从函数的形式看,函数分两类:
① 无 参 函 数 。 如例 8.1 中的 printstar 和
print_message就是无参函数 。 在调用无参函数时,主调函数不向被调用函数传递数据 。 无参函数一般用来执行指定的一组操作 。 例如,例 8
,1程序中的 printstar函数 。
② 有参函数 。 在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用 。
§ 8.2函数定义的一般形式
§ 8.2.1,无参函数的定义一般形式定义 无参函数 的一般形式为,
类型标识符 函数名 ()
{
声明部分语句部分
}
在定义函数时要用,类型标识符,
指定函数值的类型,即函数带回来的值的类型。
例 8.1中的
printstar和
print_message函数为 void类型,
表示不需要带回函数值。
§ 8.2.2,有参函数定义的一般形式定义 有参函数 的一般形式为,
类型标识符 函数名 (形式参数表列)
{
声明部分语句部分
} 例如:
int max( int x,int y)
{int z; / *函数体中的声明部分 */
z=x>y?x ∶ y;
return(z);
}
§ 8.2.3 空函数定义 空函数 的一般形式为,
类型标识符 函数名 ()
{ }
例如:
dummy ( )
{ }
调用此函数时,什么工作也不做,没有任何实际作用。在主调函数中写上,dum
my();,表明
,这里要调用一个函数,,而现在这个函数没有起作用,等以后扩充函数功能时补充上。
§ 8.3函数参数和函数的值
§ 8.3,1形式参数和实际参数在前面提到的有参函数中,在定义函数时函数名后面括弧中的变量名称为,形式参数,
(简称,形参,),在主调函数中调用一个函数时,函数名后面括弧中的参数 (可以是一个表达式 )称为,实际参数,(简称,实参,
)。 return后面的括弧中的值 ()作为函数带回的值(称 函数返回值 )。
在不同的函数之间传递数据,可以使用的法:
◆ 参数:通过形式参数和实际参数
◆ 返回值:用 return语句返回计算结果
◆ 全局变量:外部变量大多数情况下,主调函数和被调用函数之间有数据传递的关系。
#include <stdio.h>
void main ( )
{ int max(int x,int y );
/* 对max函数的声明 */
int a,b,c;
scanf( " % d,% d",&a,&b ) ;
c=max ( a,b ) ;
printf( "Max is % d",c ) ;
}
例 8.2调用函数时的数据传递
int max(int x,int y )/ *定义有参函数 max */
{
int z;
z=x>y? x ∶ y;
return( z ) ;
}
运行情况如下:
7,8 ↙
Max is 8
通过函数调用,使两个函数中的数据发生联系关于形参与实参的说明:
(1) 在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。
(2) 实参可以是常量、变量或表达式,如:
max(3,a+b);
但要求它们有确定的值。在调用时将实参的值赋给形参。
(3) 在被定义的函数中,必须指定形参的类型(见例 8.2程序中的,c=max(a,
b);” )。
(4) 实参与形参的类型应相同或赋值兼容。
例 8.2中实参和形参都是整型。如果实参为整型而形参 x为实型,或者相反,则按第 3章介绍的不同类型数值的赋值规则进行转换。
例如实参值 a为 3.5,而形参 x为整型,则将实数 3.5转换成整数 3,然后送到形参 b。字符型与整型可以互相通用。
(5) 在C语言中,实参向对形参的数据传递是,值传递,,单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,
实参单元与形参单元是不同的单元。
在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。因此,
在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。例如,
若在执行函数过程中x和y的值变为10和1
5,而a和b仍为2和3。
§ 8.3.2 函数的返回值通常,希望通过函数调用使主调函数能得到一个确定的值,这就是 函数的返回值 。例如,例 8.2中,
max(2,3)的值是3,max(5,2)的值是 5。赋值语句将这个函数值赋给变量c。
关于函数返回值的一些说明:
(1)函数的返回值是通过函数中的 return语句获得的。
如果需要从被调用函数带回一个函数值供主调函数使用,被调用函数中必须包含 return语句。如果不需要从被调用函数带回函数值可以不要 return语句。
一个函数中可以有一个以上的 return语句,执行到哪一个 return语句,哪一个语句起作用。 return语句后面的括弧也可以不要,
如,,return z ;” 等价于,return (z);,
return语句将被调用函数中的一个确定值带回主调函数中去。见图 8.2中从 return语句返回的箭头。
return后面的值可以是一个表达式。
例如,例 8.2中的函数max可以改写成:
max( int x,int y)
{
return ( x>y?x ∶ y);
}
(2)函数的返回值应当属于某一个确定的类型,
在定义函数时指定函数返回值的类型 。
例如,下面是 3个函数的首行:
int max( float x,float y) /* 函数值为整型 */
char letter( char c1,char c2) /* 函数值为字符型 */
double min( int x,int y) /* 函数值为双精度型 */
在C语言中,凡不加类型说明的函数,自动按整型处理。例 8.2中的max函数首行的函数类型 int可以省写,用 Turbo C 2.0编译程序时能通过,但用 Turbo C
++ 3.0编译程序时不能通过,因为 C++要求所有函数都必须指定函数类型。因此,建议在定义时对所有函数都指定函数类型。
( 3) 在定义函数时指定的函数类型一般应该和
return语句中的表达式类型一致。
如果函数值的类型和 return语句中表达式的值不一致,
则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。
( 4) 对于不带回值的函数,应当用,void”定义函数为,无类型,(或称,空类型,)。这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。此时在函数体中不得出现 return语句。
例 8.3 返回值类型与函数类型不同
# include <stdio.h>
void main()
{ int max( float x,float y) ;
float a,b;
int c;
scanf("%f,%f,",&a,&b);
c=max(a,b);
printf("Max is %d\n",c);
}
int max( float x,float y)
{ float z; /* z为实型变量 */
z=x>y?x ∶ y;
return(z);
}
运行情况如下:
1.5,2.5 ↙
Max is 2
§ 8.4 函数的调用
§ 8.4,1 函数调用的一般形式函数调用的一般形式为,函数名 (实参表列)
如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。
如果是调用无参函数,则,实参表列,
可以没有,但括弧不能省略。
如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。许多C版本是按自右而左的顺序求值,例如 Tubro C ++。
例 8.4 实参求值的顺序
#include <stdio.h>
void main()
{
int f(int a,int b); /* 函数声明 */
int i=2,p;
p=f(i,++i); /* 函数调用 */
printf("%d\n",p);
}
int f(int a,int b) /* 函数定义 */
{
int c;
if(a>b) c=1;
else if(a==b) c=0;
else c=-1;
return(c);
}
如果按自左至右顺序求实参的值,则函数调用相当于f(2,3)
如果按自左至右顺序求实参的值,则函数调用相当于f( 3,3)
对于函数调用
int i=2,p;
p=f(i,++i);
§ 8.4,2函数调用的方式
1.函数语句把函数调用作为一个语句。如例 8.1中的 printstar(),这时不要求函数带回值,只要求函数完成一定的操作。
2.函数表达式函数出现在一个表达式中,这种表达式称为 函数表达式 。这时要求函数带回一个确定的值以参加表达式的运算。例如,c=2 *max(a,b);
3.函数参数函数调用作为一个函数的实参。例如,
m = max (a,max ( b,c ) ) ;
其中 max ( b,c )是一次函数调用,它的值作为 max另一次调用的实参。 m的值是 a,b,c三者中的最大者。
又如,printf ("%d",max (a,b));也是把 max ( a,b )
作为 printf函数的一个参数。
函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。
§ 8.4,3对被调用函数的声明和函数原型
(1) 首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。
但光有这一条件还不够。
§ 4 3对被调用函数的声明和函数原型
(3) 如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)
的后面(在同一个文件中),应该在主调函数中 对被调用的函数作声明 。
(2) 如果使用库函数,还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。
函数原型的一般形式为
(1) 函数类型 函数名 (参数类型 1,参数类型 2) ;
(2) 函数类型 函数名 (参数类型 1,参数名 1,参数类型 2,参数名 2) ;
,声明” 一词的原文是 delaration,过去在许多书中把它译为“说明”。 声明的作用 是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。 (例如函数名是否正确,
实参与形参的类型和个数是否一致)。
注意,函数的“定义”和“声明”不是一回事 。 函数的定义 是指对函数功能的确立,
包括指定函数名,函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。而 函数的声明 的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
# include <stdio.h>
void main()
{ float add( float x,float y);
/ *对被调用函数 add的声明 */
float a,b,c;
scanf("% f,% f",& a,& b);
c= add( a,b) ;
printf(" sum is % f \n",c);
}
float add( float x,float y) / *函数首部 */
{ float z; /* 函数体 */
z=x+y;
return( z);
}
例 8.5 对被调用的函数作声明如果 被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义函数的有关情况,
会根据函数首部提供的信息对函数的调用作正确性检查。
被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义函数的有关情况,
会根据函数首部提供的信息对函数的调用作正确性检查。
改写例 8.5
# include <stdio.h>
float add( float x,float y) / *函数首部 */
{ float z; /* 函数体 */
z=x+y;
return( z);
}
void main()
{
float a,b,c;
scanf("% f,% f",& a,& b);
c= add( a,b) ;
printf(" sum is % f \n",c);
}
§ 8.5 函数的嵌套调用嵌套定义就是在定义一个函数时,其函数体内又包含另一个函数的完整定义 。
C语言不能嵌套定义函数,但可以嵌套调用函数,也就是说,在调用一个函数的过程中,
又调用另一个函数。
例 8.6 用弦截法求方程
f(x)=x3-5x2+16x-80=0 的根
(1) 取两个不同点 x1,x2,如果 f(x1)和 f(x2)符号相反,则 (x1,x2)区间内必有一个根。如果 f(x1)与
f(x2)同符号,则应改变 x1,x2,直到 f(x1),f(x2)异号为止。注意 x1,x2的值不应差太大,以保证
(x1,x2)区间内只有一个根。
(2) 连接 (x1,f(x1))和 (x2,f(x2))两点,此线 (即弦 )交 x轴于 x。
方法:
(3) 若 f(x)与 f(x1)同符号,则根必在 (x,x2)区间内,此时将 x作为新的 x1。如果 f(x)与 f(x2)同符号,则表示根在 (x1,x)区间内,将 x作为新的 x2。
(4) 重复步骤 (2) 和 (3),直到 | f(x)|<
ε 为止,ε 为一个很小的数,例如 10-6\,此时认为 f(x)≈0
N-S流程图分别用几个函数来实现各部分功能,
(1) 用函数 f(x)代表 x的函数,x3-5x2+16x-80.
(2) 用函数调用 xpoint (x1,x2)来求 (x1,f(x1))和
(x2,f(x2))的连线与 x轴的交点 x的坐标。
(3) 用函数调用 root (x1,x2)来求 (x1,x2)区间的那个实根。显然,执行 root函数过程中要用到函数 xpoint,而执行 xpoint函数过程中要用到 f函数。
#include <stdio.h >
#include <math.h >
float f(float x) / * 定义f函数,以实现 f(x) = x3-5x2+16x-80 */
{float y;
y =((x -5,0 )*x +16,0 )*x -80,0;
return(y);
}
float xpoint(float x1,float x2)
/ *定义 xpoint函数,求出弦与 x轴交点 */
{float y;
y =(x1 *f(x2) -x2 *f(x1))
/f(x2) -f(x1));
return(y);
}
float root( float x1,float x2)
/* 定义 root函数,求近似根 */
{float x,y,y1;
y1=f(x1);
do
{ x=xpoint(x1,x2);
y=f(x);
if(y *y1>0) /*f (x )与f (x1 )同符号 */
{ y1=y;
x1=x;}
else
x2=x;
} while(fabs(y)>=0,0001);
return(x);
}
void main()
{ float x1,x2,f1,f2,x;
do
{ printf("input x1,x2:\n");
scanf("%f,%f",&x1,&x2);
f1=f(x1);
f2=f(x2);
} while(f1 *f2>=0);
x=root(x1,x2);
printf("A root of equation is %8,4f \n",x );
} 运行情况如下:
input x1,x2:
2,6
A root of equation is 5.0000
§ 8.6函数的递归调用在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用 。 C语言的特点之一就在于允许函数的递归调用 。 例如:
int f( int x)
{
int y,z;
z=f(y);
return(2 *z);
}
例 8.7 有5个人坐在一起,问第5个人多少岁?
他说比第4个人大2岁。问第4个人岁数,他说比第
3个人大2岁。问第3个人,又说比第2个人大2岁。
问第2个人,说比第1个人大2岁。最后问第1个人,
他说是10岁。请问第5个人多大。
age(5)=age(4)+2
age(4)=age(3)+2
age(3)=age(2)+2
age(2)=age(1)+2
age(1)=10
可以用数学公式表述如下:
age(n)=10 (n=1)
age(n-1)+2 (n>1 )
可以用一个函数来描述上述递归过程:
int age( int n) / * */
{ int c; / * c用作存放函数的返回值的变量 */
if(n==1) c=10;
else c=age(n-1)+2;
return(c);
}
用一个主函数调用 age函数,求得第 5人的年龄。
#include <stdio.h>
void main()
{
printf( ″%d ″,age(5));
}
运行结果如下:
18
例 8.8用递归方法求n!
求n!也可以用递归方法,即5!等于
4! × 5,而4!=3! × 4 … 1!=
1。可用下面的递归公式表示:
n!=1 (n=0,1)
n ·(n-1)! (n>1)
例 8.9 Hanoi(汉诺)塔问题 。这是一个古典的数学问题,是一个用递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有 3个座 A,B,C,开始时A座上有6
4个盘子,盘子大小不等,大的在下,小的在上(见图 8.13)。有一个老和尚想把这6
4个盘子从A座移到C座,但每次只允许移动一个盘,且在移动过程中在 3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求编程序打印出移动的步骤。
为便于理解,我们先分析将A座上3个盘子移到
C座上的过程:
(1) 将A座上2个盘子移到B座上(借助C);
(2) 将A座上1个盘子移到C座上;
(3) 将B座上2个盘子移到C座上(借助A)。
其中第 (2 )步可以直接实现。第1步又可用递归方法分解为:
1.1 将A上1个盘子从A移到C;
1.2 将A上1个盘子从A移到B;
1.3 将C上1个盘子从C移到B。
第 (3 )步可以分解为:
3.1 将B上1个盘子从B移到A上;
3.2 将B上1个盘子从B移到C上;
3.3 将A上1个盘子从A移到C上。
将以上综合起来,可得到移动 3个盘子的步骤为
A → C,A → B,C → B,A → C,B → A,B →
C,A → C。
由上面的分析可知:将n个盘子从A座移到C座可以分解为以下 3个步骤:
(1) 将A上n-1个盘借助C座先移到B座上。
(2) 把A座上剩下的一个盘移到C座上。
(3) 将n-1个盘从B座借助于A座移到C
座上。
程序如下:
#include <stdio.h>
void main()
{
void hanoi(int n,char one,char two,char three);
/* 对 hanoi函数的声明 */
int m;
printf("input the number of diskes:");
scanf(“%d”,&m);
printf("The step to moveing %d diskes:\n",m);
hanoi(m,'A','B','C');
}
void hanoi(int n,char one,char two,char three)
/* 定义 hanoi函数,将n个盘从 one座借助 two座,移到
three座 */
{
void move(char x,char y); /* 对 move函数的声明 */
if(n==1) move(one,three);
else
{ hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three); }
}
void move(char x,char y) /* 定义 move函数 */
{
printf(“%c-->%c\n",x,y);
}
运行情况如下:
input the number of diskes:3↙
The steps to noving 3 diskes:
A-->C
A-->B
C-->B
A-->C
B-->A
B-->C
A-->C
§ 8.7数组作为函数参数
§ 8.7.1 数组元素作函数实参由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素当然可以作为函数的实参,与用变量作实参一样,是单向传递,即,值传送,方式 。
例 8.10 有两个数组a和b,各有10个元素,将它们对应地逐个相比 ( 即a [ 0 ] 与b [ 0 ] 比,
a [ 1 ] 与b [ 1 ] 比 …… ) 。 如果a数组中的元素大于b数组中的相应元素的数目多于 b数组中元素大于 a数组中相应元素的数目 (例如,a[ i] >b[ i] 6
次,b[ i] >a[ i] 3次,其中 i每次为不同的值 ),则认为 a数组大于 b数组,并分别统计出两个数组相应元素大于,等于,小于的次数 。
#include <stdio.h>
void main()
{ int large(int x,int y); /* 函数声明 */
int a [10],b [10],i,n=0,m=0,k=0;
printf( ″enter array a∶ \n ″);
for(i=0;i<10;i++= )
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
printf( ″ enter arrayb ∶ \n ″);
for(i=0;i<10;i++= )
scanf ( ″%d ″,&b[i]);
printf( ″\n ″);
for(i=0;i<10;i++)
{ if( large (a [i],b [i] )== 1) n=n+1;
else if( large (a [i],b [i] )==0) m =m +1;
else k=k+1; }
printf("a[i]>b[i] %d times\na[i]=b[i] %d
times\na[i]<b[i] %d times\n",n,m,k);
if(n>k) printf("array a is larger than array b\ n");
else if (n<k) printf("array a is smaller than array b\n");
else printf("array a is equal to array b\ n");
}
large( int x,int y)
{ int flag;
if(x>y)flag=1;
else if(x<y) flag=-1;
else flag=0;
return( flag);
}
运行情况如下:
enter array a:
1 3 5 7 9 8 6 4 2 0 ↙
enter array b ∶
5 3 8 9 –1 –3 5 6 0 4↙
a[i]>b[i] 4 times
a[i]=b[i] 1 times
a[i]<b[i] 5 times
array a is smaller thann array b
§ 8.7.2 数组名作函数参数可以用数组名作函数参数,此时形参应当用数组名或用指针变量 。
例 8.11 有一个一维数组score,内放
10个学生成绩,求平均成绩。
#include <stdio.h>
void main()
{ float average( float array[ 10]) ; /* 函数声明 */
float score[10],aver;
int i;
printf( ″input 10 scores:\n ″);
for(i=0;i<10;i++=
scanf( ″%f ″,& score[i]);
printf( ″\n ″);
aver= average( score );
printf ( ″ average score is %5,2f \n″,aver);
}
float average ( float array[ 10])
{ int i;
float aver,sum=array[0];
for (i=1;i<10;i++= )
sum=sum+array[i];
aver=sum/10;
return( aver);
}
运行情况如下:
input 10 scores:
100 56 78 98,5 76 87 99 67,5 7
5 97 ↙
average score is 83.40
例 8.12形参数组不定义长度
#include <stdio.h>
void main()
{ float average( float array[ ],int n)
float score_1[5] = {98.5,97,9
1,5,60,55 };
float score_2[ 10] ={ 67.5,89.5,99,69,5,
77,89.5,76.5,54,60,99.5};
printf(“the average of class A is %6.2f\ n”,
average(score_1,5));
printf(“the average of class B is %6.2f\ n”,
average(score_2,10));
}
float average( float array[ ],int n)
{ int i;
float aver,sum=array[0];
for(i=1;i<n;i++=
sum= sum+ array[i];
aver= sum/n;
return(aver);
}
运行结果如下:
the average of class A is 80.40
The average of class B is 78.20
例 8.13 用选择法对数组中 10个整数按由小到大排序。所谓选择法就是先将 10个数中最小的数与 a[ 0]对换 ;再将 a[ 1]
到 a[ 9]中最小的数与 a[ 1]对换 ……
每比较一轮,找出一个未经排序的数中最小的一个。共比较 9轮。
a[0] a[1] a[2] a[3] a[4]
3 6 1 9 4 未排序时的情况
1 6 3 9 4
将 5个数中最小的数 1与 a[ 0]对换
1 3 6 9 4
将余下的 4个数中最小的数 3与 a[ 1]对换
1 3 4 9 6
将余下的 3个数中最小的数 4与 a[ 2]对换
1 3 4 6 9
将余下的 2个数中最小的数 6与 a[ 3]对换,至此完成排序程序实例
#include <stdio.h>
void main()
{ void sort( int array[],int n) ;
int a[10],i;
printf( ″enter the array\n ″);
for(i=0;i<10;i++=
scanf( ″%d ″,&a[i]);
sort(a,10);
printf( ″the sorted array∶ \n ″);
for(i=0;i<10;i++=
printf( ″%d ″,a[i]);
printf( ″\n ″);
}
void sort( int array[],int n)
{ int i,j,k,t;
for(i=0;i<n-1;i++)
{ k=i;
for(j=i+1;j<n;j++)
if( array[j ] < array[k ]=k =j;
t =array[k];
array[k]=array[i];array[i]=t
}
}
§ 8.7.3,多维数组名作函数参数程序如下:
#include <stdio.h>
void main()
{ max_value ( int array[ ][ 4] );
int [3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}};
printf( ″max value is %d\n ″,
max_value(a) );
}
max_value( int array[ ][ 4])
{ int i,j,k,max;
max =array[0][0];
for(i =0;i<3;i++)
for(j=0;j<4;j++=
if( array[i][j]>max)
max = array [i][j];
return(max);
} 运行结果如下:
Max value is 34
§ 8.8局部变量和全局变量
§ 8.8.1局部变量在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。这称为“局部变量”。
float f1( int a) /* 函数 f1 */
{int b,c;
… a,b,c有效
}
char f2(int x,int y) /* 函数 f2 */
{int i,j; x,y,i,j有效
}
void main( ) /* */
{int m,n;
… m,n有效
}
(1) 主函数中定义的变量 (m,n)也只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效。
主函数也不能使用其他函数中定义的变量。
(2) 不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。例如,上面在 f1函数中定义了变量 b和 c,倘若在 f2函数中也定义变量 b和 c,它们在内存中占不同的单元,互不混淆。
(3) 形式参数也是局部变量。例如上面 f1函数中的形参 a,也只在 f1函数中有效。其他函数可以调用 f1函数,
但不能引用 f1函数的形参 a。
(4) 在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为
“分程序”或“程序块”。
说明
void main ( )
{int a,b;
…
{int c;
c=a+b; c在此范围内有效 a,b在此范围内有效
…
}
…
}
§ 8.8.2 全局变量在函数内定义的变量是 局部 变量,而在函数之外定义的变量称为 外部变量,外部变量是全局变量 (也称全程变量 )。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
int p=1,q=5; /* 外部变量 */
float f1(int a) /* 定义函数 f1 */
{int b,c;
…
}
char c1,c2; /* */
char f2 (int x,int y) /* 定义函数 f2 */
{int i,j; 全局变量 p,q的作用范围
… 全局变量 c1,c2的作用范围
}
void main ( ) /* */
{int m,n;
…
}
例 8.15 有一个一维数组,内放10个学生成绩,写一个函数,求出平均分、最高分和最低分。
#include <stdio.h>
float Max=0,Min=0; / * */
void main()
{ float average( float array[ ],int n) ;
float ave,score[10];
int i;
for(i=0;i<10;i++)
scanf( ″%f ″,&score[i]);
ave= average(score,10);
printf(“max=%6.2f\ nmin=%6.2f\ n
average=%6.2f\ n“,Max,Min,ave);
}
float average( float array[ ],int n)
/ * 定义函数,形参为数组 */
{ int i;
float aver,sum=array[0];
Max=Min=array[0];
for(i =1;i<n;i++)
{ if( array[i]> Max) Max= array[i];
else if( array[i]< Min) Min= array[i];
sum=sum+array[i];
}
aver=sum/n;
return(aver);
}
运行情况如下:
99 45 78 97 100 67.5 8
9 92 66 43 ↙
max=100.00
min=43.00
average=77.65
建议不在必要时不要使用全局变量,原因如下:
① 全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
② 使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部变量的值。
在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
③ 它使函数的通用性降低了,因为函数在执行时要依赖于其所在的外部变量。如果将一个函数移到另一个文件中,还要将有关的外部变量及其值一起移过去。但若该外部变量与其他文件的变量同名时,
就会出现问题,降低了程序的可靠性和通用性。一般要求把C程序中的函数做成一个封闭体,除了可以通过“实参 —— 形参”的渠道与外界发生联系外,
没有其他渠道。
例 8.1 6 外部变量与局部变量同名
#include <stdio.h>
int a=3,b=5; /* a,b为外部变量 */ a,b作用范围
void main ( )
{ int a=8; /*a为局部变量 */ 局部变量 a作用范围
printf (″%d″,max (a,b)); 全局变量 b的作用范围
}
max (int a,int b) /*a,b为局部变量 */
{ int c;
c=a> b?a∶ b; 形参 a,b作用范围
return (c);
}
运行结果为
8
§ 8.9 变量的存储类别
§ 8.9,1 动态存储方式与静态存储方式前面已介绍了从变量的作用域(即从空间)角度来分,可以分为 全局变量 和 局部变量 。那么从变量值存在的时间(即生存期)角度来分,又可以分为 静态存储方式 和 动态存储方式 。
所谓静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。这个存储空间可以分为三部分:
程序区
静态存储区
动态存储区在C语言中每一个变量和函数有两个属性,数据类型 和 数据的存储类别 。对数据类型,读者已熟悉(如整型、字符型等)。存储类别指的是数据在内存中存储的方式。存储方式分为两大类:静态存储类和动态存储类。具体包含四种,自动的 (auto),静态的 (stat
ic),寄存器的 (register),外部的 (extern) 。 根据变量的存储类别,
可以知道变量的作用域和生存期。
§ 8.9,2 auto变量函数中的局部变量,如不专门声明为 static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。
函数中的形参和在函数中定义的变量 (包括在复合语句中定义的变量 ),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。例如:
int f( int a) / *定义 f函数,a为形参 */
{ auto int b,c=3; / *定义b、c为自动变量 */
…
}
8.9.3用 static声明局部变量有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,
在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。通过下面简单的例子可以了解它的特点。
例 8.1 7 考察静态局部变量的值。
#include <stdio.h>
void main()
{ int f( int) ;
int a=2,i;
for(i=0;i<3;i++=
printf( ″%d ″,f(a));
}
int f( int a)
{auto int b=0;
static c=3;
b=b+1;
c=c+1;
return(a+b+c);
}
对静态局部变量的说明:
(1) 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放。
(2) 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3)如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)
或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
(4) 虽然静态局部变量在函数调用结束后仍然存在,
但其他函数是不能引用它的。
例 8.1 8 输出1到5的阶乘值。
#include <stdio.h>
void main()
{ int fac( int n) ;
int i;
for(i=1;i<=5;i++)
printf( ″%d! =%d\n ″,i,fac(i));
}
Int fac( int n)
{ static int f=1;
f=f *n;
return(f);
}
8.9.4 register变量一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。 经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
如果有一些变量使用频繁(例如在一个函数中执行
10000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花费不少时间。为提高执行效率,C语言允许将局部变量的值放在 CPU中的寄存器中,需要用时直接从寄存器取出参加运算,
不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做 寄存器变量,用关键字 re
gister 作声明。例如,例 8,19中的程序是输出1到 n的阶乘的值。
例 8,19使用寄存器变量
#include <stdio.h>
void main ( )
{long fac(long);
long i,n;
scanf("%ld",&n);
for(i=1;i<=n;i++)
printf("%ld!=%ld\n",i,fac(i));
}
long fac(long n)
{register long i,f=1; /* */
for (i=1;i<=n;i++)
f=f*i;
return (f);
}
8.9,5用 extern声明外部变量外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。
在此作用域内,全局变量可以为程序中各个函数所引用。编译时将外部变量分配在静态存储区。
有时需要用 extern来声明外部变量,以扩展外部变量的作用城。
1,在一个文件内声明外部变量例 8,20 用 extern声明外部变量,扩展它在程序文件中的作用域。
#include <stdio.h>
void main()
{ int max(int,int); / *外部变量声明 */
extern A,B;
printf("%d\n",max(A,B));
}
int A=13,B=-8; / *定义外部变量 */
int max(int x,int y) / *定义max函数 */
{ int z;
z=x>y?x:y;
return(z);
}
2,在多文件的程序中声明外部变量例 8.21 用 extern将外部变量的作用域扩展到其他文件。 本程序的作用是给定b的值,输入a和m,求
a × b和 am的值。文件 file1.c中的内容为:
#include <stdio.h>
int A; /*定义外部变量 */
void main()
{ int power( int); /*函数声明 */
int b=3,c,d,m;
printf( ″enter the number a and its power m:\n″);
scanf( ″%d,%d ″,& A,&m);
c= A*b;
printf( ″%d *%d=%d\n ″,A,b,c);
d=power(m);
printf( ″%d **%d=%d \n″,A,m,d);
}
文件 file2.c中的内容为:
extern A; /*声明 A为一个已定义的外部变量 */
int power( int n) ;
{ int i,y=1;
for(i=1;i<=n;i++)
y *= A;
return(y);
}
8.9.6用 static声明外部变量有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。
例如:
file1.c file2.c
static int A; extern int A;
void main ( ) void fun (int n)
{ {…
… A=A*n;
}
8.9.7关于变量的声明和定义对变量而言,声明与定义的关系稍微复杂一些。在声明部分出现的变量有两种情况:一种是需要建立存储空间的 (如,int a; ),另一种是不需要建立存储空间的
(如,extern a;)。前者称为,定义性声明,(defining declaration),或简称 定义 ( definition)。
后者称为,引用性声明,(referencing declaration)。广义地说,声明包括定义,但并非所有的声明都是定义。
对,int a;” 而言,它既是声明,又是定义。而对
,extern a;” 而言,它是声明而不是定义。
一般为了叙述方便,把 建立存储空间的声明称定义,而 把不需要建立存储空间的声明称为声明 。
显然这里指的声明是狭义的,即非定义性声明。
例如:
void main()
{extern A; /*是声明不是定义。声明 A是一个已定义的外部变量 */
…
}
int A;
§ 8.9,8存储类别小结
(1) 从 作用域 角度分,有局部变量和全局变量。它们采用的存储类别如下:
局部变量 |自动变量,即动态局部变量
(离开函数,值就消失 )
|静态局部变量 (离开函数,值仍保留 )
|寄存器变量 (离开函数,值就消失 )
|(形式参数可以定义为自动变量或寄存器变量 )
全局变量 |静态外部变量 (只限本文件引用 )
|外部变量
(即非静态的外部变量,允许其他文件引用 )
(2) 从变量存在的时间 (生存期 )来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。
动态存储 |自动变量 (本函数内有效 )
|寄存器变量 (本函数内有效 )
|形式参数 (本函数内有效 )
静态存储 |静态局部变量 (函数内有效 )
|静态外部变量 (本文件内有效 )
|外部变量 (其他文件可引用 )
(3) 从 变量值存放的位置 来区分,可分为,
内存中静态存储区 |静态局部变量
|静态外部变量 (函数外部静态变量 )
|外部变量 (可为其他文件引用 )
内存中动态存储区,自动变量和形式参数
CPU中的寄存器,寄存器变量
(4 ) 关于 作用域 和 生存期 的概念。从前面叙述可以知道,对一个变量的性质可以从两个方面分析,一是变量的作用域,一是变量值存在时间的长短,即生存期。前者是从空间的角度,后者是从时间的角度。
二者有联系但不是同一回事。
(5) static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局变量来说,它使变量局部化 (局部于本文件 ),但仍为静态存储方式。从作用域角度看,凡有 static
声明的,其作用域都是局限的,或者是局限于本函数内
(静态局部变量 ),或者局限于本文件内 (静态外部变量 )。
§ 8.10 内部函数和外部函数函数本质上是全局的,因为一个函数要被另外的函数调用,但是,也可以指定函数不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为 内部函数 和 外部函数 。
§ 8.10.1内部函数如果一个函数 只能被本文件中其他函数所调用,它称为内部函数 。在定义内部函数时,在函数名和函数类型的前面加 static。即
static 类型标识符 函数名 (形参表 )
如 static int fun ( int a,int b )
§ 8.10.2外部函数
(1) 在定义函数时,如果在函数首部的最左端加关键字
extern,则表示此函数是外部函数,可供其他文件调用。如函数首部可以写为 extern int fun (int a,int b)
这样,函数 fun就可以为其他文件调用。 C语言规定,
如果在定义函数时省略 extern,则隐含为外部函数。
本书前面所用的函数都是外部函数。
(2) 在需要调用此函数的文件中,用 extern对函数作声明,表示该函数是在其他文件中定义的外部函数例 8.22 有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现
File.c(文件1)
#include <stdio.h>
void main()
{ extern void enter_string(char str[]);
extern void detele_string(char str[],char ch);
extern void print_string(char str[]);/ *以上 3行声明在本函数中将要调用的在其他文件中定义的 3个函数 */
char c;
char str[80];
scanf("%c",&c);
detele_string(str,c);
print_string(str);
}
file2.c(文件2)
#include <stdio.h>
void enter_string(char str[80]) / * 定义外部函数
enter-string*/
{ gets(str); / *向字符数组输入字符串 */
}
file3.c(文件3)
void delete_string(char str[],char ch) / *定义外部函数
delete_string */
{ int i,j;
for(i=j=0;str[i]!='\0';i++)
if(str[i]!=ch)
str[j++]=str[i];
str[i]='\0';
}
file4.c(文件4)
#include <stdio.h>
void print_string(char str[])
{
printf("%s\n",str);
}
运行情况如下:
abcdefgc ↙ (输入str)
c ↙ (输入要删去的字符)
abdefg (输出已删去指定字符的字符串)