$ u( \+ Y q3 l7 H$ O2. 在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢? 在第四章中我们曾介绍过,数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送, 也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。图5.1说明了这种情形。图中设a为实参数组,类型为整型。a占有以2000 为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送, 把实参数 组a的首地址传送给形参数组名b,于是b也取得该地址2000。 于是a,b两数组共同占有以2000 为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占相同的两个内# \+ l- F3 h, G( ]" g
存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a等于b。 9 Q) q" r9 a6 h[例5.5]数组a中存放了一个学生5门课程的成绩,求平均成绩。float aver(float a[5]) # }( }" i$ ~' F" W/ Y<FONT color=#009900>{2 G" Y( K# L# R2 V5 }" F0 U: Q
int i; . m( f1 T$ p0 B5 P* Tfloat av,s=a[0]; 3 U; l& f5 e8 M1 s& x1 E
for(i=1;i<5;i++) 3 j+ V$ z# Z7 P# Ss=s+a; # U' s- b3 P: s/ V; U) nav=s/5; % ?3 W1 m2 ?" ~1 c$ Freturn av; - E4 [/ e6 z5 u9 `}# Z5 u9 q o7 r; v( {
void main(); W+ d/ G2 R& R
{ 2 I. ~2 U$ K7 D& |& pfloat sco[5],av;% d) b" S+ A2 Q- o5 i) Y
int i; ! { t3 m3 e+ i& F/ a# |0 M9 Zprintf("\ninput 5 scores:\n"); * Z; x. [$ T+ @" h3 W3 tfor(i=0;i<5;i++)/ Y$ Q& S# J7 K. _. _2 B; L7 H
scanf("%f",&sco); ! g+ |' i) u3 F4 V Iav=aver(sco);, B2 S, E3 x2 `$ s) @1 I8 E6 c1 b# m
printf("average score is %5.2f",av); % X6 i% H I( e0 [% n}/ Q1 b. h ?' g* [ x/ x
float aver(float a[5])0 l& d' y( \$ x: H2 `2 M
{ ……8 ?5 w2 y% Z/ m
} 4 ~+ a- I2 u4 K1 z2 b! L7 S$ @: Tvoid main() * V% T$ K) j. ]3 o{$ t1 A+ H$ P$ x9 ~
…… Q: O. p' }$ r( w4 i5 Vfor(i=0;i<5;i++)+ L$ ?5 C" d3 O
scanf("%f",&sco); : i- A' {6 N7 T& y$ a: uav=aver(sco);6 O0 I8 R1 w. O& g- x W3 L
……( u2 N: h( z& n) r* j! E
}</FONT> 6 y. W/ P+ U8 i! T 本程序首先定义了一个实型函数aver,有一个形参为实型数组a,长度为5。在函数aver中,把各元素值相加求出平均值,返回给主函数。主函数main 中首先完成数组sco的输入,然后以sco作为实参调用aver函数,函数返回值送av,最后输出av值。 从运行情况可以看出,程序实现了所要求的功能 ; e+ x8 Y" z2 _ q4 m7 i1 {) W3 \, _& a& I0 |" J
3. 前面已经讨论过,在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同, 而形参的值发生改变后,实参并不变化, 两者的终值是不同的。例5.3证实了这个结论。 而当用数组名作函数参数时,情况则不同。 由于实际上形参和实参为同一数组, 因此当形参数组发生变化时,实参数组也随之变化。 当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。为了说明这种情况,把例5.4改为例5.6的形式。[例5.6]题目同5.4例。改用数组名作函数参数。 9 f! P3 |7 f2 s0 t& c1 c! D" y<FONT color=#009900>void nzp(int a[5]) * j7 k% V/ `2 Q- f- N7 `{$ b$ x F/ {- b l7 _# U
int i; % {+ z: L& {% b. G% d- I4 hprintf("\nvalues of array a are:\n");) {2 D( g2 ~1 w, @: v: C
for(i=0;i<5;i++) 0 ~! f: d4 c" X/ l{, F( P8 r9 `5 H7 f
if(a<0) a=0; " F. z- R. Q: J6 Cprintf("%d ",a);, B3 s* U' S" I2 M
} ) O; Y" K/ ]+ n* @- O}7 f* U4 \: h! }& ?# t
main()6 o4 c( I0 @$ n2 I6 W* ]
{$ L: \6 j$ L. K- T
int b[5],i;* f, \$ x% n! i [ M! M# i7 | X5 D
printf("\ninput 5 numbers:\n");8 F( K# h+ _3 m1 R9 v) N4 ~
for(i=0;i<5;i++)+ s2 r8 x0 {0 G3 ~" \! w
scanf("%d",&b);' @# |! Q( b7 C: g! C
printf("initial values of array b are:\n"); 2 z0 z. T j) d, _& Y/ afor(i=0;i<5;i++) & X; |2 N9 M: V) O7 w& ~3 n+ J6 Mprintf("%d ",b);; y/ }% z6 v' }* f1 [% _" N
nzp(b);1 F5 b0 @. Q3 s% b3 q5 `1 B7 ?
printf("\nlast values of array b are:\n"); ( g8 ?# D2 i b% t1 r2 {+ X0 h8 }9 I: Rfor(i=0;i<5;i++) - ?" i, u% Q4 [printf("%d ",b);. \5 Q, P( M; a- M9 Y2 j
} 9 C$ Z- s/ u7 Q! R9 S$ b* Zvoid nzp(int a[5]) + B% R, {8 ]4 E4 Y" }6 S, d{ …… " C' U: Q& a! K! j! V2 m3 c9 n& \} # v: U' E3 C+ F$ e# J" T( e1 kmain()7 y/ A% G$ U' g. O5 J3 i
{ " b' x+ v$ W! ~( Q E- zint b[5],i;& Q- g0 I! F" `. R
……$ E$ y# v) ?1 }4 `% D l
nzp(b); 4 L! N* J9 [" X: G…… 5 l: t M& c4 L; e}</FONT>* Q- C7 T9 ]$ [' r
本程序中函数nzp的形参为整数组a,长度为 5。 主函数中实参数组b也为整型,长度也为5。在主函数中首先输入数组b的值,然后输出数组b的初始值。 然后以数组名b为实参调用nzp函数。在nzp中,按要求把负值单元清0,并输出形参数组a的值。 返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b 的初值和终值是不同的,数组b 的终值和数组a是相同的。这说明实参形参为同一数组,它们的值同时得以改变。 用数组名作为函数参数时还应注意以下几点: 2 [2 N# {4 q& x) v- }1 za. 形参数组和实参数组的类型必须一致,否则将引起错误。$ D+ C/ E; ]) V- t6 v3 P j
b. 形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。如把例5.6修改如下: 1 X+ k- Y: A1 J9 T; Z<FONT color=#009900>void nzp(int a[8]) ( n6 c* n/ F+ [" n- q' b B{ 3 w3 m" R7 W5 C4 a, F3 [2 ?int i;0 G* d' L, m+ |: K1 U
printf("\nvalues of array aare:\n");) @9 F" `1 ^" `$ y
for(i=0;i<8;i++)# i% q, `' H e1 n1 [
{; \4 s2 c4 R- g% r V1 J
if(a<0)a=0; 3 ? G: ^! @* X( @# e6 sprintf("%d",a); 3 G! {# P9 G, ~( F% }} ) k- T% s& w# M" Q; o% I}4 \ C9 z2 G; l7 }, m H0 f% K; Y
main()* f7 B2 x3 i8 M* t! K; |/ \& t
{ 4 i4 n8 i/ S% B! x) d4 _8 [5 m+ V) x8 Qint b[5],i; - }, e3 {7 Y# |7 t7 Kprintf("\ninput 5 numbers:\n");. v$ ]2 q& h2 d% R. P
for(i=0;i<5;i++) & t$ T$ O5 T9 M- F- B# Oscanf("%d",&b);3 N1 r0 X. q6 i5 v/ d( X
printf("initial values of array b are:\n"); ( F6 b; p% m/ r1 E" rfor(i=0;i<5;i++)9 |/ {: t( m' ?: l0 `$ b8 Z
printf("%d",b);+ s; J( h9 `; U$ S
nzp(b); : R* l# x/ S- O, }" n7 W3 Yprintf("\nlast values of array b are:\n"); $ }0 @) H% f9 O4 Ffor(i=0;i<5;i++) 9 p1 L8 x4 W9 S8 i! L, c& ]printf("%d",b);% I( I; F: ~ r S
} 5 h/ C8 O) _) J</FONT> 本程序与例5.6程序比,nzp函数的形参数组长度改为8,函数体中,for语句的循环条件也改为i<8。因此,形参数组 a和实参数组b的长度不一致。编译能够通过,但从结果看,数组a的元素a[5],a[6],a[7]显然是无意义的。c. 在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。 7 X+ p. c m9 y T, i8 N( v例如:可以写为: s- l- y; l Q% m
void nzp(int a[]) 3 h& r! x* X2 Q# ~或写为/ ~/ ]- X; t; s w' R; l
void nzp(int a[],int n) $ A& y. k$ G6 v! \3 G- U: M* ?; U( d 其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。: }5 M, q, C6 L
由此,例5.6又可改为例5.7的形式。# ~2 W1 l* d" J/ R. n
<FONT color=#009900>[例5.7]void nzp(int a[],int n) $ ~) C& F# r2 k- A4 K$ ?- N{ , U4 l9 v0 |/ wint i; - K e5 ]2 w: P: aprintf("\nvalues of array a are:\n");6 o: [5 d3 F" T! j
for(i=0;i<n;i++)8 V9 d: V: x$ O6 i
{8 T) z. E9 J$ g+ M- m# [; ]
if(a<0) a=0;! B8 r. V$ C: B T4 W5 j7 \
printf("%d ",a); , |+ T( a6 R; D0 [0 v} ! @- d* O( ]2 ^# _) @0 y( f! R} z6 Z0 t$ |( R! D$ S5 L! X
main() 4 ?5 N0 R5 O! o% R! H9 g{ 8 Z# C/ u" B9 f5 R6 Cint b[5],i;: [2 C8 G8 X( G) o o& W
printf("\ninput 5 numbers:\n");1 A3 y; z q2 \4 U7 _1 u4 A
for(i=0;i<5;i++) : U& T0 {8 u* h$ }scanf("%d",&b);/ g: y; K# ?, |8 i: M! i M4 B
printf("initial values of array b are:\n"); , A( i( Q! X [! Cfor(i=0;i<5;i++) N9 c: b4 F D1 ^printf("%d ",b); 0 r; ]" ?, v" N* } |/ mnzp(b,5);+ P/ S1 O* X4 S/ G& K! Z3 G
printf("\nlast values of array b are:\n");- C! {; l4 c, m6 @
for(i=0;i<5;i++)5 l0 h8 o0 u* T3 ?' G! e
printf("%d ",b);4 x( ~. E0 t- N# a# ~* N3 c
} 3 V4 x5 X6 @) E# z2 Cvoid nzp(int a[],int n) 7 ~: e# c5 K/ x1 G2 g{ ……& u" ]7 c8 J7 i0 T% n. H
} 8 d m" V8 B' @% y1 [( V- a9 nmain() + f3 F& x- {) q7 `' U: l{* U. Y2 Y0 s$ ?
……3 X/ s0 f7 R P, F, N9 E
nzp(b,5);* a& e6 |' b+ B1 H
…… + ~/ v' w9 g' @, R9 a}, X5 t' b) u# Q3 K5 @ B
</FONT>本程序nzp函数形参数组a没有给出长度,由n 动态确定该长度。在main函数中,函数调用语句为nzp(b,5),其中实参5将赋予形参n作为形参数组的长度。 7 p6 r( {$ N: N1 t3 g wd. 多维数组也可以作为函数的参数。 在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的。 ! v" x4 A) A- K# N. z8 y2 L2 K
int MA(int a[3][10])3 v+ j5 m" x, j b7 h' R8 d- t( o0 [
或 6 H- z% N! P- F8 Bint MA(int a[][10])1 R& _7 D( m9 }5 Z# f
( a: T) w8 K% ^ z' N% {<FONT color=#ff0000>函数的嵌套调用</FONT>, [; Z. ~0 b# n% O
8 ?, \ r x0 ^$ H0 L; W' P; @ C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。 但是C语言允许在一个函数的定义中出现对另一个函数的调用。 这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。 这与其它语言的子程序嵌套的情形是类似的。其关系可表示如图5.2。 0 ` V2 t6 {$ r+ G9 T# S* ^$ C1 c+ `# b; b
图5.2表示了两层嵌套的情形。其执行过程是:执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b 函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a 函数执行完毕返回main函数的断点继续执行。 9 `$ f3 H. z; M3 R F( e `[例5.8]计算s=2?2!+3?2! ) d% h# O: }: Z3 }2 a% Y: z本题可编写两个函数,一个是用来计算平方值的函数f1, 另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值, 再在f1中以平方值为实参,调用 f2计算其阶乘值,然后返回f1,再返回主函数,在循环程序中计算累加和。- n9 M. Z( j ]; C3 {
<FONT color=#009900>long f1(int p) 8 b! ?5 i, z7 |. Y f {{ ! `4 C, u x# a% K! Yint k;- L& p* D: P. L2 d6 x
long r;3 z/ I1 ]0 D/ x6 m7 u& g R
long f2(int); w+ A+ f" O+ ]) p3 f5 \% Y
k=p*p;9 G8 |/ Q$ |4 n2 y- G8 I
r=f2(k); * \8 Y* Y/ ~" |: ireturn r;1 X! F# _* _& A3 r5 J
}% x3 x: G* [/ e9 R
long f2(int q)2 D3 Q5 X& l, a e3 U2 T
{ . c4 f: R4 x6 C+ b2 m2 f& R& llong c=1;0 L7 h" [& I5 Z6 F: d& Y
int i; + r" s+ _* n h6 _+ Mfor(i=1;i<=q;i++) 1 q* ~& B7 f6 {/ M, v/ |6 hc=c*i; 9 O" x% X2 z' D* r+ t; v; p, `! M$ lreturn c; ( v) ^: l% y& i2 N) f. z} ' h/ I- n' p! Y9 W6 ~. xmain(): _$ w7 }0 t1 V) w
{& s" y6 {- B6 |$ {
int i;/ v) d( D. V7 R: h8 c) @
long s=0; , n( j- ~! @3 k; E. \( wfor (i=2;i<=3;i++)$ a( `5 _* [$ P3 }- k
s=s+f1(i);; x( j" R3 g* y8 S$ k/ |
printf("\ns=%ld\n",s);3 Z1 \7 Q0 s1 b2 o7 C
}) p5 W6 C5 `* c, M& j% h- A
long f1(int p)2 L! T5 _- [' ~, C0 i$ L
{" E- ]) u0 y$ H5 `! @6 E
……. H" C c t; s- ?+ g
long f2(int);+ ?7 j' | p. Z' e, L$ N( Y+ s( W; w
r=f2(k);. k: r' [) Y' y! y8 k8 l
…… 7 }+ |, W/ {& ~$ m' E, e} ( y* ~9 V Z/ Slong f2(int q)9 ~, N0 E5 m1 ~: z2 Z! o6 w
{ 0 c( x7 e& s9 Z g$ L……) C" v* @9 `& I4 e. p
} 2 B% A0 n% V. T7 N+ N5 w6 I) r: T Rmain() % {9 w( X @- ] p1 k* ^5 a{ …… , ~0 b8 x6 P* x# Y9 @s=s+f1(i);( X a: \- u2 h7 H9 ], A g: v. E1 g
……+ E8 ]9 g+ h! ]8 ^; S! T- Q
} - p3 l# N% [$ v3 o5 Y</FONT> 在程序中,函数f1和f2均为长整型,都在主函数之前定义, 故不必再在主函数中对f1和f2加以说明。在主程序中, 执行循环程序依次把i值作为实参调用函数f1求i?2值。在f1中又发生对函数f2的调用,这时是把i?2的值作为实参去调f2,在f2 中完成求i?2! 的计算。f2执行完毕把C值(即i?2!)返回给f1,再由f1 返回主函数实现累加。至此,由函数的嵌套调用实现了题目的要求。 由于数值很大, 所以函数和一些变量的类型都说明为长整型,否则会造成计算错误。 ' n* I2 `! W/ T! p8 E' e5 C0 q7 t' C6 i7 L
<FONT color=#ff0000>函数的递归调用</FONT> 1 |) k8 i5 ?( ^; T+ v, R % e; z+ E' |% Z2 H9 J7 f' h% W 一个函数在它的函数体内调用它自身称为递归调用。 这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中, 主调函数又是被调函数。执行递归函数将反复调用其自身。 每调用一次就进入新的一层。例如有函数f如下:; k. C7 @: b7 a% ~2 G
<FONT color=#009900>int f (int x) 4 l5 r' k+ m8 A/ o. S{ + [: X* @# w, I( [int y;, X# i, T. J E$ L4 j2 i
z=f(y); + }- n% q! {2 U7 ?# C1 v0 @, [) Zreturn z;: D+ N8 _- A5 T4 t
} . q6 R" i# V4 F8 N5 D0 _; c. @</FONT> 这个函数是一个递归函数。 但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行, 必须在函数内有终止递归调用的手段。常用的办法是加条件判断, 满足某种条件后就不再作递归调用,然后逐层返回。 下面举例说明递归调用的执行过程。1 M2 Q1 B' `" |' J5 I& l, ?
[例5.9]用递归法计算n!用递归法计算n!可用下述公式表示:/ ], j% |* `' e9 s- U
n!=1 (n=0,1)' r; Q5 E |0 w5 B9 W" k' P& j9 q6 w
n×(n-1)! (n>1)& \( }$ x& z9 C" f, b/ o! q
按公式可编程如下: 5 F6 X4 e# j5 j( r/ d8 w# ]; g) U<FONT color=#009900>long ff(int n) 5 Y& K2 _% ]1 E( C4 ]9 [: V. q{ 0 i' G' v6 l! C# l+ t0 @) zlong f;0 C( P. b6 i/ R) b- S; @
if(n<0) printf("n<0,input error");5 F3 b" G4 ^) o) J2 v5 r
else if(n==0||n==1) f=1; " x" D; \8 k5 {else f=ff(n-1)*n;# f; p2 ^9 u: @. w
return(f); 7 P9 b$ u- }8 U+ j& m- t4 v# S} 5 \* R! F( ~9 J7 p1 ~6 U# pmain() * W5 H' V* x. f{/ u3 g5 y0 Y# n! t0 j
int n; ( W& R3 A+ ?4 x9 t. l glong y; 7 A+ m& a3 c4 C3 n$ P9 i' Sprintf("\ninput a inteager number:\n"); q9 h2 p3 S5 S9 x% b5 f4 @( d
scanf("%d",&n);0 R5 M" P/ R' v4 _* e
y=ff(n);6 M# r# v( S; q4 a$ T1 p, p& Y2 x0 s$ C
printf("%d!=%ld",n,y); 6 N s; \2 X1 `5 I. U} " [% F: A( {$ x/ @8 d' |' }long ff(int n). W/ _0 O+ o1 }3 P7 C: G9 J( `
{ …… 2 [( V! V; `* E. \, x$ Telse f=ff(n-1)*n; 2 ~% o; H4 Z6 U5 ?+ w+ s…… U7 R3 U7 A+ R! ~}8 ^1 W5 c% Y* l) a* b8 d. x/ h
main() 1 R! y# V9 ~; o0 T; q{ …… * i6 y& _: w: D& z, l5 s4 hy=ff(n); 3 Q" m2 v) z2 y1 x5 V……6 X& R1 A0 u q, E. O7 E% \
}</FONT> & t$ b) n1 D- F# i
程序中给出的函数ff是一个递归函数。主函数调用ff 后即进入函数ff执行,如果n<0,n==0或n=1时都将结束函数的执行,否则就递归调用ff函数自身。由于每次递归调用的实参为n-1,即把n-1 的值赋予形参n,最后当n-1的值为1时再作递归调用,形参n的值也为1,将使递归终止。然后可逐层退回。下面我们再举例说明该过程。 设执行本程序时输入为5, 即求 5!。在主函数中的调用语句即为y=ff(5),进入ff函数后,由于n=5,不等于0或1,故应执行f=ff(n-1)*n,即f=ff(5-1)*5。该语句对ff作递归调用即ff(4)。 逐次递归展开如图5.3所示。进行四次递归调用后,ff函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。ff(1)的函数返回值为1,ff(2)的返回值为1*2=2,ff(3)的返回值为2*3=6,ff(4) 的返' K, @! L* O: c" O! U
回值为6*4=24,最后返回值ff(5)为24*5=120。 0 ^' Q/ W3 j) b# R# Z " [' Q: b. h' v 例5. 9也可以不用递归的方法来完成。如可以用递推法,即从1开始乘以2,再乘以3…直到n。递推法比递归法更容易理解和实现。但是有些问题则只能用递归算法才能实现。典型的问题是Hanoi塔问题。; E+ d }; K" {3 \6 _2 C) d ^
- s% P/ i! z G6 h( H* _* N [例5.10]Hanoi塔问题7 i ~) ^0 w1 G5 B
一块板上有三根针,A,B,C。A针上套有64个大小不等的圆盘, 大的在下,小的在上。如图5.4所示。要把这64个圆盘从A针移动C针上,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。3 W- j( R8 x/ k; r3 L3 D$ m7 t
本题算法分析如下,设A上有n个盘子。0 v! K2 |/ i. M, m
如果n=1,则将圆盘从A直接移动到C。 9 y9 N: B$ ?& R$ N" d8 `如果n=2,则:' p' H9 y" _# `# d, x; R" ]9 N% a
1.将A上的n-1(等于1)个圆盘移到B上;3 g: `: ~+ V0 p& v$ |+ y- @
2.再将A上的一个圆盘移到C上; 7 @) h8 [, |3 w" X3.最后将B上的n-1(等于1)个圆盘移到C上。7 g0 [7 L& K0 B0 Y) o( h4 d* q
如果n=3,则: 4 H' C9 J* H. R3 o0 y9 XA. 将A上的n-1(等于2,令其为n`)个圆盘移到B(借助于C), . L+ X2 t. @/ }$ [
步骤如下: # I. n _. M( l(1)将A上的n`-1(等于1)个圆盘移到C上,见图5.5(b)。 4 m+ [. \# D; l7 I' B* r9 V(2)将A上的一个圆盘移到B,见图5.5(c) . H4 S' C$ r0 k' o- r m. A(3)将C上的n`-1(等于1)个圆盘移到B,见图5.5(d) I6 V1 z# B V) \- cB. 将A上的一个圆盘移到C,见图5.5(e) ( r& h/ g( [6 M: `- D! `* D1 S# T hC. 将B上的n-1(等于2,令其为n`)个圆盘移到C(借助A),& U+ l+ U: [! b
步骤如下:: Z7 i7 Z9 j) P
(1)将B上的n`-1(等于1)个圆盘移到A,见图5.5(f): D! n# C" |/ |& `5 d
(2)将B上的一个盘子移到C,见图5.5(g) - H2 [4 k! ^' N2 P! }; J. g8 l(3)将A上的n`-1(等于1)个圆盘移到C,见图5.5(h)。 1 D( g4 E. n( d: w/ \# O. _到此,完成了三个圆盘的移动过程。 n- v, I( E3 Y7 Y# `" Q4 X7 |8 D从上面分析可以看出,当n大于等于2时, 移动的过程可分解为; G: b) ?1 U( H u1 s
三个步骤:# Z* {& w7 P2 q8 d7 Y& A
第一步 把A上的n-1个圆盘移到B上;2 O" r7 a8 e9 T. E& q6 U- X+ _5 z
第二步 把A上的一个圆盘移到C上; 0 ]- k; a! \' L+ [& x第三步 把B上的n-1个圆盘移到C上;其中第一步和第三步是类同的。 * s0 \) I+ m& ^, m+ H8 x$ [- Q. O, }当n=3时,第一步和第三步又分解为类同的三步,即把n`-1个圆盘从一个针移到另一个针上,这里的n`=n-1。 显然这是一个递归过. Y! i8 V1 v5 P; j. G0 r! q( ]4 A
程,据此算法可编程如下:7 ?" i% g5 W* S0 H
<FONT color=#009900>move(int n,int x,int y,int z) % ~: S L' r: I$ ?{ - w- a$ `( }( W" V! w' G$ [if(n==1) + R1 r( v( B9 o iprintf("%c-->%c\n",x,z); & _; x+ i( s6 M" w/ C, B6 r& Belse * x* P" t* h" m% T% ?, v{, E8 s% P, M3 X9 V0 _: a
move(n-1,x,z,y); 7 Y% o# b* {. Z. J( K4 j3 Kprintf("%c-->%c\n",x,z); / C1 o& N; d- N3 P cmove(n-1,y,x,z);5 Q+ _& {. l. [! P: O# z
} : k8 n- p* {4 D$ A P}& N0 S$ y/ c% i5 P/ b
main() . w" T* |' |2 A) Y. \3 a{ . k- z( ]2 @3 @' }& Rint h;% |% r2 u/ }2 _3 h6 C: a
printf("\ninput number:\n"); 0 S* f3 m, o# W. }scanf("%d",&h); ) M4 c: H) O9 b: Rprintf("the step to moving %2d diskes:\n",h);! t- d! k Q! }; x# j- ^
move(h,'a','b','c'); . W- F) x R2 `: R} 2 v( Q5 V+ I* K+ l9 e5 F: o% nmove(int n,int x,int y,int z)$ S* F( [( F. K
{ , E3 K+ _# L) A, jif(n==1)# c' g0 ?4 x4 B' L) i
printf("%-->%c\n",x,z); 2 \3 N+ L+ a- C' ]; c1 Uelse % P& ^4 N4 K$ y5 s( f{ . ?5 R, j! n$ j- O4 d- o2 t7 j9 R( Dmove(n-1,x,z,y); 4 W# n) y3 M9 K. G: \printf("%c-->%c\n",x,z);# F9 O' Q$ |8 a7 l# c& n) ~
move(n-1,y,x,z);# @% u1 U: B$ ~2 m4 R8 a
}5 q' B9 N& z) D$ h7 S& v6 f
} 1 [. G0 r9 b0 Y- Q* Wmain() [- r% Q0 O: l( N{ …… % x; C& O2 N4 H; ^move(h,'a','b','c');" F: U g$ I& f4 k9 d5 ~+ Q; \
} 0 L1 x: c' u$ `3 r& ~0 r</FONT> 从程序中可以看出,move函数是一个递归函数,它有四个形参n,x,y,z。n表示圆盘数,x,y,z分别表示三根针。move 函数的功能是把x上的n个圆盘移动到z 上。当n==1时,直接把x上的圆盘移至z上,输出x→z。如n!=1则分为三步:递归调用move函数,把n-1个圆盘从x移到y;输出x→z;递归调用move函数,把n-1个圆盘从y移到z。在递归调用过程中n=n-1,故n的值逐次递减,最后n=1时,终止递归,逐层返回。当n=4 时程序运行的结果为 0 r+ O* L( i! z9 t' ~, K" s<FONT color=#009900>input number:$ z, M7 W3 x& _
4 : [$ Y1 O X' o7 s5 A6 |the step to moving 4 diskes:; T# F8 s+ ~5 `% k' ]# Y; D; j0 f. z
a→b" K0 B' q y, b6 L7 Y
a→c 9 o% A2 P8 U. Y: Yb→c / H$ |* \4 }" ]0 R3 F1 G% {a→b 7 F7 [! R y- b, a1 A% tc→a 6 ^) h3 |% R" I6 P6 A Ic→b( f& C7 T+ s, M4 u( x4 C) H+ M7 A
a→b 5 g' I' \: g* k2 _7 C0 va→c; }5 Z' Q7 q/ |# u( O2 q+ ]" t. O
b→c 8 i" J0 o/ V. }/ `- `; F- o- Q9 \+ Hb→a 5 @( I9 u' ~6 b: D- m9 i1 \* fc→a$ ]5 m" L* P4 @* I/ x' T0 h% B
b→c 8 r8 Y. _+ a& j" f8 C- d7 ]3 Fa→b , t- }9 b2 }, R4 Y0 ]' \! M" oa→c' x; b* g9 r/ m6 M' F/ Z
b→c</FONT>% z- T0 S3 Q' L9 g
+ ~% g/ e( W9 A
<FONT color=#ff0000>变量的作用域</FONT> ) T+ b! R5 p. I9 W: ?: b$ b/ O' g0 U2 Z! p0 u
在讨论函数的形参变量时曾经提到, 形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的, 离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量, C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。 C语言中的变量,按作用域范围可分为两种, 即局部变量和全局变量。2 X2 z) K/ X. k' D( t
+ n5 L& P D. { S
<FONT color=#ff0000>一、局部变量</FONT> " t9 x; z! \9 h2 K! f. D- B! z" S$ H % K" N3 ^* K* k5 [ 局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。% w7 H+ ]( O8 D! D; d
例如:6 F; C4 P* F' ] F: o
<FONT color=#009900>int f1(int a) /*函数f1*/ " I7 _* ~ r* P{0 @; h2 s% }9 a4 e
int b,c; / f, X. T3 u0 Z0 W; G( U& z, I7 q
…… , w- E% e' d5 J& B1 K/ t% M; U5 S}a,b,c作用域 2 j) @) N. f, i& w+ b4 Y( u+ k) x, Tint f2(int x) /*函数f2*/9 e6 I1 n% ?. P' s% |( K/ \, Y" t
{1 Q+ a% ~' y/ K5 f8 a6 ~
int y,z; " D% G. T+ G& b5 P: `. t% m, [
}x,y,z作用域% k3 S- ^- W7 s' \/ ~' k$ n- F- _2 m
main() 1 y' f' |) O6 I- a+ ]{8 h" L5 \) E3 k. n; @4 e
int m,n; # O0 J' I$ K* @/ g9 f1 l" W
}</FONT><FONT color=#ff0000> 0 w2 c) d: }& m* L0 {+ ?% p</FONT>m,n作用域 在函数f1内定义了三个变量,a为形参,b,c为一般变量。在 f1的范围内a,b,c有效,或者说a,b,c变量的作用域限于f1内。同理,x,y,z的作用域限于f2内。 m,n的作用域限于main函数内。关于局部变量的作用域还要说明以下几点: $ {& F" P# a- T, [ + ^0 p1 O& O8 i" p1 M; \0 @1. 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。 9 u- {% |: H8 N) q; L , g) H, o# f( i2. 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。* r: n: f5 m' C% o K
' Z9 k5 }7 D( Y" P+ y; R! [$ `& y
3. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如在例5.3 中,形参和实参的变量名都为n,是完全允许的。4. 在复合语句中也可定义变量,其作用域只在复合语句范围内。例如:2 l. S4 u2 R5 z- V
<FONT color=#009900>main()/ E" h0 R, E- J* S7 d& E
{ % g2 B3 v- p! c% ]( H. L2 S) B3 c2 I" kint s,a;1 l0 Z7 f5 y3 B7 j. K
……( ]; O4 y, R2 Z# ?8 m/ L$ I
{. H4 a& ], m4 U' r
int b;# I8 U$ _4 b6 l) p; L$ O6 ?$ W
s=a+b; # Y @8 N5 f: j3 r0 r" q8 k……b作用域 2 o4 p0 o0 K/ J# A. p) }}3 Z& P5 D$ {5 G1 j2 ]' H1 D0 `
……s,a作用域 * v3 ^* D: C6 N}[例5.11]main() . I }; g% R: F{* v* o I$ h8 P
int i=2,j=3,k; f. E6 f/ P; E0 P* ck=i+j;; {& Z9 x: G4 D* S" r v6 |
{. C' a# j1 x# }: e' }; R
int k=8;" D5 a& |6 R2 R M: R9 s* [
if(i==3) printf("%d\n",k); ; ~+ } X R7 c} h2 i+ k) o$ \2 K/ F: v, p! o% l
printf("%d\n%d\n",i,k); - z# l( O" z* c} ! A& E& p7 r+ h8 B: A$ Nmain()2 n8 K9 u3 }' G( E1 S: W
{ 3 M1 k7 ?! U- R' u, K% Nint i=2,j=3,k;8 c+ Y* I6 d' k! l
k=i+j;1 D7 n/ S# z/ {0 ]
{# O! x) C \7 b
int k=8;* h( \! B0 }8 f6 y0 {/ M& Z; j
if(i=3) printf("%d\n",k);# k1 P1 A8 { a/ _4 j5 @
} , f- f' m, q1 Y ?printf("%d\n%d\n",i,k); 9 V6 h' G8 p7 F5 u/ d9 `: c}</FONT> w, T, L4 N1 }3 p8 C0 {1 O9 k
本程序在main中定义了i,j,k三个变量,其中k未赋初值。 而在复合语句内又定义了一个变量k,并赋初值为8。应该注意这两个k不是同一个变量。在复合语句外由main定义的k起作用,而在复合语句内则由在复合语句内定义的k起作用。因此程序第4行的k为main所定义,其值应为5。第7行输出k值,该行在复合语句内,由复合语句内定义的k起作用,其初值为8,故输出值为8,第9行输出i,k值。i是在整个程序中有效的,第7行对i赋值为3,故以输出也为3。而第9行已在复合语句之外,输出的k应为main所定义的k,此k值由第4 行已获得为5,故输出也为5。' o/ g) Z6 }2 F" ]: O
" h5 f6 o* |/ `* z9 V& ^. [
<FONT color=#ff0000>二、全局变量</FONT> ! k3 S8 C3 G' ^; S! o$ a. j, g i9 o6 \; A$ E, L4 O" e5 q" T" n' Y
全局变量也称为外部变量,它是在函数外部定义的变量。 它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。 例如:8 @$ x0 S2 R$ R9 V
<FONT color=#009900>int a,b; /*外部变量*/0 X; |, U0 I) b+ z& n
void f1() /*函数f1*/ " ?" n3 C3 E( `) P( B% E2 b7 X p{ , k2 \# k. x. |! Q( U I4 o( Z9 F…… $ r. O0 j; \) a. B5 l, h0 o}1 V7 d' r7 T9 j& E0 S" Y! L8 D
float x,y; /*外部变量*/ " M4 e0 N5 }* x4 h+ ?7 f; {
int fz() /*函数fz*// a+ e8 u3 y% [ _
{ ( k4 H9 Z; h3 S# R% i# f…… 0 ]+ v$ [6 d- Q' c' {2 h% w2 M}4 f ] u% {2 n
main() /*主函数*/8 z# B5 u' K( r( k g( w
{ $ M2 R& r$ c1 Z+ G2 w……: I9 a7 x* e4 ] |5 J9 G* s
}/*全局变量x,y作用域 全局变量a,b作用域*// A, I4 _! d, M0 L+ C) n$ z
</FONT> 从上例可以看出a、b、x、y 都是在函数外部定义的外部变量,都是全局变量。但x,y 定义在函数f1之后,而在f1内又无对x,y的说明,所以它们在f1内无效。 a,b定义在源程序最前面,因此在f1,f2及main内不加说明也可使用。 - |) j3 M& A% E3 p; Z- t8 B. y/ M1 ]3 d& n9 n
[例5.12]输入正方体的长宽高l,w,h。求体积及三个面x*y,x*z,y*z的面积。 . z8 j5 a, c) A, q' R<FONT color=#009900>int s1,s2,s3; 8 r3 _* {1 D$ Z3 o+ t8 ^int vs( int a,int b,int c) : r+ m. y1 e7 k/ Z- D{3 G; f( T9 p9 C2 [" T6 ?7 ?( Q
int v;7 D+ U. n" J- p- T* k
v=a*b*c; - e4 v/ |# Z! _; b& B) N4 _0 D, ^s1=a*b;- ]8 Y) ]& k( U- F0 e# S
s2=b*c; 5 ^1 h& Q0 O6 L1 g4 `s3=a*c;8 [: [/ s0 V6 W4 M
return v;6 V3 c% T. w! b4 ?0 i
} 9 W1 p+ Q+ [0 f) ^. W3 c0 @main() h# G' c0 |2 v: I+ A" v# a
{ % o p, M: A0 J/ t( Gint v,l,w,h; 4 |& E/ B/ j) H) E- N, T7 Vprintf("\ninput length,width and height\n"); # Q+ M. _5 i1 v$ rscanf("%d%d%d",&l,&w,&h); 0 y$ N5 Z% O6 A* i0 vv=vs(l,w,h); 4 X7 d1 d" J- eprintf("v=%d s1=%d s2=%d s3=%d\n",v,s1,s2,s3);% C8 J, I5 r" Q3 ?, k
}7 t/ k. {& e$ Y9 j) i
</FONT> 本程序中定义了三个外部变量s1,s2,s3, 用来存放三个面积,其作用域为整个程序。函数vs用来求正方体体积和三个面积, 函数的返回值为体积v。由主函数完成长宽高的输入及结果输出。由于C语言规定函数返回值只有一个, 当需要增加函数的返回数据时,用外部变量是一种很好的方式。本例中,如不使用外部变量, 在主函数中就不可能取得v,s1,s2,s3四个值。而采用了外部变量, 在函数vs中求得的s1,s2,s3值在main 中仍然有效。因此外部变量是实现函数之间数据通讯的有效手段。对于全局变量还有以下几点说明: ! X" X% ^& V7 `- T5 } + b" Q% N: y4 G! U: p6 r1. 对于局部变量的定义和说明,可以不加区分。而对于外部变量则不然,外部变量的定义和外部变量的说明并不是一回事。外部变量定义必须在所有的函数之外,且只能定义一次。其一般形式为: [extern] 类型说明符 变量名,变量名… 其中方括号内的extern可以省去不写。* U) N0 r0 H1 z
例如: int a,b;4 H6 w2 B/ a2 y/ A; ~3 G5 h
等效于: K. S+ N+ e: x; i
extern int a,b;7 U! f1 J& P, u4 k; h& [
而外部变量说明出现在要使用该外部变量的各个函数内, 在整个程序内,可能出现多次,外部变量说明的一般形式为: extern 类型说明符 变量名,变量名,…; 外部变量在定义时就已分配了内存单元, 外部变量定义可作初始赋值,外部变量说明不能再赋初始值, 只是表明在函数内要使用某外部变量。 - |* E/ A1 b7 g7 O 5 A$ X1 [( n N) W" }2. 外部变量可加强函数模块之间的数据联系, 但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的, 因此在不必要时尽量不要使用全局变量。! O9 w. d( n- u
" ^3 A8 n5 I5 S5 y1 c6 B8 z3. 在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。<FONT color=#009900> 3 q# x! U1 z2 _& G; h$ v[例5.13]int vs(int l,int w)' Q K- a6 x0 U# I9 q: B }
{5 ^* y [. c) I1 s; g
extern int h; / ~4 }! `9 r5 Q0 sint v; - _/ u& m2 J m2 ?v=l*w*h;$ |0 @1 I J. K8 t+ d
return v;- P' D2 Z0 Y- k; m
} # u9 t8 W0 r' m$ q' \) g9 x hmain() 1 n1 I, @( C0 f- X. D8 I8 q{ m$ l/ `+ H: E, }9 }extern int w,h; e$ T! O, g' |+ y6 X8 ?int l=5;* l+ L% V, \! W0 z5 U' h1 m! D
printf("v=%d",vs(l,w)); & r' R& N* j* ^4 E} ; W' I+ T8 Z6 D- Q+ zint l=3,w=4,h=5; 0 e8 l3 J$ x# b- G" Y</FONT> 本例程序中,外部变量在最后定义, 因此在前面函数中对要用的外部变量必须进行说明。外部变量l,w和vs函数的形参l,w同名。外部变量都作了初始赋值,mian函数中也对l作了初始化赋值。执行程序时,在printf语句中调用vs函数,实参l的值应为main中定义的l值,等于5,外部变量l在main内不起作用;实参w的值为外部变量w的值为4,进入vs后这两个值传送给形参l,wvs函数中使用的h 为外部变量,其值为5,因此v的计算结果为100,返回主函数后输出。变量的存储类型各种变量的作用域不同, 就其本质来说是因变量的存储类型相同。所谓存储类型是指变量占用内存空间的方式, 也称为存储方式。0 Q& X4 V- m l& i8 Y& \" ~7 ^& [
8 F- u6 \& @/ _9 T/ u
<FONT color=#ff0000>变量的存储方式可分为“静态存储”和“动态存储”两种。</FONT> ( a; k2 R$ s( q5 z5 {/ _" l' O" V# q' {, A( d6 b. s! o$ o
静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。5.5.1节中介绍的全局变量即属于此类存储方式。动态存储变量是在程序执行过程中,使用它时才分配存储单元, 使用完毕立即释放。 典型的例子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配, 调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、 释放形参变量的存储单元。从以上分析可知, 静态存储变量是一直存在的, 而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性称变量的生存期。 生存期表示了变量存在的时间。 生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。 一个变量究竟属于哪一种存储方式, 并不能仅从其作用域来判断,还应有明确的存储类型说明。 ( V, w$ G4 D- @" |1 ` L7 e9 {, `% B
在C语言中,对变量的存储类型说明有以下四种: 4 P( @9 s$ M- l c8 O. v7 o: lauto 自动变量$ G6 x( b1 q8 {
register 寄存器变量; r" M( ^6 X+ `" i0 l7 F& _9 z% a; B& f1 p
extern 外部变量 , P5 J# J: S# x3 k9 ]( Mstatic 静态变量 " S' U; x* `1 Z% S- U( Y 自动变量和寄存器变量属于动态存储方式, 外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后, 可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。 因此变量说明的完整形式应为: 存储类型说明符 数据类型说明符 变量名,变量名…; 例如:, X, I, y, w. c( g- ~6 `( ]3 ^
static int a,b; 说明a,b为静态类型变量3 N% G! X3 p/ D. N) F9 y6 x3 B2 u4 t! a
auto char c1,c2; 说明c1,c2为自动字符变量) D/ U. l. u! W5 ?2 s
static int a[5]={1,2,3,4,5}; 说明a为静整型数组) v; q y, n6 C b
extern int x,y; 说明x,y为外部整型变量 $ h3 U; Z- |$ o$ x下面分别介绍以上四种存储类型:' t+ L; _2 ~8 H0 H, [0 t
* K# ?, F$ L/ \. u" x一、自动变量的类型说明符为auto。* H) P5 t" U3 z
这种存储类型是C语言程序中使用最广泛的一种类型。C语言规定, 函数内凡未加存储类型说明的变量均视为自动变量, 也就是说自动变量可省去说明符auto。 在前面各章的程序中所定义的变量凡未加存储类型说明符的都是自动变量。例如: . F$ Y1 X: i5 ]3 \ N0 [1 K<FONT color=#009900>{ int i,j,k; ) G$ r# g5 S5 e; ?# o, U$ Cchar c;4 m p3 e9 U; h' k! m. T* D# Y. u
…… % c7 Q! V' |3 }+ d8 S1 l}等价于: { auto int i,j,k;+ u+ F3 v' ?% _! v- b
auto char c;2 o( b! n3 ?! ]! ?: K
……8 s6 z: H! W; u) ^' j$ Y0 m, ]
} * X4 _( v e4 @7 W. U</FONT> 自动变量具有以下特点:* ]- P( X1 h, i6 W8 m* `
1. 自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量只在该复合语句中有效。 例如: 4 n, O% Y3 j$ f& D! T
<FONT color=#009900>int kv(int a) " j5 w# A0 Y) D, h- [ \. p; |' m{ ! e! \3 `3 f9 f+ X0 Yauto int x,y; / t* M( H$ ]9 R3 ]{ auto char c; / B+ T; H' Z$ b* i
} /*c的作用域*/ 3 W% c8 V) Q0 e4 K: E……0 B+ Z# _ y2 k% i% @% K
} /*a,x,y的作用域*/ 8 H6 j% N% R+ `$ t9 O</FONT> 3 K( C2 H3 w7 q* b$ V2. 自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。例如以下程序: 6 M) Y$ N2 \3 d c3 S) }/ x4 Z
<FONT color=#009900>main() ; S/ q! G/ B/ w8 O3 z2 }{ auto int a,s,p; / S# w. I& }; x, W$ m0 Wprintf("\ninput a number:\n"); ; H4 ?7 b- L$ \- ^! I! y! }% Mscanf("%d",&a);3 d4 y7 O: C" i; l5 f6 X
if(a>0){- _! T0 L3 i; C* M
s=a+a;4 b" y& }# r5 `9 m
p=a*a;( J5 J3 F7 X* R7 X4 `9 ]* c
}, C( J3 k2 @( R4 D" l$ m
printf("s=%d p=%d\n",s,p);2 ^" V3 X; L) ~1 R) b" r
}+ b& P- ]( K7 G4 X5 i
{ auto int a; 7 L1 U) p7 U, ~3 h# ^0 g2 n5 R5 {printf("\ninput a number:\n"); - C* Y) ?) e0 r! ]7 b, t0 Kscanf("%d",&a); + F4 u- m# L- Q4 E* z ]if(a>0){/ n9 X# y& k. Q6 q! H
auto int s,p; 2 f6 q1 w5 Q" H y! }& N' K7 @s=a+a;4 R* z" f+ k4 ?+ F% o5 c
p=a*a;% T3 T: W: _5 c% s1 m6 \$ B8 E
}, @+ @/ r3 e5 P3 ~& V, m- b' Y
printf("s=%d p=%d\n",s,p); p2 G9 R [) k
} + V& L( n9 R& G B+ @</FONT>s,p是在复合语句内定义的自动变量,只能在该复合语句内有效。而程序的第9行却是退出复合语句之后用printf语句输出s,p的值,这显然会引起错误。$ Z; a4 U& l. p+ ^8 R' T* ]. H
( @- I, u" u3 h3 H+ S3. 由于自动变量的作用域和生存期都局限于定义它的个体内( 函数或复合语句内), 因此不同的个体中允许使用同名的变量而不会混淆。 即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名。例5.14表明了这种情况。3 k+ X4 [7 j' Q0 _2 t4 l
[例5.14]9 G9 F/ `) o5 \5 d
<FONT color=#009900>main() : p9 T. Q4 y: L/ Y+ [$ t$ K5 _{% g. V8 ^2 {6 r
auto int a,s=100,p=100;8 G+ E3 T2 `9 G
printf("\ninput a number:\n"); 2 ] H9 o. b# D3 B3 {- h% pscanf("%d",&a); ( r! x6 _5 l+ R! e' X5 Wif(a>0) ( v$ }( ~. v& i% i' M& g( k{ 8 y: j0 N* M3 x l5 \auto int s,p;6 ^' A. R; W4 d- r' [7 f
s=a+a; : z& X% B2 f9 G6 x8 D* rp=a*a;& c8 }+ P; d. L, w2 c3 r' h
printf("s=%d p=%d\n",s,p);. {2 {) q7 {6 c6 J8 {( x4 N
} - C6 n; M( B2 r. M j" x) wprintf("s=%d p=%d\n",s,p);6 H( R o) p" K2 J. e1 `
} & Z. M5 B' U9 j</FONT> 本程序在main函数中和复合语句内两次定义了变量s,p为自动变量。按照C语言的规定,在复合语句内,应由复合语句中定义的s,p起作用,故s的值应为a+ a,p的值为a*a。退出复合语句后的s,p 应为main所定义的s,p,其值在初始化时给定,均为100。从输出结果可以分析出两个s和两个p虽变量名相同, 但却是两个不同的变量。 - n. U0 Y7 o4 n) J) W* C6 ~- I- v0 d % }' i5 `5 r4 _; F0 {- S( T4. 对构造类型的自动变量如数组等,不可作初始化赋值。 4 W# D, w1 d0 V5 ~8 q& g 7 B! L6 h! P7 ?0 F8 L1 `2 F<FONT color=#ff0000>二、外部变量外部变量的类型说明符为extern。</FONT>4 j& Y/ o- }/ J& M$ W
- k; Y. L; e- L/ z3 x1 W在前面介绍全局变量时已介绍过外部变量。这里再补充说明外部变量的几个特点:6 J6 R. K' a3 L1 F4 |4 p
1. 外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。 , j( j- U; z c; ?: C/ `/ o' Q3 w/ u$ i/ p: T
2. 当一个源程序由若干个源文件组成时, 在一个源文件中定义的外部变量在其它的源文件中也有效。例如有一个源程序由源文件F1.C和F2.C组成: <FONT color=#009900>F1.C/ U# A: m2 s9 K8 q% t& @- K8 `
int a,b; /*外部变量定义*/6 L7 Q( Z) O& S
char c; /*外部变量定义*/% W1 \; K) h2 E5 ~* }
main() $ p, v0 i( d' Z: c8 d/ q7 I. C{ / f5 w7 H; b' k& I# z
……/ u4 O3 }9 m) J# \
} 7 ~/ _4 w) Q% x I8 ?+ UF2.C2 {; l& ~5 Y$ p7 d/ r3 w- j2 j S
extern int a,b; /*外部变量说明*/ + U" `8 t% U+ ?3 Y& |, Zextern char c; /*外部变量说明*// _. }& w+ x. U
func (int x,y) $ `. b5 H/ H- g* c9 v{ # ~4 O& O9 T# S" K- n9 | F……2 a* |9 M5 c9 u
} # J* m3 Y9 v a' ~% g</FONT>在F1.C和F2.C两个文件中都要使用a,b,c三个变量。在F1.C文件中把a,b,c都定义为外部变量。在F2.C文件中用extern把三个变量说明为外部变量,表示这些变量已在其它文件中定义,并把这些变量的类型和变量名,编译系统不再为它们分配内存空间。 对构造类型的外部变量, 如数组等可以在说明时作初始化赋值,若不赋初值,则系统自动定义它们的初值为0。 3 I- w* ^: f) G, {/ D' O. t' m<FONT color=#ff0000> 5 S8 i! |& I( ?1 @# L6 ?' T三、静态变量</FONT>. i/ _# z% [2 K3 H6 q, @
/ Y' G, L! ~. R5 Q" @
静态变量的类型说明符是static。 静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量, 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。 对于自动变量,前面已经介绍它属于动态存储方式。 但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。 z8 v; H" x0 j7 c8 O3 d H5 \- Z, w由此看来, 一个变量可由static进行再说明,并改变其原有的存储方式。9 y; Z0 s& p% L' }5 ^' j
" x; M: V# _$ z. V C1. 静态局部变量8 _* Q$ U0 U6 l# g4 q* x
在局部变量的说明前再加上static说明符就构成静态局部变量。 : X) ^9 N3 t8 `5 m例如:( i: y `; Z5 `4 l$ u
static int a,b; 1 b) i: z( P* P2 R; a# N% a3 F$ ~8 Ostatic float array[5]={1,2,3,4,5};. ]1 b) U, n. S" k# J; L
! j* x- x4 `4 a4 W5 W% r- |5 v
静态局部变量属于静态存储方式,它具有以下特点:3 r1 T( d# m y
(1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。5 w& [9 @2 l5 \
2 K) `( K$ U- u: o% U5 k9 F! C
(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。! j' u+ ~& a9 b: B/ e8 Q' d2 w
) R' j- L& a9 ~. Q0 i(3)允许对构造类静态局部量赋初值。在数组一章中,介绍数组初始化时已作过说明。若未赋以初值,则由系统自动赋以0值。 + c4 \! x8 a1 b/ u3 d* E0 n: H+ s6 Y* J/ |' [0 l# Y. M- L
(4)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。 根据静态局部变量的特点, 可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。3 @( n& p2 z, T+ I8 f
<FONT color=#009900>[例5.15]main() 9 _$ Y1 N/ h. A/ R{ 3 u1 A3 f. ?0 I5 q F: h8 }int i;6 N) `+ s$ {& a
void f(); /*函数说明*/5 e. ^9 m; v1 ^/ _
for(i=1;i<=5;i++) : i. A$ z, X* F/ v4 pf(); /*函数调用*/8 E1 N; C+ D+ m% O; R1 A
} ( C6 L _8 K0 C# K* nvoid f() /*函数定义*/ , @0 p, K* W. ]! ~{ + i3 R8 \+ i4 M& m" lauto int j=0; 7 i8 I' n1 |2 I0 K++j;* k8 c, W2 N% D: B
printf("%d\n",j);. I S* Z2 ^6 k! O' |; S; D: N! n
}. V i& i* v+ `" A4 @& Q
</FONT> 程序中定义了函数f,其中的变量j 说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋初值为0,故每次输出值均为1。现在把j改为静态局部变量,程序如下:5 Q6 R- g- i1 B. E$ d2 a0 ?# K
<FONT color=#009900>main() 3 J1 d( H1 e) E# v2 c6 T{ / u( E4 I- D- S! y/ sint i;/ B& K1 }" F6 }" G! ?- H
void f(); 7 u ?( B; }2 Qfor (i=1;i<=5;i++) 4 h0 T/ {8 l* Bf();+ }( R- p- t9 `4 j9 D, L/ |
} + x9 N8 g5 n; U ?void f() , }3 C9 |3 R9 c! |{) q, S' t$ t# ^$ |. G
static int j=0;; h0 }* ~& \+ Q* k2 }8 u/ K1 n
++j; - ]) Z3 e$ k; }printf("%d\n",j);! D# l9 K" e' r/ D3 W- F2 w1 v
}5 }5 t8 @/ j0 X2 t( a
void f() 0 l4 k9 x0 ?# @6 B" B" ~$ [) T{- z" N G! c6 n+ ^0 L, K1 c
static int j=0;& E6 D( M4 a" ^0 P2 r
++j;7 n" M0 l7 q) D
printf("%d/n",j); % |5 G( S n1 `) J$ g}</FONT> , z7 g) U4 i6 d2 L* Z由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。读者可自行分析其执行过程。 6 o1 b$ F9 c B2 x% \& b- { * a8 Y$ K; W; ~% v0 q2.静态全局变量+ H* a& V+ }1 |3 H# |! P
全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它 $ L9 N4 Q& y. p6 s( R3 Q; K5 A5 P的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。 3 G& r. L8 h, `* b1 s % c7 k9 p! P7 t/ `/ Q+ P C<FONT color=#ff0000>四、寄存器变量</FONT> # v4 p, V8 N* L9 W. u# K ; z8 O1 e$ B, D1 q4 v1 ~( ^3 z& J; r 上述各类变量都存放在存储器内, 因此当对一个变量频繁读写时,必须要反复访问内存储器,从而花费大量的存取时间。 为此,C语言提供了另一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写, 这样可提高效率。寄存器变量的说明符是register。 对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。3 t$ g6 e4 \/ N8 G+ d8 s
<FONT color=#009900>[例5.16]求∑200i=1imain()' Y x( B7 F0 y& M% c
{! v0 Q& R6 u' ?0 Q$ w! Y1 `0 M
register i,s=0; , [/ ^! ]" d% f' xfor(i=1;i<=200;i++)) T6 A6 O/ u; n' l
s=s+i;/ H" D/ ?' n1 j. T( X
printf("s=%d\n",s);, S1 j9 `1 U4 Q8 w
}) b' Q9 C6 \) r) K
</FONT>本程序循环200次,i和s都将频繁使用,因此可定义为寄存器变量。 7 T* Z7 ~( Y8 Z8 Q7 d0 Q- i对寄存器变量还要说明以下几点: 7 N1 y8 K r% |+ O2 X0 q* X1 U% ?9 O1 W
1. 只有局部自动变量和形式参数才可以定义为寄存器变量。因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为寄存器变量。 5 N R! `- F; O8 ~4 d$ `! M+ J. K, e7 U % `) Y% M. ^# y( \2 W2 @2. 在Turbo C,MS C等微机上使用的C语言中, 实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高。 而在程序中允许使用寄存器变量只是为了与标准C保持一致。3. 即使能真正使用寄存器变量的机器,由于CPU 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。6 \: z' ^' r! i
1 K: ^9 P' Q- B<FONT color=#ff0000>内部函数和外部函数</FONT> # f$ L2 o. ^8 P T7 ]/ o 0 U' b. m. e" A/ s3 V' @ q 函数一旦定义后就可被其它函数调用。 但当一个源程序由多个源文件组成时, 在一个源文件中定义的函数能否被其它源文件中的函数调用呢?为此,C语言又把函数分为两类:* K( y+ m1 K* Y1 w
8 s# D) {3 A7 E* _1 [9 \+ y/ z一、内部函数 . G/ N* r' L/ b. V% ]& {' G: _/ R4 E2 d0 }2 N y! D5 L' k; ]8 m
如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用, 这种函数称为内部函 : |7 M5 N8 B F8 E" P8 B% `数。定义内部函数的一般形式是: static 类型说明符 函数名(形参表) 例如:/ r3 {, |. w1 z3 P. I$ u b) t
static int f(int a,int b) 内部函数也称为静态函数。但此处静态static 的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件。 因此在不同的源文件中定义同名的静态函数不会引起混淆。 0 g: I1 }) ^1 V/ @3 I/ A, D" D& g; n& y8 v7 _
二、外部函数 2 }. P9 C( @7 x" B2 S) T- Q" I 外部函数在整个源程序中都有效,其定义的一般形式为: extern 类型说明符 函数名(形参表) 例如:9 \9 K# n7 X, j9 F& L
extern int f(int a,int b)如在函数定义中没有说明extern或static则隐含为extern。在一个源文件的函数中调用其它源文件中定义的外部函数时,应 用extern说明被调函数为外部函数。例如: - V$ i3 g' x7 O. }<FONT color=#009900>F1.C (源文件一)1 l* ]: ]7 c* t
main() ; D8 f. q; Y P9 ~6 L3 E& I{2 g/ M8 J! p1 I& _' D& M7 _! S
extern int f1(int i); /*外部函数说明,表示f1函 2 x( Q" C6 Z/ S! K; J' _数在其它源文件中*/. F+ t b! @4 p+ @* g, u
…… 0 i; r( F7 M9 a% H& p}' o, z8 D* k7 g
F2.C (源文件二) : Y7 w/ q- g4 xextern int f1(int i); /*外部函数定义*/ # q. E1 J0 ]# }4 r0 A4 v$ s{ 0 j& ~% T' ]1 Q5 V& _6 B' X7 Z5 ?…… 5 ^& e) p4 Z T+ i- U7 Q}" k* I+ a, K0 e+ C6 \& L. w( b9 {
</FONT> 6 a# S1 [8 l/ j. c3 ?) }! Z<B><FONT color=#cc0000>本章小结</FONT></B>. R! {7 `0 K% e* o
$ `; L- w0 F! ~
1. 函数的分类 0 y6 p( A7 U8 }3 ?(1)库函数:由C系统提供的函数; 1 V C+ ?! G/ ?4 m9 h) s1 m(2)用户定义函数:由用户自己定义的函数; ) m# D+ I |. e/ m(3)有返回值的函数向调用者返回函数值,应说明函数类型( 即返回值的类型 );2 [1 w. X4 o9 E X( ~# o0 _
(4)无返回值的函数:不返回函数值,说明为空(void)类型;# }' Q% y* Z6 P. c6 N" u; N% v0 v* `
(5)有参函数:主调函数向被调函数传送数据;/ R% j8 X+ v) ~2 [( _5 c% I
(6)无参函数:主调函数与被调函数间无数据传送; 2 h) ^% v, v5 J5 K(7)内部函数:只能在本源文件中使用的函数; + u" L9 \# y ?8 b8 F9 i7 @(8)外部函数:可在整个源程序中使用的函数。1 D4 C2 h _, y/ I, {+ E