第 6 章 函数 与编译预处理命令在 C程序设计中,通常将一个较大程序分成几个功能较为单一的子程序模块,用函数来完成每个子程序的作用。 C语言是函数驱动语言,C程序都由一个或多个函数构成,其中有且只有一个名为 main的函数,即主函数,C程序总是从 main函数开始执行并最后在 main函数中结束整个程序的运行。
函数常识
1,功能模块,求解较小问题的算法和程序称作
“功能模块”,各功能模块可以先单独设计,
然后将求解所有子问题的模块组合成求解原问题的程序。
2、一个解决大问题的程序,可以分解成多个解决小问题的模块,这就是,自顶向下” 的模块化程序设计方法。
3、用一个或多个 函数 来实现这些功能模块。
例,打印图形,******************
How do you do!
******************
******************
编程:
main()
{ printf(“****************\n”);
printf(“How do you do! \n”);
printf(“******************\n”);
printf(“******************\n”);
}
源程序可简化为,
三个语句一样,可编写成 函数,
void printstar()
{ printf(“**************\n” );
}
main()
{ printstar(); printstar()
printf(,How do you do!\n”),用户自定义的函数
printstar():
}
1,说明几点:
①,一个原程序文件由一个或多个函数组成。一个源程序文件是一个编译单位,即以 源文件 为单位进行编译,而不是以函数为单位进行编译。
②、C程序的执行从 main函数开始,在 main函 数中结束整个程序的运行。 main函数是系统定义的。
③、所有的函数都是 平行 的,不能嵌套定义,但可以互相 调用,但不能调用 main 函数。
模块与函数函数,完成相对独立功能的程序,
标准函数,由系统提供的库函数,不必
①从使用角度分 定义直接引用,如 fabs()等。
用户自定义函数,用以解决用户专门需要。
无参函数,如 printsar();
② 从函数形式分有参函数,如 sin(x),sqrt(x);
不传递数据主调函数 被调用函数传递数据
[例 ]输入三个数,将其按从小到大的顺序输出,
main()
{ float a,b,c,t;
scanf(“%f,%f,%f”,&a,&b,&c);
if(a>b) t=a,a=b,b=t;
if(a>c) t=a,a=c,c=t;
if(b>c) t=b,b=c,c=t;
printf(“%6.1f,%6.1f,%6.1f\n”,a,b,c);
}
用函数实现:
输入 (用 scanf函数 )
主控模块 排序输出 (用 printf函数 )
排序函数 sortabc:
void sortabc( )
{ float t;
if(a>b) t=a,a=b,b=t;
if(a>c) t=a,a=c,c=t;
if(b>c) t=b,b=c,c=t;
}
float a,b,c; /* 定义 a,b,c为全程变量 */
main( )
{
scanf("%f,%f,%f”,&a,&b,&c); /* 输入 */
sortabc( ); /* 排序 */
printf(“%6.1f,%6.1f,%6.1f\n”,a,b,c); /*输出 */
}
模块独立:功能独立,关系简单;
模块设计原则,模块规模适当分解模块要注意层次算法
1.算法,是求解某一类特定问题的方法和步骤。
2.算法的性质
( 1) 算法是一个有限操作序列 。 即算法的有穷性 。
( 2) 算法的每一步都应是确定的,没有二义性 。
( 3) 算法的每一步都应是计算机能进行的有效操作 。
( 4) 有一个或多个输入 。
( 5) 有一个或多个输出,表示问题的解 。
3.算法的描述
( 1) 自然语言
( 2) 框图语言 ( N-S流程图 )
( 3) 计算机语言例:找出 a,b两数中的较大者,并输出。
1、自然语言描述
( 1)输入两个数;
( 2)如果 a>b,则 a max,
否则 b max;
( 3)输出 max的值。
2,N-S 框图语言描述输入 a,b
a>b是 否
a max b max
输出 max的值
3、计算机语言描述:
main()
{ int a,b,max;
scanf(“%d,%d”,&a,&b);
if(a>b) max=a;
else max=b;
printf(“max=%d\n”,max);
}
6.1 库函数
1、定义在不同的头文件中
2、用户使用时,必须用 #include“头文件” 把相应的头文件包含到程序中来。
例 6.1 数学函数库调用举例
#include <math.h>
#include <stdio.h>
main( )
{ double a,b;
scanf (“%lf,,&a);
b = sin (a);
printf(,%6.4lf”,b);
}
注意,
include命令必须以 #开头,文件名用一对双引号,,或一对尖括号 <>括起来,二者的区别是:用 <math.h>表示编译时只按系统标准方式检索文件目录,而用,math.h”形式,则编译系统先从目标文件所在的子目录中找 math.h文件,若找不到再按尖括号包围时的办法重新搜索一次。 include是命令,不是语句,结尾没有分号。
6.2.1 函数定义
C语言函数定义格式如下,
[函数返回值的类型名 ] 函数名 ([类型名 形式参数 1,
类型名 形式参数 2,? ])
/*函数首部 */
{
[说明部分; ] /*函数体 */
[语句部分; ]
}
其中 [ ]内为可选项。注意:函数名、一对圆括号和花括号不能省!
6.2 函数的定义和说明例,
int max(int a,int b)
{ int t;
t=a>b?a:b; /*函数体 */
return t; /*返回语句 */
}
不能写成,int max(int a,b)
{ int t;
t=a>b?a:b; /*函数体 */
return t;
}
函数返回值类型名函数名 参数类型说明及参数列表
1.无参函数根据函数是否需要参数,又可将函数分为无参函数和有参函数两种 。
无参函数的一般形式函数返回值的类型名 函数名 ( void )
{ [说明语句部分; ]
[可执行语句部分; ]
}
例 6.2 构造一个输出一行” *”的函数,
void printstar()
{ printf(“******************\n”);}
2.有参函数有参函数的一般形式函数返回值的类型名 函数名 (数据类型 参数 1[,数据类型参数 2……] )
{ [说明语句部分; ]
[可执行语句部分; ]
}
例 6.3 一个求两个双精度数之和的函数:
double add(double x,double y) /*函数首部 */
{double s; /*说明部分 */
s=x+y; /*语句部分 */
return s; /*返回语句 */
}
例 6.4 求两个数中的大数
(注意:此例中函数调用时的类型转换 )
程序如下,
#include<stdio.h>
max(float x,float y)
{ float z;
z=x>y?x:y;
return z;}
main()
{ float a=1.5,b=0.5;
float c;
c=max(a,b);
printf(“max is %f\n”,c);}
运行结果为:
max is 1.000000
注意,
(1)函数名和形式参数都是用户命名的标识符。在同一程序中,函数名必须唯一;形式参数只要在同一函数中唯一即可,可以与其它函数中的变量同名。
(2)C语言规定,不能在一个函数的内部再定义函数。
(3)对函数类型的说明,必须与 return语句中返回值表达式的类型一致。如果不一致,则以函数类型为准,由系统自动进行转换。如果缺省函数类型,
则系统一律按 int类型处理。
注意,
(4) 空函数 ──既无参数、函数体又为空的函数。其一般形式为:
[函数类型 ] 函数名 (void) {}
如,dump(){}
(5) 带参数的形式参数表中类型和变量必须成对出现,如下面的定义是错误的:
double add(double x,y)
6.2.2 函数的返回值
C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种 。
在函数定义的语句部分,常常有个 return返回语句,函数的返回值也就是 return语句中的表达式的值 。
return 语句的格式:
return(表达式 ); 或 return 表达式 ; 或 return;
功能,1,把 return后面,表达式,的值带给调用函数;
2,把控制转向调用函数;
格式 return;只有功能 2
例如 return(x); return x+y; return(x>y?x:y); return; 都是合法的 。
注意,
(1) 声明为 void型的函数中不能包括带值的 return 语句;主函数体内不能出现 return语句 。
(2)当函数没有 return语句时,以结束函数的大括号 }
作为返回点 。 但这时并不表明函数没有返回值,
这时的返回值是系统给的不确定值 。
(3)除了空值函数以外的所有函数都返回一个值,那么我们是不是非得去使用这个返回值呢? 答案是否定的 。 如果没有用它赋值,那它就被丢弃了 。
(4)在同一函数内,可用根据需要在多处出现 return
语句,但函数第一次遇到 return时就立即停止执行,
并返回到主调函数例 6.6 计算两数之和
#include<stdio.h>
double add(double x,double y)
{ double s;
s=x+y;
return s; }
main()
{ int a,b,c;
a=10;b=20;
c=add(a,b);/*1*/
printf("%f",add(a,b));/*2*/
add(a,b);/*3*/}
运行结果为:
30.0
6.2.3 函数说明和函数原型
1,函数说明的形式函数说明也称为函数声明,使用函数说明语句能够让 C编译程序了解函数返回值类型在 ANSI C新标准中,采用函数原型方式,对被调用函数进行说明,其一般格式如下:
函数类型 函数名 (数据类型 [ 参数名 ][,数据类型
[ 参数名 2]… ]);
函数说明语句其实就是函数定义中的函数首部加上分号,这些内容称为函数原型。
如,float max(float x,float y);
等价于 float max(float,float);
2.函数说明的位置
(1)C语言规定,被调用函数的说明位置,处在该函数被调用前并且是在所有函数的外部时,则后面所有位置上都可以对该函数进行调用。
(2)函数说明放在调用函数内的说明部分,这时作用范围只在本调用函数内部。
例 求两个数中的大数
#include<stdio.h>
main()
{ float a=1.5,b=0.5;
float max(float,float);
float c;
c=max(a,b);
printf("max is %f\n",c);
}
float max(float x,float y)
{ float z;
z=x>y?x:y;
return z;
}
运行结果为:
max is 1.500000
(max)函数说明
6.3 函数的调用
1、调用形式:
函数名(实参表列);
实参与形参个数相等,类型一致。调用函数时两者按顺序一一对应传递数据。
对实参表的求值顺序,自右而左 (TC当中 )。
如,当 i=3时,
printf(“%d,%d”,i,i++); 的运行结果为 4,3。
例,i=2;
p=f(i,++i);
等价于,p=f(2,2);
p=f(2,3);
p=f(3,2);
p=f(3,3);
×
×
×
2、调用方式:
按函数在程序中出现的位置来分有三种方式。
① 函数语句。
只完成一个操作,并不要求函数带回值。
如,printstar();
scanf(“%d”,&a);
② 函数表达式。
出现在表达式中,函数值参与表达式运算。
如,c=2*max(a,b);
y=x+power(x,3);
③ 函数参数。
函数调用作为一个函数的实参。
如,m=max(a,max(b,c));
printf(“%8.2f\n”,power(x,3));
3、说明几点:
在一个函数中调用另一个函数需要具备哪些条件:
① 被调用函数已经存在(库函数或用户自定义);
② 使用库函数或别的文件中的函数,应该在本文件开头用
#include命令将有关的编译预处理信息包含到本文件中来。
例,使用输入输出函数 #include,stdio.h”
使用数学函数 #include,math.h”
③ 若使用用户自己定义的函数,而且该函数与主调函数在同一个文件中,则需 在主调函数中 对被调用的函数的返回值类型作 说明 。
说明形式有三种:
类型名 函数名( 类型 1 变量 1,…,类型 n 变量 n ) ;
作用:告诉系统在本函数中将要用到的某函数的类型。
注意函数说明与函数定义的区别。
如:
float max(int x,int y);
函数说明应与该函数定义中的函数类型与名称、形参的个数、类型、次序相一致。
float max(int,int);
float max();
但有三种情况不需说明:
a,函 数返回值为 整型或字符型 ;
b,被 调用函数定义出现在主调函数之前;
c,已 在所有的函数定义之前(即文件的开头)
说明了函数类型。
6.4 函数的参数例:编写从两整数中求取较大数的程序。
main()
{ int a,b,c;
scanf(“%d,%d”,&a,&b);
c=max(a,b); 调用函数语句,a,b为实参
printf(“a=%d,b=%d\nmax=%d\n”,a,b,c); 注①
}
max(int x,int y) 函数定义,无分号,x,y 为形参
{ int z;
z=x++>y++?x:y; 注①,通过函数调用,使两
printf(“x=%d,y=%d\n”,x,y); 个函数中数据发生联系。
return(z);
}
输入,2,3↙
输出结果,x=3,y=4
a=2,b=3
max=4
a b x y z
3 4
main()
2 a,3 b max(x,y)
c=max(a,b) z=4
输出,x=3,y=4
输出,a=2,b=3
max=4
2 3 2 3 4
关于形参和实参说明如下,
1、形参与实参:在调用函数时,形参才被分配内存单元,
同时实参将数据传给形参。调用结束后,形参所占的内存单元被释放。实参单元仍保留并维持原值。
2、实参可以是变量、常量和表达式。
3、实参与形参应个数相等,类型相容,实参与形参是按位置一一对应地传递数据的 。 (字符型与整型可以互相通用 )
4、调用有参函数时,主调函数和被调函数之间有数据传递关系,而且 实参对形参变量的数据传递是单向传递,只由实参传给形参,反之不行,即形参的改变不会影响实参 。
形参 实参 传送的数据数组名 数组名或数组指针 数组首地址变量 常量、或表达式 表达式的确定值
6.5 函数的嵌套调用和递归调用
6.5.1 函数的嵌套调用函数的嵌套调用是指,在执行被调用函数时,被调用函数又调用了其它函数 。 这与其它语言的子程序嵌套调用的情形是类似的,其关系可表示如右图所示 。
函数的定义是互相平行、独立的,不能嵌套定义,但可以嵌套调用。
例 6.10,已知:
f( x+y) 2
x+y (x<=y)
g(x,y)=
f(x-y) 2
x+y ( x>y)
其中,f(t)=(1+e-t)/(1+et)
求 result=g(2.5,3.4)。
程序如下:
#include,math.h”
float f(float t)
{ float s;
s=(1+exp(-t))/(1+exp(t));
return(s);
}
float g(fioat x,float y)
{ float z,q;
z=x>y?f(x-y)/(x+y):f(x+y)/(x+y);
q=z*z;
return (q); }
main()
{ float x=2.5,y=3.4,result;
result=g(x,y);
printf(“result=%12.2f\n”,result);
}
x y x y z
main( ) g(x,y) f(t)
2.5 x,3.4 y ② ③ x<y ④ ⑤

result=g(x,y) z=f(x,y)/5.9 s=(1+exp(-
5.9))/(1+exp(5.9))
⑨ ⑧ ⑦ ⑥
输出,result= q=z*z
2.5 3.4 2.5 3.4 5.9
main() 函数 g函数 f函数
① ② ③ ④
调用 g函数 调用 f函数 ⑤
⑨ ⑧ ⑦ ⑥
结束 return return
由以上例题我们可以清楚地了解函数嵌套调用的过程。
例,编写求组合数的函数。
)!(!
!
nmn
mC n
m
long fac( int k) /*求阶乘 k!*/
{ long f=1;
int i;
for(i=1; i<=k; i++)
f = f * i;
return f;
}
long comb(int n,int m)
{ long c;
c=fac(m)/(fac(n)*fac(m-n));
return c;
}
主函数如下,
#include <stdio.h>
main( )
{ int n,m;
long c;
scanf(“%d,%d”,&n,&m);
if ( m>=n)
{ c = comb(n,m) ;
printf (“%ld”,c);
}
else printf(“Error!”);
}
6.5.2 函数的递归调用在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用 。
递归调用形式如下,
(1) 直接递归
void a( )
{...…
a ( ); / * 自己调用自己,直接递归 */
...…
}
函数 a()
调用函数 a()
(2) 间接递归
void a( )
{...…
b ( ); / * 调用函数 b */
...…
}
void b( )
{......
a ( ); /* 调用函数 a,间接递归 */
..}
函数 a() 函数 b()
调用函数 b() 调用函数 a()
以上是无终止的函数调用,因此,一个正确的递归调用的函数必须满足以下条件:
① 必须有一个终止条件;
② 一个问题转化成一个新问题,解题方法相似,而且每一步转化都越接近终止条件。
例 6.11 用递归算法计算 n!
(1)递推,从一个已知的事实出发,按一定的规律推出下一个事实。用同一个变量存放推出的结果,给同一个变量赋新的值。
n!=1*2*3*…*(n -2)*(n-1)*n
i 1 2 3 … n 初值,n=1
m m=m*i m=1
程序段为,m=1;
for(i=1;i<=n;i++)
m=m*i;
(2)递归,1 (n=0) 终止条件
n!=
n*(n-1)! (n>1)
求 n!与求 (n-1)!的方法是一样的,只是参数值不同。
long fac( int n)
{ long f ;
if(n==0) f=1; /*递归结束条件 */
else f=n*fac(n-1); /*直接递归调用 */
return f;
}
main( )
{ long y;
int n;
scanf(“%d”,&n);
y = fac(n);
printf(“%d!=%ld”,n,y);}
当输入 3时,函数调用过程如下:
回推阶段
main() fac函数 fac函数 fac函数 fac
函数
n=3 n=2 n=1
n=0
fac(3) fac(2) age(1)
fac(0)
y=fac(3) f=3*fac(2) f=2*fac(1) f=1*fac(0) f=1
6 2 1 1
return(f) return(f) return(f)
return(f)
输出,3!=6 递推阶段例 6.12,汉诺塔问题。
1 2 3
将 n个盘子从 1针移到 3针可以分解为以下三个步骤:
1、将 1上 n-1个盘借助 3针先移到 2针上;
2、把 1针上剩下的一个盘移到 3针上;
3、将 n-1个盘从 2针借助于 1针移到 3针上。
这三个步骤又可以分成两类操作:
1、将 n-1个盘从一个针移到另一个针上 (n>1)。这是一个递归过程。
2、将 1个盘子从一个针上移到另一针上。
void move(int start,int end)
{
printf(“%d-->%d\n”,start,end);
}
void hanoi(int n,int one,int two,int three)
{
if(n==0) return;
if(n==1) move(one,three);
else { hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three); }
}
main( )
{
int n;
printf(“input n:”);
scanf(“%d”,&n);
printf(“\nThe step:\n”);
hanoi(n,1,2,3);
}
运行情况如下:
input n:3↙
the step:
1-->3
1-->2
3-->2
1-->3
2-->1
2-->3
1-->3
例 6.13 有 5个人,第 5个人说他比第 4个人大 2岁,第 4个人说他比第 3个人大 2岁,第 3
个人说他比第 2个人大 2岁,第 2个人说他比第 1个人大 2岁,第 1个人说他 10岁。求第 5
个人多少岁?
分析:
10 (n=1)
age(n)=
age(n-1)+2 (n>1)
程序如下:
#include<stdio.h>
#include<conio.h>
main()
{ int age(int);
clrscr();
printf("%d",age(5));
}
int age(int n)
{
int c;
if(n==1)c=10;
else c=age(n-1)+2;
return c;
}
结果,18
例 6.14 Fibonacci数列问题(求前 12项之和)
分析,1(n=1)
fib(n)= 1(n=2)
fib(n-1)+fib(n-2)(n>1)
程序如下,
#include<stdio.h> main()
fib(int n) {
{ int i,s=0;
int f; if(n==1||n==2)
for(i=1;i<=12;i++) s=s+fib(i);
f=1; printf("n=12,s=%d",s);
else }
f=fib(n-1)+fib(n-2);
return(f); 结果,376
}
例 6.15 反向输出一个整数(非数值问题)
非数值问题的分析无法象数值问题那样能得出一个初值和递归函数式,但思路是相同的。
分析方法:
①简化问题:设要输出的正整数只有一位,则“反向输出”
问 题可简化为输出一位整数。
②对大于 10的正整数,逻辑上可分为两部分:个位上的数字和个位以前的全部数字。将个位以前的全部数字看成一个整体,则为了反向输出这个大于 10的正整数,可按以下步骤:
a、输出个位上的数字;
b、将个位除外的其他数字作为一个新的整数,重复 a步骤的操作。
其中 b问题只是对原问题在规模上进行了缩小而已,故可用递归解决。
将反向输出一个正整数的算法归纳为:
if( n为一位整数)
输出 n;
else
{ 输出 n的个位数字;
对剩余数字组成的新整数重复
“反向输出”操作;
}
程序如下:
#include<stdio.h>
void main()
{ void printn(int x);
int n;
printf("Input n=");
scanf("%d",&n);
if(n<0)
{ n=-n;putchar('-'); }
printn(n);
}
void printn(int x) /*反向输出整数 x*/
{ if(x>=0&&x<=9) /*若 x为一位整数 */
printf("%d",x); /*则输出整数 x*/
else /*否则 */
{ printf("%d",x%10); /*输出 x的个位数字 */
printn(x/10);
/*将 x中的个位数字去掉,形成新的 x后,继续递归操作 */
}
}
执行,Input n=9876
结果,6789
执行,Input n=- 3579
结果:- 9753
注意输入数时,不要超过程序中整型数的取值范围。
6.6.1变量的作用域与生存期
C语言中变量必须先定义后使用,变量的数据类型决定了计算机为变量预留多少存储空间以及该变量上应具有的一组运算。
C语言中,除了对变量进行数据类型说明,
还可以说明变量的存储类型。不同的存储类型可以确定一个变量的作用域和生存期。
6.6变量的存储类型
1.变量的作用域变量的作用域是指变量的作用范围,在 C语言中分为在全局有效、局部有效和复合语句内有效三种。
C语言中所有的变量都有自己的作用域。变量说明的位置不同,其作用域也不同,据此可将C语言中的变量分为局部变量(内部变量)和全局变量(外部变量)。
2,生存期变量的生存期是指变量作用时间的长短,在 C语言中分为程序期、函数期和复合期三种。
6.6.2 变量的存储类型一个完整的变量说明格式如下:
[存储类型 ] 数据类型 变量表;
或 数据类型 [存储类型 ] 变量表;
如 static int x,y;
或 int static x,y;
C语言中变量有 4种存储类型:
自动变量( auto)、寄存器变量 (register)、
静态变量( static)、外部变量( extern)。
用户的存储空间一般分为三个区,如右图所示,例举了一个 C程序 在内存中的存储映象。
根据变量在内存中的不同存放位置,又可以将它们分为自动和静态两类。其中
auto,register描述的是自动类,存储在动态存储区;
static,extern描述的是静态类,存储在静态存储区。
静态存储区存放的变量在编译时分配存储单元,程序执行结束才收回存储单元。
动态存储区中的变量是在程序运行期间根据需要随时动态分配存储空间。
动态存储区(堆栈)
静态存储区程序代码区
6.6.3局部变量
★ 在函数内部或复合语句内部定义的变量,称为局部变量。函数的形参也属于局部变量。
局部变量的作用域是:本函数内部或复合语句内部。所以局部变量也称“内部变量”。
★ 局部变量分为自动变量、寄存器变量、静态局部变量;
1.自动变量
★ 在函数内部或复合语句内部定义的变量,如果没有写明存储类,或使用了 auto说明符,系统就认为所定义的变量具有自动变量类别,有时也称 (动态 )局部变量。
★ 形参属于被调用函数的局部变量。注意:形参缺省的关键字是 auto,但不能将 auto直接加在形参之前。
★ 自动变量的初始化是:每一次调用,形参都以实参为初值,非形参的自动变量在函数体内部或复合语句内部都重新赋初值。所以,未赋初值的自动变量,“无定义”,其值不定。
注意,
当复合语句内部定义的局部变量与所在函数局部变量同名时,在复合语句内部,所在函数的同名局部变量被屏蔽掉,只有复合语句内部的同名变量有效。
例 6.16
#include<stdio.h>
main()
{ int x=1;
{ int x=2;
{int x=3;
printf("%d\n",x);
}
printf("%d\n",x);
}
printf("%d\n",x);
}
运行结果:
3
2
1
2,寄存器变量寄存器变量存放在 CPU中的寄存器中,它们属于自动类变量。
例 6.17 计算 2i,(-3)i的值。 i=0,1,2,……,9
分析:定义函数 power,用以计算 xn的值。
xn=1*x*x*……*x
程序如下:
#include<stdio.h>
#include<conio.h>
double power(int x,register int n)/*n是循环变量,被定义为 register变量 */
{
register int i; /*i是用来控制累乘次数的变量,是寄存器变量 */
int p=1; /*p是用来存放累乘积的变量,p是普通变量 */
clrscr();
for(i=0;i<n;i++)
p=p*x;
return(p);
}
main()
{
register int i; /*i是用来控制累乘次数的变量,是寄存器变量 */
clrscr();
for(i=1;i<10;i++)
printf("i=%d:%.0lf\t,%.0lf\n",i,power(2,i),power(-3,i));
}
运行结果为:
i=1:2,-3
i=2:4,9
i=3:8,-27
i=4:16,81
i=5:32,-243
i=6:64,729
i=7:128,-2187
i=8:256,6561
i=9:512,-19683
3,静态局部变量在函数体(或复合语句)内部,用以下定义格式定义的变量称为静态局部变量:
static数据类型 变量表;
例如,static int a=8;
静态局部变量的初始化只在编译时进行一次,每次调用它们所在的函数时,不再重新赋初值,只是保留上次调用结束时的值 。
若定义但不初始化,则自动赋以 "0 "(数值型)
或 '\0'(字符型)。
例 6.18 求下列程序的输出结果
#include<stdio.h>
main()
{
int f(void);/*函数声明 */
int j;
for(j=0;j<3;j++)
printf("%d\n",f());
}
int f(void)/*无参函数 */
{
static int x=1;
x++;
return x;
}
程序运行结果:
2
3
4
例,分析执行结果
f(int a)
{int b=0; static int c=3;
b++;c++;
printf(“%5d%5d%5d”,a,b,c);
return(a+b+c);
}
main()
{int a=2,k;
for(k=0;k<3;k++)
printf(“%5d\n”,f(a));
}
静态变量只初始化一次结果:
2 1 4 ( a,b,c)
7 (f(a))
2 1 5
8
2 1 6
9
6.6.4 全局变量
★ 当变量定义放在函数体外时,该变量就称为全局变量,全局变量也称为外部变量。
★ 对于全局变量分可使用 extern和 static两种说明符,用 static修饰的全局变量称为静态全局变量(静态外部变量)。
★ 全局变量和静态全局变量都属于静态存储类。生存期都是程序的一次执行,定义和初始化都是在程序编译时进行,其初始化只有一次。若没有初始化,则自动赋以 "
0 "(数值型)或 '\0'(字符型)。
1.全局变量全局变量不属于任何一个函数,其作用域是:从全局变量的定义位置开始,到本文件结束为止。
这里有两个问题:
其一,当全局变量定义在后,引用它的函数在前时,如何使用该全局变量?这就需要把该全局变量的作用域延伸至该函数。
其二,能否使在某文件中定义的全局变量,
在其它文件中无须再次定义而直接使用它呢?
这就需要把全局变量的作用域进行延伸。
C语言可通过外部变量说明达到此目的。
外部变量 说明 的一般形式为:
extern 数据类型 全局变量 [,全局变量
2……] ;
注意,外部变量的定义和外部变量的说明是两回事。
例 6.19 在同一个文件中用 extern说明符来扩展全局变量的作用域程序如下,
#include<stdio.h>
void try_1(void)
{ extern int i; /*先用 extern进行说明,说明 i是在后面定义的外部变量,i的作用域延伸为从此处到整个文件的结尾 */
i=i+5;
}
int i;/*外部变量定义 */
main()
{ try_1();
printf("i=%d\n",i);
}
运行结果为,i=5
例 6.20 在其它文件内用 extern说明符来扩展全局变量的作用域
#include<stdio.h>
int i;/*在 FILE1中定义外部变量 i*/
void func(); /*外部函数说明 */
main()
{ i=5;
printf("file1:%d\n",i);
func();
}
/*FILE2*/
extern int i;/*外部变量说明,说明文件 FILE2中的变量 i是引用 FILE1中定义的外部变量 i*/
void func()
{ printf("file2:%d\n",i);
}
该程序的运行结果是,file1:5
file2:5
例,
#include <stdio.h>
int a=2,b=4; /*a,b为全局变量 */
void f1( )
{ int t1,t2;
t1 = a * 2;
t2 = b * 3;
b = 100;
printf (“t1=%d,t2=%d,b=%d\n”,t1,t2,b);
}
main()
{ int b=4; /* 此 b是局部变量,赋值 */
f1( ); /* 调用函数 f1( ) */
printf (“a=%d,b=%d”,a,b);
}
结论,全局变量与局部变量同名时,局部变量起作用,全局变量被屏蔽(不影响),应小心使用程序输出结果为,
t1=4,t2=12,b=100
a=2,b=4
例 6.21 求程序运行结果
#include<stdio.h>
void num()
{ extern int x,y;/*外部变量说明,说明 num函数中的 x,y是后面定义的全局变量 */
int a=15,b=10;/*变量 a,b是局部变量,其作用域只在 num()
函数内部 */
x=a-b;
y=a+b;
}
int x,y;/*全局变量定义 */
main()
{ int a=7,b=5;/*变量 a,b是局部变量,其作用域只在 main()函数内部 */
x=a+b;
y=a-b;
num();
printf("%d,%d\n",x,y);
}
结果,5,25
2.静态全局变量
★ 当用 static说明符说明全局变量时,此变量就称为静态全局变量或静态外部变量。
★ 此时 static的作用不是把全局变量改为静态存储,因为它本身就是静态存储类,而是限制了它的作用域只能在本文件内,不能用 extern说明符使其作用域扩展到程序的其它文件中。
★ 静态外部变量允许程序的一部分对其它部分充分隐蔽,这有利于管理大型复杂程序,
程序员不必担心因全局变量重名而引起混乱。
变量存储类型小节静态动态存储方式程序整个运行期间函数调用开始至结束生存期编译时赋初值,只赋一次每次函数调用时赋初值自动赋初值 0或空字符不确定未赋初值静态存储区动态区存储区 寄存器局部变量 外部变量作用域 定义变量的函数或复合语句内 本文件 其它文件
局部变量默认为 auto型
register型变量个数受限,且不能为 long,double,float型
局部 static变量具有全局寿命和局部可见性
局部 static变量具有可继承性
extern不是变量定义,可扩展外部 变量作用域
register 局部 staticauto 外部 static 外部存储类别
6.7 内部函数、外部函数
C语 言根据函数能否被其它源文件中的函数调用,
将函数分为内部函数和外部函数 。
6.7.1 内部函数 ( 又称静态函数 )
如果一个函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,则称为内部函数 。
定义一个内部函数,只需在函数类型前再加一个,static”
关键字即可,定义格式如下:
static 函数类型 函数名 (函数参数表 )
{…… }
如 static int fun(a,b,c) {…… }
关键字,static”,译成中文就是,静态的,,所以内部函数又称静态函数 。 但此处,static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件 。
6.7.1 外部函数在定义函数时,如果没有加关键字,static”,或者冠以
,extern”,则表示此函数是外部函数,其定义格式为:
[extern] 函数类型 函数名 (函数参数表 )
{…… }
其中 []内为可选项 。 如
int fun(a,b,c) {…… } 或 extern int fun(a,b,c) {…… }
调用外部函数时,必须对被调用的外部函数进行说明:
[extern] 函数类型 函数名 (参数类型表 [函数名 1[,参数类型表
2…… ];
其中 []内为可选项 。
6.8 编译预处理命令
“编译预处理,是 C语言编译系统的一个组成部分 。
编译预处理是在 编译前 由编译系统中的预处理程序对源程序的预处理命令进行加工 。
源程序中的预处理命令均以,#”开头,结束 不加分号,以区别源程序中的语句,它们可以写在程序中的任何位置,作用域是自出现点到源程序的末尾 。
预处理命令包括:
宏定义 #define
包含文件 #include
条件编译 #if… #else… #endif
#ifdef… #else… #endif
6.8.1宏替换
1、不带参数宏定义形式为,#define 宏名 替换文本如,#define PI 3.14159
/*定义后,可以用 PI来代替串 3.14159*/
宏定义的作用,在一个宏定义行之后,该程序中宏名就代表了该字符串 。
说明:
① 可以用 #undef命令终止宏定义的作用域 。
例如,#undef PI
宏体
② 宏定义的嵌套使用
# define R 3.0
# define PI 3.1415926
# define L 2*PI*R /*宏体是表达式 */
# define S PI*R*R
main ( )
{
printf ("L=%f\nS=%f\n",L,S);
}
程序运行结果如下:
L=18.849556 (应换行) S=28.274333
③ 双引号内的与宏同名的字母不作宏展开不替换
2*PI*R替换 L
PI*R*R替换 S
2、带参数的宏定义
1.带参数的宏定义的一般形式为
# define 宏名 (参数表) 字符串如:
#define S (a,b) a*b
#define PR (x) printf(" s=%f\n”,x)
2.带实参的宏名被展开时,宏名被所定义的宏体替换,宏体中的形参按从左到右的顺序被实参替换。
例如,
area = S (3,2);
PR(area) ;
area=3*2;
printf(" s=%f\n”,area) ;
例,若 a,b,c,d,t均为 int型变量,写出执行以下程序段后的结果 。
#define MAX( A,B) ( A) >( B)? ( A),( B)
#define PRINT( Y) printf(“Y=%d\n”,Y)
a=1; b=2; c=3; d=4;
t=8*MAX( a+b,c+d) ;
PRINT( t) ;
先计算 t的值,
t = 8*( a+b) >( c+d)?( a+b):(c+d)
= 8*(1+2) > (3+4)? (1+2):(3+4)
= 3
因此,输出结果为,
Y=3
3、宏定义与函数的区别
(1) 引用宏只占编译时间,不占运行时间 。
(2) 引用宏没有返回值 。
如,#define squ(n) n*n
void main (void)
{
printf (" %f\n",27.0/squ(3.0));
}
程序的输出结果为,27.000000
(3) 宏替换的形参无类型。
27.0/3.0*3.0
(4) 实参为表达式的情况。 函数调用是先计算出实参的值,再将值传递给形参;宏的引用是用表达式替换形参,
例如,
#define S (a,b) a*b
引用,S(a+c,b+c)
展开后的表达式为,a+c*b+c
使用宏替换应注意的问题
(1) 宏名与宏体之间用空格相隔所以宏名中 不能含有空格 。如有宏定义:
#define f (x) ((x) – 1) /*宏名 f(x)中有空格 */
进行宏替换时,编译系统会认为 f是宏名,(x)
((x) – 1)是宏体,因此会用 (x) ((x) – 1)替换所有的 f。
(2)宏名尽管是字符串,但不能用引号括起来
(3) 在宏定义中结尾不能有分号” ;,
如,
#define PI 3.14159; /*以分号结尾 */
则对程序中语句:
printf (“%f”,PI * 10 * 10);
预处理后将会被替换为:
printf (“%f”,3.14159; * 10 * 10);
显然这是错误的。
(4) 宏定义中的参数尽量用圆括号括起来以免错误例,求平方的宏定义如果写成,
#define SQR(x) x*x
则程序中若使用 SQR (x + 1);
则替换之后会得到 x + 1 * x + 1,显然并不能得到预期的结果 (x + 1)2。所以在宏定义时,一定要用圆括号将参数括起来。
(5)较长的宏定义在一行中写不下时,要在本行结尾使用反斜杠表示续行。
如:
#define OUTPUT printf (“This is an interesting
program which teaches\
pepole how to use #define command”)
这是比较特殊的,因为在 C语言中一般情况下不需要有续行的标识,而预处理是个例外。
(6)宏定义可以写在程序中的任何地方,但因其作用域为从定义之处到文件未尾,所以一定要写在程序引用该宏之前,通常写在一个文件之首。
6.8.2 文件包含 #include
1,文件包含是指一个源文件可以将另一个源文件的全部内容包含进来 。
2,#include命令有两种格式 。
(1) #include <文件名 > (到系统规定的路径去查找该文件 )
(2) #include,文件名,( 先查当前目录,若无,再到系统指定的路径去查找 )
6.8.3 条件编译
1.控制条件为常量表达式的条件编译,有以下几种形式:
(1)# if 常量表达式程序段
#endif
其功能是,当常量表达式为非0时,程序段被编译 。 否则,程序段不被编译 。
( 2 ) # if 常量表达式程序段1
# else
程序段2
#endif
其功能是,当常量表达式为非0,程序段1被编译 。 否则,编译程序段2 。
(3 )# if 常量表达式1
程序段1
#elif 常量表达式2
程序段2
......
#elif 常量表达式n
程序段 n
#else
程序段 n+1
#endif
2,控制条件为定义标识符的条件编译
1 ) # ifdef 标识符程序段
#endif
其功能是,当标识符在该条件编译结构前已定义过时,程序段被编译 。 否则,
程序段不被编译 。
( 2 ) # ifdef 标识符程序段1
# else
程序段2
#endif
其功能是,当标识符在该条件编译结构前已定义过时,程序段
1被编译 。 否则,编译程序段2 。
( 3 ) # ifndef 标识符程序段1
# else
程序段2
#endif
其功能是,当标识符在该条件编译结构之前没有被# define定义过时,程序段1被编译;否则,编译程序段2
例 求 n个由键盘输入的整数之和 。 数目 n可以由程序定义,若程序没有定义数目 n,则从键盘输入 。
#include,stdio.h”
#define N 2
main ( )
{
int i,num,n;
int sum = 0;
#ifdef N
n = N;
#else
printf (“please Enter the number n:\n”);
scanf (“%d”,&n);
#endif
printf (“please Enter %d numbers\n”,n);
for (i = 0; i < no; i ++)
{
scanf (“%d”,&num);
sum = sum + num;
}
printf (“sum = %d\n”,sum);
}
程序输出结果:
please Enter 2 numbers
2 3
sum = 5
程序中的条件编译命令表示,首先判断符号常量 N 是否被定义,
若已被定义,则只将程序段:
n = N;
编译成目标代码。
否则将只编译程序段:
printf (“please Enter the number n:”);
scanf (“%d”,&n);
这时,程序将只接收键盘的输入作为要输入数据的个数 。
如果删去例中的预编译命令,#define N 2
则在程序运行时会出现如下提示信息,要求输入数据的个数 n:
please Enter the number n:
3 ( 假定输入为 3)
please Enter 3 numbers
2 3 4 ( 输入 3个整数 )
sum = 9 ( 输出结果 )