4.13 示例
现在我们给出几个具体的示例程序,它们都使用了本章讲述的C++/C基础知识,供C++/C初级程序员学习。
例1:从键盘输入一行字符,直到按下回车键,然后将所有字符转换成大写字符并反序输出。
从键盘读入字符使用getchar()库函数,读入字符串使用gets()库函数,因此需要预先申请在内存中保存字符串的缓冲区。但是我们无法预知用户到底会输入多少个字符才结束,因此要预留足够的空间。转换工作使用库函数toupper()完成,输出字符使用putchar()库函数。完整的程序见示例4-18。
示例4-18
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char temp[255] = { '\0' }; printf("Input a string, then press <CR> : \n"); gets(temp); /* * 或者使用:int i=0; * while((temp[i]=getchar())!='\n')i++; * temp[i]='\0'; */ unsigned int strLen = strlen(temp); for(int j = strLen -1; j >= 0; j--) { temp[j] = toupper(temp[j]); putchar(temp[j]); } putchar('\n'); return 0; }
例2:计算正整数n的阶乘。可以使用迭代法计算也可以使用递归法计算,我们将在第7章中给出递归法的程序。注意这种计算可能导致结果“上溢”(试着输入20并查看输出)。程序见示例4-19。
示例4-19
#include <stdio.h> int main(void) { unsigned long n = 0, fact = 1; printf("Input a positive number : "); scanf("%d", &n); for(unsigned int k = 1; k <= n; k++) fact = fact * k; printf("The factorial of %d is %d: \n", n, fact); return 0; }
例3:输入一个正整数n,计算斜边长在n以内的所有可能的边长为整数的直角三角形的边长组合。最笨的办法是使用三重循环,每一重循环都迭代n次。不过这样检测出来的三角形有重复的(如<5, 3, 4>和<5, 4, 3>是同一个三角形),而且性能太低,执行了许多无谓的循环检测。我们仅检测斜边和一条直角边固定的情况下,另一条直角边大于或等于第一条直角边并小于斜边的情况就可以了。不过这样还是执行了一些无用的检测。我们知道,如果斜边和第一条直角边固定,则第二条直角边必定小于等于 的整数部分的值,因此我们按照这个算法来搜索。程序见示例4-20,读者可能还有更高效率的解法,请编程试一试。
示例4-20
#include <stdio.h> #include <math.h> int main(void) { } unsigned int n = 0, count = 0; printf ("Input a positive number : "); scanf ("%u", &n); printf ("The list of right-angled triangles: \n"); for(unsigned int r=1;r<=n;r++){ /*斜边r从1到n迭代*/ unsigned long rsquare = r * r; for(unsigned int p=1;p<r;p++){ /*第一条直角边p从1到r迭代*/ unsigned int psquare = p * p; unsigned int w = (unsigned int)sqrt (rsquare - psquare); for( unsigned int q = p; q <= w; q++) { /*第二条直角边q从p到w迭代*/ if( rsquare == (psquare + (q * q) ) ) { printf ("%u\t\t%u\t\t%u \n", r, p, q); count++; } } } } if(count == 0) { printf ("No such right-angled triangles!\n"); }else { printf ("There are %d right-angled triangles in total!\n", count); } return 0; }
计算机不仅可以解决数学领域的复杂计算,还可以通过计算来解决逻辑推理难题。现在的“人工智能”就是利用计算机“不知疲倦”的计算能力来进行定理证明、逻辑推理和决策支持等工作的。
例4:协助破案。假设已经查清,有A、B、C、D、E五个嫌疑人可能参与制造了一起银行抢劫案,但是不知道其中到底哪几个人是真正的案犯。不过,有确凿证据表明:
① 如果A参与了作案,则B一定也会参与。
② B和C两人中只有一人参与了作案。
③ C和D要么都参与了作案,要么都没有参与。
④ D和E两个人中至少有一人参与作案。
⑤ 如果E作案,则A和D一定参与作案。
是不是有点绕口?我们可以用数理逻辑中的正规表达式来表示上述论断:
① A→B
②(B∧¬C)∨(¬B∧C)
③(C∧D)∨(¬C∧¬D)
④(D∨E)
⑤ E→(A∧D)
我们现在用1(可理解为TRUE)表示作案,0(可理解为FALSE)表示未作案,则每个人的取值范围就是{0,1}。然后我们在5个人取值的所有可能的组合空间中进行搜索,如果某个组合同时满足这5条线索,那么它就是本案的答案了。上述正规表达式可以进一步表示为下列C++/C逻辑表达式:
① A == 0 ||(A == 1 && B == 1)
② B + C == 1
③ C == D
④ D + E >= 1
⑤ E == 0 ||(E == 1 && A == 1 && D == 1)
我们用另一个变量sum来表示组合空间中某一个组合能够同时满足的论断个数,如果出现了这样一个组合——它同时满足了这5条论断——那么它就是我们要找的组合。当然,同时满足所有论断的组合可能不止一个,因此我们有必要搜索整个组合空间(虽然在本案中这种情况不应该存在,但是在做其他类似的搜索时不要忘记这一点)。程序见示例4-21。
示例4-21
#include <stdio.h> int main (void) { int count=0; // 满足所有条件的答案个数 int sum=0; // 累计满足的条件个数 int A, B, C, D, E; for ( A = 0; A < 2; A++) { for ( B = 0; B < 2; B++) { for ( C = 0; C < 2; C++) { for ( D = 0; D < 2; D++) { for ( E = 0; E < 2; E++) { sum=0; // 计数器清0 sum += ( A == 0 || ( A == 1 && B == 1 ) ) ? 1 : 0; sum += ( ( B + C ) == 1 ) ? 1 : 0; sum += ( C == D ) ? 1 : 0; sum += ( ( D + E ) >= 1 ) ? 1 : 0; sum += ( E == 0 || ( E == 1 && A == 1 && D == 1 ) ) ? 1 : 0; if(sum==5){ // 找到一个满足所有条件的组合 count++; printf ("// This is the %d th answer:\n", count); printf ("Suspect A is %s.\n", ( A == 1 ) ? "a criminal" : "not a criminal"); printf ("Suspect B is %s.\n", ( B == 1 ) ? "a criminal" : "not a criminal"); printf ("Suspect C is %s.\n", ( C == 1 ) ? "a criminal" : "not a criminal"); printf ("Suspect D is %s.\n", ( D == 1 ) ? "a criminal" : "not a criminal"); printf ("Suspect E is %s.\n", ( E == 1 ) ? "a criminal" : "not a criminal"); printf ("\n"); } } } } } } return 0; }
输出结果如下:
// This is the 1 th answer: Suspect A is not a criminal. Suspect B is not a criminal. Suspect C is a criminal. Suspect D is a criminal. Suspect E is not a criminal.
在这个例子中,goto语句还是很有用的!
例5:数值计算。最后我们举一个数值计算的例子:使用迭代法计算超越方程f (x)= 0的近似解。
理论证明:最高次幂不高于4的一元方程的根可用根式来表示,而高于4次的则一般不存在根的解析表达式,对于超越方程则更不太可能存在。所以对于超越方程,要借助数值计算来寻找方程的近似解。
已知方程x6 -8 x -4 = 0,求它的近似解,要求误差小于10-10。
迭代法是这样的:首先将方程f (x) = 0改写为如下的等价形式:
x = g (x)
然后在可能的根区间[a, b]上任取一点x0,代入g (x),得到x1 = g (x0),一般说来x1≠x0;再把x1代入g (x),得到x2 = g (x1);……这样就计算出了一系列值:
x0, x1, x2, …, xn-1, xn, …
当(xn - xn-1)的绝对值小于给定的误差时就可以认为xn是所求的根了。
我们把方程改写为 。取x0 = 1,程序见示例4-22。
示例4-22
#include <stdio.h> #include <math.h> double g(double x); int main(void) { double x0 = 1, x1 = 0; int count = 0; printf("x%d = %10f\n", count, x0); while( fabs( x1- x0 ) >= 1e-10 ){ x1 = x0; x0 = g(x1); count++; printf("x%d = %10f\n", count, x0); } printf("approximate x = %10f\n", x0); return 0; } double g(double x) { return pow(x, 6) / 8.0-0.5; } x0= 1.000000 x1= -0.375000 x2= -0.499652 x3= -0.498055 x4= -0.498092 x5= -0.498091 x6= -0.498091 x7= -0.498091 x8= -0.498091 approximate x= -0.498091
我们看到,在第5次迭代后就已经找到了满足误差条件的解,所以后面的迭代就是不必要的了。