第5章像搭积木一样搭建程序 5.1习题解析 5.1请联系生活中的例子,描述一下模块化思想。 参考答案: 模块化思想使用非常广泛,在大规模的现代工业化生产随处可见。例如,计算机的制造过程就是模块化的生产过程。计算机的配件是模块化的配件,这些配件一般都有卡槽,通过连接线可以很容易地将它们连接在一起。不同公司生产的计算机配件的功能和性能可能是不同的,但是这些卡槽和连接线的接口一定是相同的。这样计算机的集成商通过选择性价比不同的计算机配件就可以快速地组装出一台性价比不同的计算机产品。 5.2简要介绍库函数的作用,并举例介绍库函数的使用方法。 参考答案: C语言的库函数并不是C语言本身的一部分,它是由编译程序根据一般用户的需要,编制并提供用户使用的一组程序。也就是说,C语言库函数是别人实现的自定义函数,只不过这些常用的函数被放在了库中,供其他程序员使用。 C的库函数极大地方便了用户,同时也补充了C语言本身的不足。在编写C语言程序时,使用库函数,既可以提高程序的运行效率,又可以提高编程的质量。 程序员使用库函数时,需要把声明库函数的头文件名用#include < >或#include " "加到程序的头部(尖括号或西文双引号内填写文件名)。例如,使用数学计算库函数时,需要包含math.h,在程序头部需要书写#include 。 5.3简要介绍形参与实参的概念,并说明它们之间数据的传递过程。 参考答案: 形参全称为形式参数,它在函数定义中出现,可以被看作一个占位符。由于在函数定义的时候形参没有数据,只能等到函数被调用时才接收传递进来的数据,所以称为形式参数。 实参全称为实际参数,它是函数被调用时给出的参数,出现在函数调用过程中。实参包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数。 形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。即通过函数调用,主调函数可以将它所包含的实参变量的数据传递给被调函数的形参变量,并执行被调函数的函数体中的语句对形参变量的数据进行处理。 5.4实现一个判别输入正整数是否为素数的函数。比如当函数的输入数据是“17”时,函数的输出为“1”,表示该数为素数; 如果输入“20”,则输出为“0”,表示该数为合数。 参考程序: // 函数说明: 对代入的一个整数,判断其是否为素数 // 形式参数: n,整型,待判断的整数值 // 返回值: 整型,若n为素数,为1,若n为合数,为0,若n为不合法的输入,为-1 int isPrime(int n) { int i; int p = 1; if (n<=1) { return -1; } for(i=2;i*i<=n;i++) { if(n%i==0) { p = 0; break; } } return p; } 解析: (1) 函数命名为isPrime,因为只要判断一个正整数,所以参数为一个。 (2) 函数内的p,可以看作一个flag(标志),默认为1,默认输入的整数为素数。 (3) return -1处的判断,是判断输入的数据是否合法,要求输入的数不能小于1。 (4) for循环用于寻找n的因子,如果能找一个n的因子,那么p置0,表示n是一个合数,n的因子找到一个即可,所以利用一个break中断循环,这样可以节省循环的时间。 5.5根据摄氏温度和华氏温度的转换关系 C=59×(F-32) 其中C表示摄氏度,F表示华氏温度,实现一个从华氏温度到摄氏温度换算的函数。 参考程序: // 函数说明: 将华氏温度换算为对应的摄氏温度 // 形式参数: f,float类型,华氏温度值 // 返回值: float类型,换算的摄氏温度值 float TempTrans(float f) { float c; c = 5.0*(f-32)/9; return c; } 解析: 本题非常简单,但该函数包括了函数定义的基本要素,并且能够完成一个比较有意义的换算。 5.6利用主教材5.4节中求距离和三角形面积的函数,实现一个求任意凸四边形面积的函数,函数的参数为4个顶点的坐标值。 参考程序: // 函数说明: 代入三角形的3个顶点坐标,计算三角形的面积,使用主教材例5.22中的函数 // distance 和 triangleArea // 形式参数: // x1,y1,float类型,第一个顶点的坐标 // x2,y2,float类型,第二个顶点的坐标 // x3,y3,float类型,第三个顶点的坐标 // x4,y4,float类型,第四个顶点的坐标 // 返回值: float类型,四边形面积 float convexQuadArea(float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4) { float a,b,c; float s1,s2,s; a = distance(x1,y1,x2,y2); b = distance(x1,y1,x3,y3); c = distance(x2,y2,x3,y3); s1 = triangleArea(a,b,c); a = distance(x1,y1,x4,y4); b = distance(x1,y1,x3,y3); c = distance(x4,y4,x3,y3); s2 = triangleArea(a,b,c); s = s1+s2; return s; } 图5.1利用三角形计算凸四边形 的面积 解析: 如图5.1所示,任何一个凸四边形都可以划分成两个三角形。所以求凸四边形的面积就可以转换为求两个三角形的面积之和。参考代码体现了像搭积木一样搭建程序的思想。在参考代码中,多次反复调用distance函数和triangleArea函数,体现了模块的复用性。 当然以上参考代码对4个顶点的输入要求比较严格,输入的时候按顺时针或逆时针的顺序输入,并且要保证输入的是一个凸四边形。 5.7利用5.6题的方法,设计求任意凸多边形面积的函数,可以将多边形的顶点坐标利用数组进行存储,函数以数组作为参数。数组相关的知识参考主教材第6章。 参考程序: // 函数说明: 代入凸多边形顶点坐标和边数,计算凸多边形面积,使用主教材例5.22中 // 的函数distance 和 triangleArea // 形式参数: // x[],float类型数组,各个顶点的横坐标 // y[],float类型数组,各个顶点的纵坐标 // n,整型,顶点数 // 返回值: float类型,多边形面积 float convexArea(float x[],float y[], int n) { float a,b,c; float s = 0.0; int i; for(i=1;i // 函数说明: 对n个圆盘的汉诺塔问题,输出解决方案 // 形式参数: // n,整型,圆盘个数 // A,字符型,圆盘所处的初始柱子 // B,字符型,圆盘搬移过程中的辅助柱子 // C,字符型,圆盘搬移后的目的柱子 // 返回值: void,无返回值 void HanoiMove(int n, char A, char B, char C) { if(n==1)//递归退出条件 printf("%c --> %c \n", A, C); else { HanoiMove(n-1,A,C,B);   //第一步,从A整体搬移n-1个圆盘到B printf("%c --> %c \n", A, C);  //第二步,将最大的圆盘从A搬移到C HanoiMove(n-1,B,A,C);   //第三步,从B整体搬移n-1个圆盘到C } } // 调用该函数的主函数 int main(void) { HanoiMove(7, 'A', 'B', 'C'); return 0; } 解析: 这是一个很古老的数学问题,而且只能用递归的方式来编程实现。 图5.3汉诺塔游戏的初始状态 示意图 虽然这个游戏规定一次只能挪动一个圆盘,但是我们在考虑问题的时候,可以考虑整体挪动圆盘。 图5.3代表初始状态。3个圆柱分别用A、B、C来表示。A柱上的矩形代表最底下的一块圆盘,三角形代表上面n-1块圆盘。 此时在main函数内调用HanoiMove函数。该函数第一个参数的意思是要挪动多少个圆盘,参数A代表圆盘所处的初始的柱子,参数C代表最终的柱子,而参数B代表中间需要辅助的柱子B。 在main函数调用该函数 HanoiMove(7, 'A', 'B', 'C'); 意思是将7个圆盘从A柱上利用中间的B柱,最终挪到C柱上。 那这个工作是如何具体实现的呢? 可以这样去想: 假设我们有办法把A柱上面n-1个圆盘挪到B柱上,然后就可以把最后一个最大的圆盘从A挪动到C柱子上。只要最后一个圆盘挪动成功了,那在B柱子上剩下的n-1个圆盘中的最大的圆盘是不是可以利用通用的方法,借助A柱挪动到C柱上呢?这样问题规模就可以从n减到n-1。按照这个思路,继续执行,要移动的圆盘数会继续减为n-2,n-3,一直到1,问题就解决了! 所以汉诺塔问题的解决思路如下: 第一步,将n-1个圆盘从A柱搬移到B柱,此时A柱只留一个最大的圆盘,B柱所有圆盘从小到大叠放在一起(见图5.4)。 第二步,将最大的圆盘从A柱搬移到C柱,此时最大的圆盘已经搬移到位了(见图5.5),只剩下其他n-1个圆盘要从B柱搬移到C柱。 第三步,将剩下的n-1个圆盘从B柱搬移到C柱,就完成了最终的任务。 图5.4汉诺塔游戏第一步搬移结果示意图,最后一个圆盘保持位置不变,其他圆盘移至B柱 图5.5汉诺塔游戏第二步搬移结果示意图,最后一个圆盘移至C柱,其他圆盘移至B柱 由于大任务中包含着小任务的解决过程,因此可以利用递归方法实现程序。要注意,这里递归的结束条件是: 除了最小的圆盘外,其他圆盘都搬移到C柱了,待搬移的圆盘只剩最后1个。因此在递归调用时,n=1是结束条件。 5.10利用函数实现复数的简单运算。复数的形式是a+bi的形式,其中a和b都为实数,a称为复数的实部,b称为复数的虚部。复数同样有加减乘除四则运算。现在考虑设计一个简单的复数运算的函数,比如复数的加法ComplexAdd,假设将该函数定义为 double ComplexAdd(double a,double b,double c,double d) 其中,a代表第一个复数的实部,b代表第一个复数虚部的系数,c代表第二个复数的实部,d代表第二个复数虚部的系数。 试问该函数的定义能否返回两个复数相加的结果。如果这种定义方式不便于实现,请参考主教材第6章数组和第7章结构体的内容,设计一个可以满足需求的函数。 参考答案: 不能返回两个复数相加的结果。如果还采用a、b、c和d分别表示两个复数的实部和虚部,可以采用间接访问的方式,利用指针实现加法计算。这时由于没有需要返回的值,所以函数类型可以定义为void类型。 // 函数说明: 对两个复数计算它们的和 // 形式参数: // a,double类型,参与运算的第一个复数实部 // b,double类型,参与运算的第一个复数虚部 // c,double类型,参与运算的第二个复数实部 // d,double类型,参与运算的第二个复数虚部 // r,double指针类型,记录和的实部 // i,double指针类型,记录和的虚部 // 返回值: void,无返回值 void ComplexAdd(double a,double b,double c,double d,double *r,double *i) { *r = a + c; *i = b + d; } 利用主教材第7章结构体的内容可以实现如下的函数。 typedef struct {   //结构体类型的定义,参见第7章 double real;   //结构体成员类型和名称 double imag; } complex; // 函数说明: 对两个复数计算它们的和 // 形式参数: // a,complex结构体类型,参与运算的复数 // b,complex结构体类型,参与运算的复数 // 返回值: complex结构体类型,复数加法的结果 complex ComplexAdd(complex a, complex b) { complex c; c.real = a.real+b.real;   //结构体变量的成员对应相加 c.imag = a.imag+b.imag; return c; } 解析: 如果按照题干的思路,所给出的函数首部是不能实现返回一个复数的功能的。因为一个函数只能返回一个值,而复数包括实部和虚部两部分。如果需要同时返回实部和虚部,必然要返回两个值。因此仅通过返回值的方式无法实现。 从函数的定义来看,本参考程序不是很复杂。这里用到了结构体的相关知识。通过结构体定义了一个新的数据类型complex。该类型包括两个成员,分别表示复数的实部和虚部。定义新的结构体类型以后,函数返回值可以直接返回一个complex类型的数据。 5.11设计一个函数计算两个日子相隔多少天。函数的参数可以为6个,分别代表起始的年、月、日和截止的年、月、日。注意,在计算过程中,可能需要多次计算某一个年份是否为闰年,所以可以首先设计一个判断某年为闰年的函数。 参考程序: #include // 函数说明: 代入一个整数,判断该年份是否为闰年 // 形式参数: year,整型,年份 // 返回值: 整型,1表示闰年,0表示平年 int isLeap(int year) { int i = 0; if(((year%4 == 0)&&(year%100 != 0))||(year%400) == 0) { i = 1; } return i; } // 函数说明: 给定一个日期(年、月、日),计算当年已经过去的天数 // 形式参数: year,month,day,整型,年,月,日 // 返回值: 整型,一年中已经经过的天数 int daysPastofYear(int year,int month,int day) { int sum = 0; switch(month) { case 1:sum = day;break; case 2:sum = 31 + day;break; case 3:sum = 59 + day;break; case 4:sum = 90 + day;break; case 5:sum = 120 + day;break; case 6:sum = 151 + day;break; case 7:sum = 181 + day;break; case 8:sum = 212 + day;break; case 9:sum = 243 + day;break; case 10:sum = 273 + day;break; case 11:sum = 304 + day;break; case 12:sum = 334 + day;break; default : printf("输入的月份有错误\n"); break; } if(month>2) { if(isLeap(year)){ sum = sum+1; } } return sum; } // 函数说明: 代入两个年份,计算它们相差的天数 // 形式参数: year1,year2,整型,两个参与比较的年份 // 返回值: 整型,两个年份相差的天数 int daysBetweenYears(int year1,int year2) { int sum_year_day = 0; int i = 0; sum_year_day = (year2-year1)*365; for(i=year1;i int max(int x,int y) { return x>y?x:y; } int main(void) { int a = 3; int b = 4; int c; c = max(a,b); printf("%d,%d,%d",a,b,c); return 0; } (A) 3,4,3(B) 3,4,4(C) 3,4,0(D) 3,4,1 7. 下列程序的输出结果为()。 #include int change(int x,int y) { x = 1; y = 2; return x+y; } int main(void) { int x = 3; int y = 4; int z; z = change(x,y); printf("%d,%d,%d",x,y,z); return 0; } (A) 1,2,3(B) 3,4,3(C) 3,4,7(D) 1,2,7 8. 有以下代码,则代码填充处可以正确执行的语句为()。 #include int main(void) { /************代码填充处********/ /************end**************/ } int Max(int x,int y) { int max; if(x>y) max = x; else max = y; return max; } (A) int z = Max(0,1); (B) int Max(int x,int y); int z = max(0,1); (C) int max(int x,int y); int z = max(x,y); (D) int Max(int x,int y); int z = Max(0,2)+7; 9. 有以下程序: #include int func(int a,int b) { int c; c = a+b; return c; } int main(void) { int x = 6,y = 7,z = 8,r; r = func((x--,y++,x+y),y); printf("%d\n",r); return 0; } 则程序的输出结果是()。 (A) 11(B) 10(C) 21(D) 31 10. 有以下程序: #include int fun(int a, int b) { if(b == 0) return a; else return(fun(--a, --b)); } main(void) { printf("%d\n", fun(4, 2)); } 程序运行后的输出结果是()。 (A) 1(B) 2(C) 3(D) 4 11. 已定义以下函数: int fun( int *p) {return* p;} fun函数的返回值是()。 (A) 一个整数(B) 不确定的值 (C) 形参p中存放的值(D) 形参p的地址值 12. 程序源代码如下所示,它的输出结果为()。 #include void hello_world(void) { printf("Hello, world!\n"); } void three_hellos(void) { int counter; for (counter = 1; counter <= 3; counter++) hello_world( );// 调用hello_world函数 } void main(void) { three_hellos( );   // 调用three_hellos函数 } 13. 如下源代码的执行结果是()。 #include int main(void) { int i,num; num = 2; for(i=0;i<3;i++) { printf(" The num equal %d\n",num); num++; { static int num = 1; printf(" The internal block num equal %d\n",num); num++; } } return 0; } 14. 编写一个函数,输入n为偶数时,计算1/2+1/4+…+1/n; 当输入n为奇数时,计算1/1+1/3+…+1/n。 15. 斐波那契数列问题: 有一对兔子,从出生后第三个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?利用递归方法编程计算结果。