static的滥用与变态的阉割
什么情况下需要用局部静态变量呢?需要保留函数上一次调用结束时的值时,例如可以用下面的方法求n!。
例7.17 输出1到5的阶乘值。
#include <stdio.h>
int main( void )
{int fac(int n);
int i;
for(i=1;i<=5;i++)
printf("%d!=%d\n" ,i,fac(i) );
return 0;
}
int fac(int n)
{static int f=1;
f=f*n;
return(f);
}
————谭浩强 ,《C程序设计》(第四版),清华大学出版社, 2010年6月,p207
这段程序的输出结果是:
1!=1
2!=2
3!=6
4!=24
5!=120
看起来似乎没有问题,然而稍微仔细思考一下就不难发现,如果若问题再补充一个要求时,比如在求完1到5的阶乘后再重新求3的阶乘时,那个fac()函数彻底报废、毫无用处。而造成这种结局的原因则是fac()函数对static莫名其妙的滥用,亦即在不该使用static局部变量时使用了static局部变量。
为了进一步分析这种写法的荒谬,下面把fac()函数的内容等价地还原到调用者main()函数中,这样代码就变成了
#include <stdio.h> int main( void ) { int i; for ( i = 1 ; i <= 5 ; i++ ) { static int f = 1 ; f *= i ; printf("%d!=%d\n" , i , f ); } return 0; }
这段代码与样本中代码在功能上是等效的,但却没有样本代码的毛病。从这段代码中不难看出代码段
for ( i = 1 ; i <= 5 ; i++ ) { static int f = 1 ; f *= i ; printf("%d!=%d\n" , i , f ); }
是一个有机的功能整体,其中i的取值顺序、static局部变量f及求阶乘的运算“f *=i”三者之间互相依赖,唇齿相依。
样本代码的荒谬则在于把紧密依赖、三位一体的东西变态地阉割出一部分来,并把阉割出的部分写成了只能一次性使用的“残疾”函数。由于阉割出的部分包含static局部变量,fac()函数的行为对调用次序具有强烈的依赖性,函数就其本性来说根本不应该具有这种娇生惯养的依赖性。在大多数情况下,只要函数的调用者提供正确的实参,函数都应该产生正确的副效应和返回值,这才是函数的本分。它与调用者之间只应该存在必要的联系,所谓“必要”就是不多也不少的参数,除此之外不应该再有什么别的联系,这就是所谓的“低耦合”的含义。一旦违背了这个原则,比如样本代码那样阉割不可分的功能,就必然导致变态的强耦合联系方式。在不满足这种苛刻的强耦合条件下,函数就会变得毫无用处。这就好比把一个完整的计算机主板掰成两半,再用胶水粘合在一起,摆摆样子可以,但主板被分开的任何一部分都已经成了废品。在函数中滥用static局部变量,最终的效果多半如此。
那么,是否static局部变量绝对不可以使用呢?倒也不是。但总的来说static局部变量的适应范围较窄。下面的例子中对static局部变量的使用就非常恰当,无论是把数组name设置为局部还是把它设定为static存储类别都非常恰到好处。
#include <stdio.h> char * month(unsigned) ; int main( void ) { puts( month(10) ); return 0; } char * month(unsigned m) { static char * const name[12] = {"Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec",}; return name[m-1]; }