• flag标志什么?哦,它标志代码馊了——(一)


      几乎每次在代码中发现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)输入一个十进制整数后输入的<CR>(回车换行)。但实际上如果程序使用者输入十进制整数后又顺手按了一个<SP>空格再回车,这个“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 ) ;
    /*……*/
    }
  • 相关阅读:
    RecyclerView中装饰者模式应用
    Android中的Drawable和动画
    Android的线程和线程池
    Bitmap的加载和Cache
    Android的消息机制
    【Java基础】线程和并发机制
    Asp.Net 将HTML中通过dom-to-image.js标签div内的内容转化为图片保存到本地
    Asp.Net MVC @Html.TextBox 只允许输入数字问题
    程序员编程10大原则,请牢牢记住
    Asp.Net MVC WebAPI的创建与前台Jquery ajax后台HttpClient调用详解
  • 原文地址:https://www.cnblogs.com/pmer/p/2245012.html
Copyright © 2020-2023  润新知