如果n和n+2都是素数,则称它们是孪生素数。输入m,输出两个数均不超过m的最大孪生素数。5≤m≤10000。例如m=20时答案是17、19,m=1000时答案是881、883。

【分析】

被1和它自身整除的、大于1的整数称为素数。由于要判断n和n+2是否是素数,所以把“判断素数”可以写成一个函数,只需调用这个函数两次就可以了。这样的“判断一个事物是否具有某一性质”的函数还有一个学术名称——谓词(predicate)。

程序4-2  孪生素数(1)

 

#include<stdio.h> /* do NOT use this if x is very large or small */ int is_prime(int x) {   int i;   for(i = 2; i*i <= x; i++)     if(x % i == 0)   return 0;   return 1; }  int main() {   int i, m;   scanf("%d", &m);   for(i = m-2; i >= 3; i--)     if(is_prime(i) && is_prime(i+2)) {       printf("%d %d\n", i, i+2);       break;     }   return 0; } 


 

说明:(1)在is_prime函数的编写中,用到了两上小技巧。一是只判断不超过sqrt(x)的整数i;二是及时退出:一旦发现x有一个大于1的因子,立刻返回0(假),只有最后才返回1(真)。

(2)函数的命名应注意做到“见名知意”,即选有含义的英文单词(或其缩写)作为函数名。例如,“is_prime”取自英文“is is a prime?”(它是素数吗?)。

提示4-8:建议把谓词(用来判断某事物是否具有某种特性的函数)命名成“is_xxx”的形式。它返回int值,非0表示值,0表示假。

提示4-9:编写函数时,应尽量保证它能对任何合法参数都能得到正确的结果。如若不然,应在显著位置标明函数的缺陷,以避免误用。

下面改进之后的版本:

程序4-3  孪生素数(2)

#include <stdio.h> #include <math.h> #include <assert.h> int is_prime(int x){   int i, m;   assert(x >= 0);     //使用断言,防止x<0   if(x == 1) return 0;   m = floor(sqrt(x) + 0.5);   for(i = 2; i <= m; i++)     if(x % i == 0) return 0;   return 1; }  int main(){   int i, m;   scanf("%d", &m);   for(i = m-2; i >= 3; i--)     if(is_prime(i) && is_prime(i+2)) {       printf("%d %d\n", i, i+2);       break;     }   return 0; } 


 

除了特判n==1的情况外,程序中还使用了变量m,一方面避免了每次重复计算sqrt(x),另一方面也通过四舍五入避免了浮点误差。

最后,程序使用了assert.h的assert宏来限制非法的函数调用:当x>=0不成立时,程序将异常终止,并给出了提示信息。

说明:(1)断言(assert)的语义如下:如果表达式的值为0(假),则输出错误消息并终止程序的执行(一般还会出对话框,说明在什么地方引发了assert);如果表达式为真,则不进行任何操作。因此,断言失败就表明程序存在一个bug。

(2)C/C++的宏(assert)就是这样的断言,当表达式为假时,调用库函数abort()终止程序。

(3)程序中可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段,所以不要把程序中的assert语句删除掉。

(4)如果程序在assert处终止了,并不是说含有该assert的函数有错误,而是调用函数出了差错,assert可以帮助我们追踪到错误发生的原因。

(5)在函数的入口处,建议使用断言来检查参数的有效性(合法性)。请给assert语句加注释,告诉人们assert语句究竟要干什么。

提示4-10:编程时合理利用assert宏,将给调试带来很大的方便。

总而言之,在实际的系统中,“一个地方的参数错误就引起整个程序异常退出”是不可取的,在编写和调试算法程序中,assert会“迫使”编写出更高质量的程序。