样本代码3
题目:写一个判(判断?)素数的函数,在主函数输入一个整数,输出是否为素数的信息。
- #include <stdio.h>
- int main ()
- {int prime (int);
- int n;
- printf ("input an integer:");
- scanf ("%d",&n);
- if (prime(n))
- printf ("%d is a prime. \n",n);
- else
- printf ("%d is not a prime. \n",n);
- return 0;
- }
- int prime (int n )
- {int flag=1,i;
- for (i=2;i<n/2&&flag==1;i++)
- if (n%i==0)
- flag=0;
- return (flag);
- }
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p77
这段代码用于判断一个整数是否是素数,其中也用到了flag。
不过在讨论这个flag的得失利弊之前,必须要指出的是,prime()这个函数根本上就是错的。因为prime(4),prime(1) ,prime(0)的返回值都是1,也就是说prime()把0,、1、4都判定为素数。更让人喷饭的是,甚至对于负整数,prime()也会判断为素数,这非常滑稽。代码作者在写prime()函数时犯了两个非常浅薄的错误:第一,压根没考虑到n可能不是正整数的情况;第二,误把i<=n/2写成了i。而把“<=”误写为“<”是初学者才会犯的一个毛病,后果是导致循环次数错误。
prime()函数没有错误的写法应该是:
- int prime (int n )
- {int flag=1,i;
- if (n<=1)
- return 0;
- for (i=2;i<=n/2 && flag==1;i++)
- if (n%i==0)
- flag=0;
- return (flag);
- }
修改过的代码中,flag依然如阑尾一般啰嗦无效。这个flag的作用之一是通过对“flag==1”求值结束for循环语句,然而这是一种虽然可行却很怪异的结束循环语句的方法,表明代码作者根本不懂得循环语句可以用break语句或return语句这两种正规方法提前结束,但却可以写一本《C程序设计(第四版)学习辅导》。就这个函数而言,由于“n%i==0”时已经可以确定n是否为素数,所以用return语句结束循环更加直截了当些。
这个题目的正确写法是
- #include <stdio.h>
- #define TRUE 1
- #define FALSE 0
- int be_prime( int );
- int main ( void )
- {
- int n;
- printf ("输入一个整数:");
- scanf ("%d",&n);
- printf ("%d%s是素数。\n" ,
- n , (be_prime(n)==TRUE)?"":"不" ) ;
- return 0;
- }
- int be_prime ( int n )
- {
- int i;
- if ( n <= 1 )
- return FALSE;
- for ( i = 2 ; i <= n / 2 ; i++ )
- if ( n % i == 0 )
- return FALSE;
- return TRUE ;
- }
样本代码4
写一个函数,输入一个十六进制数,输出相应的十进制数。
- #include <stdio.h>
- #include <stdlib.h>
- #define MAX 1000
- int main()
- { int htoi(char s[]);
- int c,i,flag,flag1;
- char t[MAX];
- i=0;
- flag=0;
- flag1=1;
- printf("input a HEX number:");
- while((c=getchar())!='\0'&&i<MAX&&flag1)
- {if(c>='0'&&c<='9'||c>='a'&&c<='f'||c>='A'&&c<='F')
- {flag=1;
- t[i++]=c;
- }
- else if(flag)
- {t[i]='\0';
- printf("decimal number%d\n",htoi(t));
- printf("continue or not?");
- c=getchar();
- if(c=='N'||c=='n')
- flag1=0;
- else
- {flag=0;
- i=0;
- printf("\ninput a HEX number:");
- }
- }
- }
- return 0;
- }
- int htoi(char s[])
- { int i,n;
- n=0;
- for(i=0;s[i]!='\0';i++)
- {if(s[i]>='0'&&s[i]<='9')
- n=n*16+s[i]-'0';
- if(s[i]>='a'&&s[i]<='f')
- n=n*16+s[i]-'a'+10;
- if(s[i]>='A'&&s[i]<='F')
- n=n*16+s[i]-'A'+10;
- }
- return(n);
- }
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p94~96
这个题目本身就不伦不类,“输入一个十六进制数,输出相应的十进制数”这个要求其实用三行代码就可以完成:
int n;
scanf("%x",&n);
printf("相应的十进制数为%d\n",n);
对照代码,发现真正的要求应该是“输入十六进制数形式的字符序列,输出它所表示的数的十进制的值”。为了一个如此简单的要求,在main()函数中竟然使用了两个flag,并构造了一个无比复杂的while语句,令人吐血。下面就来分析一下这两个flag的利弊得失。
首先来看flag1:
- flag1=1;
- while((c=getchar())!='\0'&&i<MAX && flag1 )
- {
- if (/*……*/)
- { /*……*/
- }
- else
- if( flag )
- { /*……*/
- if(c=='N'||c=='n')
- flag1=0;
- else
- { /*……*/
- }
- }
- }
很显然,flag1的作用无非是用来结束while循环语句,没有其他作用。既然如此,为什么不采用简单明了的break语句呢?对比一下下面的写法
- while( (c=getchar())!='\0' && i<MAX )
- {
- if (/*……*/)
- { /*……*/
- }
- else
- if( flag )
- { /*……*/
- if(c=='N'||c=='n')
- break ;
- else
- { /*……*/
- }
- }
- }
就不难发现,flag1纯粹是画蛇添足的写法,应该删除。
删除了flag1之后,再来看flag。
- int c,i=0,flag = 0;
- char t[MAX];
- /*……*/
- while( (c=getchar())!='\0' && i<MAX )
- {
- if(c>='0'&&c<='9'||c>='a'&&c<='f'||c>='A'&&c<='F')
- {
- flag=1;
- t[i++]=c;
- }
- else
- if(flag)
- {
- t[i]='\0';
- /*……*/
- if((c=getchar())=='N'||c=='n')
- break ;
- else
- {
- flag = 0;
- i=0;
- /*……*/
- }
- }
- }
首先,当flag的值为0时,如果读入的字符不是十六进制数形式的字符,程序将继续循环;如果读入十六进制数形式的字符,则flag将反复地被赋值为1,并将该字符写入t数组;之后若再次读入非十六进制数形式的字符,则被作为'\0'写入t数组,然后询问是否重新读下一个十六进制数,如果回答不是'N'或'n',则开始下一轮处理。
不难看出,其中的“(c=getchar())!='\0'”是作茧自缚而又多此一举的循环控制条件,因为这种情况完全可以作为非十六进制数形式的字符来处理,这样程序的适应性更强。更何况从键盘输入'\0'字符是绝大多数用户并不了解的技巧。总之,无法弄清代码作者在这里的思路,就如同你永远不可能弄清头脑混乱的人在想什么一样。如果容许猜测的话,大概作者把处理字符串的常见写法稀里糊涂、移花接木地“嫁接”到了这里,这就如同给正常人安装了一条假肢一样。没人能弄清楚为什么给正常人装假肢。
另一个循环控制条件i则是代码作者心里没“数”、痴心妄想的产物
- #define MAX 1000
- /*……*/
- char t[MAX];
- /*……*/
- while((c=getchar())!='\0'&&i<MAX)
- /*……*/
- printf("decimal number%d\n",htoi(t));
在这里代码作者想表达的是程序可以转换最多不超过1000位的十六进制数字形式的字符序列。然而htoi()返回值的类型是int类型。int类型最多能存储几位十六进制整数是一个小学数学问题,这里就不宣布答案了。用C语言来描述,这个MAX其实应该是
- #include <limits.h>
- #define HEX_BIT (4)
- #define MAX ( sizeof ( unsigned ) * (CHAR_BIT/ HEX_BIT) )
这里的MAX是unsigned类型数据类型所能存储的十六进制数的最多位数。同样,htoi()这个函数的返回值应该是unsigned类型,因为main()中输入十六进制数形式的字符序列根本没有考虑正负号。
那么,完成样本代码4中的那个复杂的while语句的功能是否需要那个蹩脚的flag呢?答案是根本用不着。非但用不着,而且不用的话代码可以写得更简洁、更清晰。
样本代码4中的那个复杂的while语句的功能可以用下面简单的伪代码说明:
- do
- {
- //跳过非十六进制形式的字符
- //读取最多MAX个十六进制数形式的字符
- //询问用户是否继续
- }
- while(继续);
其中“跳过非十六进制字符”可以自己写函数完成,也可以使用库函数。如果使用库函数,相应的代码为:
- scanf("%*[^0123456789ABCDEFabcdef]");
其中的“*”表示读取但不存储,“^0123456789ABCDEFabcdef”表示读取非十六进制数形式的字符。
“读取最多MAX个十六进制数形式的字符”可以用
- char hex_str[ MAX + 1 ] ;
- scanf("%8[0123456789ABCDEFabcdef]%*[^\n]",hex_str);
这里的8为MAX的值。这将保证向hex_str数组中最多写入8个十六进制数形式的字符。
改正后的代码为:
- #include <stdio.h>
- #include <limits.h>
- #define HEX_BIT 4 //一个十六进制数字符占4位
- #define MAX ( sizeof( unsigned ) * ( CHAR_BIT / HEX_BIT ) )
- #define HEX_CHAR "0123456789ABCDEFabcdef"
- unsigned htoi(char []);
- int main( void )
- {
- char hex_str[ MAX + 1 ] ; // + 1 for \0
- char yn;
- do
- {
- printf("请输入一个十六进制数:");
- scanf("%*[^"HEX_CHAR"]");
- scanf("%8[" HEX_CHAR"]%*[^\n]",hex_str);
- printf("%s的十进制为%u\n",hex_str,htoi(hex_str));
- printf("继续(Y/N)?");
- scanf(" %c",&yn);
- }
- while(!(yn=='N'||yn=='n'));
- return 0;
- }
- unsigned htoi(char s[])
- {
- unsigned i , n = 0 ;
-
- for( i = 0 ; s[i] != '\0' ; i++ )
- {
- n *= 16 ;
- if(s[i]>='0'&&s[i]<='9')
- n += s[i] - '0' ;
- else if(s[i]>='a'&&s[i]<='f')
- n += s[i] - 'a' + 10 ;
- else if(s[i]>='A'&&s[i]<='F')
- n += s[i] - 'A' + 10 ;
- }
-
- return n ;
- }