高质量程序设计指南:C++/C语言
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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),一般说来x1x0;再把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次迭代后就已经找到了满足误差条件的解,所以后面的迭代就是不必要的了。