几乎每次在代码中发现flag变量,我总是能嗅到一股馊味。不管你闻没闻到,反正我闻到了。
在代码中,flag通常作为标志变量的名字。但问题在于,不是所有的问题或代码都需要使用这种标志变量,更不是使用标志变量一定要用flag这个名字。需要使用标志变量的问题只有一小部分而已,况且即使使用标志变量,取flag这个名字也往往是一种草率的下策。
然而由于基础不扎实及某些书籍的误导等缘故,不少初学者往往特别善于滥用flag。几乎在任何场合他们都有能力把这个flag使用上且用得出神入化般的别扭,就如同用if语句和goto语句构造循环结构一样,甚至比这更坏。这种情况是由于不熟悉基本的代码结构所致。
另一种滥用flag的情况则是,由于最初的设计千疮百孔漏洞百出,等到发现时用flag打上丑陋的补丁。
下面开始阅读代码,请自备口罩。
样本代码1
- #include <stdio.h>
- #define N 10
- int main()
- {
- int num [N],number,flag=1,c;
- char name[N][8];
- /*……*/
- while(flag==1)
- {printf("\ninput number to look for:");
- scanf("%d",&number);
- search(number,num,name);
- printf("continue ot not(Y/N)?");
- getchar();
- c=getchar();
- if(c=='N'||c=='n')
- flag=0;
- }
- /*……*/
- return 0;
- }
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p92
这段代码中的while语句是明显的缺陷,因为从这个程序段的功能来说使用do-while才是自然的表达。
- #include <stdio.h>
- #define N 10
- int main( void )
- {
- int num [N],number,flag=1,c;
- char name[N][8];
- /*……*/
- do{
- printf("\ninput number to look for:");
- scanf("%d",&number);
- search(number,num,name);
- printf("continue ot not(Y/N)?");
- getchar();
- c=getchar();
- if(c=='N'||c=='n')
- flag=0;
- }
- while(flag==1);
- /*……*/
- return 0;
- }
其次flag标志变量根本没有必要,代码完全可以写成
- #include <stdio.h>
- #define N 10
- int main( void )
- {
- int num [N],number,flag=1,c;
- char name[N][8];
- /*……*/
- do{
- printf("\ninput number to look for:");
- scanf("%d",&number);
- search(number,num,name);
- printf("continue ot not(Y/N)?");
- getchar();
- c = getchar() ;
- }
- while(!(c=='N'||c=='n'));
- /*……*/
- return 0;
- }
最后,getchar();也用得十分蹩脚,它的用意是读入前面调用scanf("%d",&number)输入一个十进制整数后输入的(回车换行)。但实际上如果程序使用者输入十进制整数后又顺手按了一个空格再回车,这个“getchar();”就会因为读入了空格而导致后面的“c = getchar() ;”读取N、n、Y、y这几个文字字符的企图遭到失败。更好的写法应该是
- #include <stdio.h>
- #define N 10
- int main( void )
- {
- int num [N],number,flag=1,c;
- char name[N][8];
- /*……*/
- do{
- printf("\ninput number to look for:");
- scanf("%d",&number);
- search(number,num,name);
- printf("continue ot not(Y/N)?");
- scanf("\n%c",&c );
- }
- while(!(c=='N'||c=='n'));
- /*……*/
- return 0;
- }
与样本代码1对比一下,不难发现样本代码1中的flag有多么拙劣。
样本代码2
- #define N 10
- char str[N];
- int main()
- {
- /*……*/
- int i,flag;
- for(flag=1;flag==1;)
- {scanf("%s",&str);
- if(strlen(str)>N)
- printf("string too long,input again!");
- else
- flag=0;
- }
- /*……*/
- }
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p85
这段代码的flag用得更加可惊可愕,它甚至不如写成功能等价的
- #define N 10
- char str[N];
- int main(void)
- {
- /*……*/
- int i,flag;
- do {
- scanf("%s",&str);
- if(strlen(str)>N)
- printf("string too long,input again!");
- else
- break;
- }
- while(1);
- /*……*/
- }
由此不难看出那个flag有多么多余。然而这个写法依旧啰嗦,更简洁的写法是:
- #define N 10
- char str[N];
- int main(void)
- {
- /*……*/
- do {
- scanf("%s",&str);
- }
- while(strlen(str)>N && printf("string too long,input again!"));
- /*……*/
- }
这写法虽然简洁,然而依然带着样本代码中原来的毛病,这个毛病就是scanf("%s",&str);中的&str。因为str的定义是 char str[10];,而与转换格式%s相对应的参数应该是char *类型的指针,所以这个调用应该写为scanf("%s",str);。
即便如此,代码依然是错误的。原代码企图在strlen(str)>N的情况下重新输入,这是幼稚的一厢情愿,而且有着双重的错误。从逻辑来说,strlen(str) 等于N时就已经是越界使用数组了。所以strlen(str)>N这个关系表达式不应该使用“>”运算。从实践的角度来说strlen(str)>=N时的越界已然对内存中的数据存储造成了破坏,程序以后的行为是建立在错误的基础上,最后的结果必然是错误的,至少是不可靠的。而结果不可靠,对于程序来说就是一种错误。
那么,这段代码应该怎样写呢?由于样本代码2要解决的问题是对十个字符排序,这里的要求是输入10个字符。这根本不用写一个循环语句反复折腾,只需简单地
- #define N 10
- char str[N];
- int main(void)
- {
- /*……*/
- int i;
- scanf("%10c" , str ) ;
- /*……*/
- }
即可。
当然,也许有人会指责scanf("%10c" , str )没有用上N这个宏,这个指责确实有道理。其实用N这个宏来写scanf("%10c" , str )也并非什么难事,代码可以这样写:
#define N 10
- #define FMT(x) ("%"NUM(x)"c")
- #define NUM(x) #x
- char str[N];
- int main(void)
- {
- /*……*/
- int i;
- scanf( FMT(N) , str ) ;
- /*……*/
- }