配色: 字号:
《C语言程序设计(第2版)》第10章函数与程序结构
2023-05-24 | 阅:  转:  |  分享 
  
Chap 10 函数与程序结构10.1 圆形体积计算器 10.2 汉诺塔问题 10.3 长度单位转换 10.4 大程序构成 本章要
点怎样把多个函数组织起来?怎样用结构化程序设计的思想解决问题?怎样用函数嵌套求解复杂的问题?怎样用函数递归解决问题?如何使用宏?如
何使用多文件模块构建较大规模程序使用结构化程序设计方法解决复杂的问题把大问题分解成若干小问题,小问题再进一步分解成若干更小的问题写
程序时,用main()解决整个问题,它调用解决小问题的函数这些函数又进一步调用解决更小问题的函数,从而形成函数的嵌套调用10.1
圆形体积计算器 程序结构例10-1 设计一个常用圆形体体积计算器,采用命令方式输入1、2、3,分别选择计算球体、圆柱体、圆锥体的体
积,并输入计算所需相应参数。分析:输入1、2、3选择计算3种体积,其他输入结束计算设计一个控制函数cal(),经它辨别圆形体的类型
再调用计算球体、圆柱体、圆锥体体积的函数设计单独的函数计算不同圆形体的体积10.1.1 程序解析-计算常用圆形体体积3层结构,5个
函数降低程序的构思、编写、调试的复杂度可读性好程序结构例10-1源程序#define PI 3.141592654void c
al ( int sel ); int main(void){ int sel; while( 1 ){
printf(" 1-计算球体体积\n"); printf(" 2-计算圆柱体积\n"); printf("
3-计算圆锥体积\n"); printf(" 其他-退出程序运行\n"); printf(“ 请输入计算命
令:" ); scanf("%d",&sel); if (sel < 1 || sel > 3)
break; / 输入非1~3,循环结束 / else cal (sel );
/ 输入1~3,调用cal() / } return 0;}/ 常用圆形体体积计算器的主控函数 /void
cal ( int sel ){ double vol_ball(void ); double vol_cylind
(void ); double vol_cone(void ); switch (sel) { cas
e 1: printf("球体积为:%.2f\n", vol_ball( )); break; case 2:
printf("圆柱体积为:%.2f\n", vol_cylind( ) ); break; case 3:
printf("圆锥体积为:%.2f\n", vol_cone( ) ); break; }}/ 计算球体体积 V=4/
3PIrrr /double vol_ball( ){ double r ; printf("请输入球的半
径:"); scanf("%lf",&r); return(4.0/3.0PIrrr);}/ 计算圆柱体
积 V=PIrrh /double vol_cylind( ){ double r , h ; prin
tf("请输入圆柱的底圆半径和高:"); scanf("%lf%lf",&r,&h); return(PIrr
h);}/ 计算圆锥体积 V=h/3PIrr /double vol_cone( ){ double r
, h ; printf("请输入圆锥的底圆半径和高:"); scanf("%lf%lf",&r,&h); r
eturn(PIrrh/3.0);}10.1.2 函数的嵌套调用顺序调用int main(void){ …… y =
fact(3); …… z = mypow(3.5, 2); ……}double fact(int n){
……}double mypow(double x, in n){ ……}嵌套调用int main(void){ ……
cal (sel); ……}void cal (int sel){ …… vol_ball() ……}
double vol_ball( ){ ……}10.1.2 函数的嵌套调用例10-1 分析int main(void){
…… cal (sel);}void cal (int sel){ …… vol_ball(); vol_c
ylind(); vol_cone();}double vol_ball( ){ ……}double vol_cyl
ind( ){ ……}double vol_cone( ){ …… }在一个函数中再调用其它函数的情况称为函数的嵌
套调用。如果函数A调用函数B,函数B再调用函数C,一个调用一个地嵌套下去,构成了函数的嵌套调用。具有嵌套调用函数的程序,需要分别定
义多个不同的函数体,每个函数体完成不同的功能,它们合起来解决复杂的问题。10.1.2 函数的嵌套调用结构化程序设计方法 自顶向下,
逐步求精,函数实现 自顶向下:程序设计时,应先考虑总体步骤,后考虑步骤的细节;先考虑全局目标,后考虑局部目标。先从最上层总目标开始
设计,逐步使问题具体化。不要一开始就追求众多的细节。 逐步求精:对于复杂的问题,其中大的操作步骤应该再将其分解为一些子步骤的序列,
逐步明晰实现过程。函数实现:通过逐步求精,把程序要解决的全局目标分解为局部目标,再进一步分解为具体的小目标,把最终的小目标用函数来
实现。问题的逐步分解关系,构成了函数间的调用关系。限制函数的长度。一个函数语句数不宜过多,既便于阅读、理解,也方便程序调试。若函数
太长,可以考虑把函数进一步分解实现。避免函数功能间的重复。对于在多处使用的同一个计算或操作过程,应当将其封装成一个独立的函数,以达
到一处定义、多处使用的目的,以避免功能模块间的重复。减少全局变量的使用。应采用定义局部变量作为函数的临时工作单元,使用参数和返回值
作为函数与外部进行数据交换的方式。只有当确实需要多个函数共享的数据时,才定义其为全局变量。函数设计时应注意的问题10.2 汉诺塔
问题 10.2.1 程序解析10.2.2 递归函数基本概念10.2.3 递归程序设计10.2.1 汉诺(Hanoi)塔问题解析
将64 个盘从座A搬到座B(1) 一次只能搬一个盘子(2) 盘子只能插在A、B、C三个杆中(3) 大盘不能压在小盘上
A B C分析 A
B C分析 A B C A
B Cnn-1n-1分析 A B C A
B Cn10.2.1 汉诺(Hanoi)塔问题解析递归方法的两个要点(1)递归出口:
一个盘子的解决方法;(2)递归式子:如何把搬动64个盘子的问题简化成搬动63个盘子的问题。把汉诺塔的递归解法归纳成三个步骤:n-1
个盘子从座A搬到座C第n号盘子从座A搬到座Bn-1个盘子从座C搬到座B算法:hanio(n个盘,A→B, C为过渡) {
if (n == 1) 直接把盘子A→B else{ hanio(n-1个盘,A→C, B为过渡)
把第n号盘 A→B hanio(n-1个盘,C→B, A为过渡) }}10.2.2递归函数基本概念例10
-2 用递归函数实现求n!递推法在学习循环时,计算n!采用的就是递推法: n!= 1×2×3×…×n用循环语句实现: res
ult = 1; for(i = 1; i <= n; i++) result = result i;递归法n!=
n ×(n-1)! 当n>1 递归式子 = 1 当n=1或n=0 递归出口即求n!可以在(n-1)!的
基础上再乘上n。如果把求n!写成函数fact(n),则fact (n)的实现依赖于fact(n-1)。10.2.2递归函数基本概念
例10-2 用递归函数求n!。#include double fact(int n);int main(vo
id){ int n; scanf ("%d", &n); printf ("%f", fact (n) );
return 0;}double fact(int n) / 函数定义 /{ double
result; if (n==1 || n == 0) / 递归出口 / re
sult = 1; else result = n fact(n-1); return
result;}10.2.2 递归函数基本概念递归式递归出口例10-2分析求n! 递归定义n! = n (n-1)! (n
> 1) n! = 1 (n = 0,1)#include double fact
(int n);int main(void){ int n; scanf ("%d", &n); printf (
"%f", fact (n) ); return 0;}double fact(int n){ double resul
t; if (n==1 || n == 0) result = 1; else resu
lt = n fact(n-1); return result;}fact(n)=nfact(n-1);m
ain() fact(3) fact(2)
fact(1) { .... { .... { ...
. { .... printf(fact(3)) f=3fact(2)
f=2fact(1) f=1} return(f)
return(f) return(f)
} } } 递归函
数 fact( n )的实现过程fact(3)= 3fact(2)=
2fact(1)=
fact(1)=121=23
2=6同时有4个函数在运行,且都未完成10.2.3 递归程序设计用递归实现的问题,满足两个条件:问题可以逐步简化成自身较简单的
形式(递归式)n! = n (n-1)! n n-1Σi = n +Σ i i=1
i=1递归最终能结束(递归出口)两个条件缺一不可解决递归问题的两个着眼点10.2.3 递归程序设计例1
0-3 编写递归函数reverse(int n)实现将整数n逆序输出。 分析:将整数n逆序输出可以用循环实现,且循环次数与
n的位数有关。递归实现整数逆序输出也需要用位数作为控制点。归纳递归实现的两个关键点如下:递归出口:直接输出n,如果n<=9,即n为
1位数递归式子:输出个位数n%10,再递归调用reverse(n/10) 输出前n-1位,如果n为多位数10.2.3 递归程序设
计由于结果是在屏幕上输出,因此函数返回类型为void void reverse(int num){ if(num<=9) p
rintf("%d",num); / 递归出口 / else{ printf("%d",num%10); rev
erse(num/10); / 递归调用 / }}例10-4 汉诺(Hanoi)塔问题 A
B Chanio(n个盘,A→B,C为过渡) {
if (n == 1) 直接把盘子A→B else{ hanio(n-1个盘,A→C,B为过渡)
把n号盘 A→B hanio(n-1个盘,C→B,A为过渡) }} 源程序 / 搬动n个盘,从a到b,c
为中间过渡 /void hanio(int n, char a, char b, char c){ if (n == 1)
printf("%c-->%c\n", a, b); else{ hanio(n-1, a, c,
b); printf("%c-->%c\n", a, b); hanio(n-1, c, b, a);
}}int main(void){ int n; printf("input the number of disk
: " ); scanf("%d", &n); printf("the steps for %d disk are
:\n",n); hanio(n, ''a'', ‘b'', ‘c'') ; return 0;}input the n
umber of disk: 3the steps for 3 disk are:a-->ba-->cb-->ca-->bc-->
ac-->ba-->b A B Cinput the number of di
sk: 3the steps for 3 disk are:a-->ba-->cb-->ca-->bc-->ac-->ba-->b
课堂练习:利用递归函数计算x的n次幂int mi(int x, int n) { if (n==1) return x;
else return xmi(x,n-1); } 10.3 长度单位转换 10.3.1 程序解析 10.3.2 宏基
本定义 10.3.3 带参数的宏定义 10.3.4 文件包含 10.3.5 编译预处理 10.3.1 程序解析 例10-5
欧美国家长度使用英制单位,1英里=1609米,1英尺=30.48厘米,1英寸=2.54厘米。请编写程序转换。#include dio.h> #define Mile_to_meter 1609 / 1英里=1609米 /#define Foot_to
_centimeter 30.48 / 1英尺=30.48厘米 /#define Inch_to_centimeter
2.54 / 1英寸=2.54厘米 /int main(void) { float foot, inch,
mile; / 定义英里,英尺,英寸变量 / printf("Input mile,foot and inch:");
scanf("%f%f%f", &mile, &foot, &inch); printf("%f miles=%f
meters\n", mile, mile Mile_to_meter); / 计算英里的米数 / printf
("%f feet=%f centimeters\n", foot, foot Foot_to_centimeter)
; / 计算英尺的厘米数 / printf("%f inches=%f centimeters\n", inch, in
ch Inch_to_centimeter); / 计算英寸的厘米数 / return 0;}Input m
ile,foot and inch:1.2 3 5.11.200000 miles=1930.800077 meters3.000
000 feet=91.440000 centimeters5.100000 inches=12.954000 centimete
rs10.3.2 宏基本定义 #define 宏名标识符 宏定义字符串编译时,把程序中所有与宏名相同的字符串,用宏定义字符串替代
#define PI 3.14#define arr_size 4说明:宏名一般用大写字母,以与变量名区别宏定义不是C语句,后面
不得跟分号宏定义可以嵌套使用 #define PI 3.14 #define S 2PIPI多用于符号常量宏定义可以写在程序中
任何位置,它的作用范围从定义书写处到文件尾。可以通过“#undef”强制指定宏的结束范围。10.3.2 宏基本定义#define
A “This is the first macro”void f1(){ printf( “A\n” );}#de
fine B “This is the second macro” A 的有效范围void f2( ){ printf
( B ) ; B 的有效范围}#undef Bint main(void){ f1( ); f2
( ); return 0;}宏的作用范围10.3.3 带参数的宏定义例10-6 简单的带参数的宏定义。#include
#define MAX(a, b) a > b ? a: b#define SQR(x) x xi
nt main(void){ int x , y; scanf (“%d%d” , &x, &y) ; x = MAX (
x, y); / 引用宏定义 / y = SQR(x); / 引用宏定义 / printf(“%d %
d\n” , x, y) ; return 0;}10.3.3 带参数的宏定义例: #define f(a) aaa
int main(void) / 水仙花数 / { int i,x,y,z; for (i=1; i<10
00; i++) { x=i%10; y=i/10%10; z=i/100 ; if (xxx+y
yy+zzz==i) printf(“%d\n” ,i); } return 0; }#defin
e f(a) (a)(a)(a)各位数字的立方和等于它本身的数。例如153的各位数字的立方和是13+53+33=153=
x+yx+yx+y(f(x)+f(y)+f(z)==i)f(x+y) = (x+y)3 ? #define f(a,b,t)
t=a; a=b; b=t; int main( ) { int x,y,t ; scanf(“%d%d” ,&x
, &y); f(x,y,t) printf(“%d %d\n”, x, y
) ; return 0; }t=x ; x=y ; y=t ;编译时被替换带参数的宏定义不是函数,宏与函数是两种不同的概念
宏可以实现简单的函数功能示例 用宏实现两个变量值的交换与函数的区别在哪里?宏定义应用示例定义宏LOWCASE,判断字符c是否为小
写字母。 #define LOWCASE(c) (((c) >= ''a'') && ((c) <= ''z'') ) 定义宏CT
OD将数字字符(‘0’~‘9’)转换为相应的 十进制整数,-1表示出错。 #define CTOD(c) (((c) >= ''
0'') && ((c) <= ''9'') ? c - ''0'' : -1) #define F(x) x - 2#define D(
x) xF(x)int main(){ printf("%d,%d", D(3), D(D(3))) ; return 0
;}练习——带宏定义的程序输出阅读带宏定义的程序,先全部替换好,最后再统一计算不可一边替换一边计算,更不可以人为添加括号D(3)
= xF(x) 先用x替换展开 = xx-2 进一步对F(x)展开,这里不能加括号
= 33-2 = 7 最后把x=3代进去计算D(D(3)) = D(xx-2) 先对D(
3)用x替换展开, = xx-2 F(xx-2) 拿展开后的参数对D进一步进行宏替换 =
xx-2 xx-2-2 拿展开后的参数对F进一步进行宏替换 = 33-233-2-2 =
-13 最后把x=3代进去计算运行结果:7 -13结果分析10.3.4 文件包含系统文件以stdio.h、math.h等形式供编
程者调用实用系统往往有自己诸多的宏定义,也以.h的形式组织、调用问题:如何把若干.h头文件连接成一个完整的可执行程序?文件包含 i
nclude格式 # include <需包含的文件名> # include “需包含的文件名”作用把指定的文件模块内容插入到 #
include 所在的位置,当程序编译连接时,系统会把所有 #include 指定的文件拼接生成可执行代码。注意编译预处理命令,以
#开头。在程序编译时起作用,不是真正的C语句,行尾没有分号。文件包含系统文件夹当前文件夹+系统文件夹例10-7 将例10-5中长
度转换的宏,定义成头文件length.h,并写出主函数文件。头文件length.h源程序#define Mile_to_meter
1609 / 1英里=1609米 /#define Foot_to_centimeter 30.48 / 1英尺=30.
48厘米 /#define Inch_to_centimeter 2.54 / 1英寸=2.54厘米 /主函数文件pro
g.c源程序#include #include “length.h” / 包含自定义头文
件 / int main(void) { float foot, inch, mile; / 定义英里,英尺,英寸变量
/ printf("Input mile,foot and inch:"); scanf("%f%f%f", &m
ile, &foot, &inch); printf("%f miles=%f meters\n", mile, mile
Mile_to_meter); printf("%f feet=%f centimeters\n", foot, foo
t Foot_to_centimeter); printf("%f inches=%f centimeters\n",
inch, inch Inch_to_centimeter); return 0;}将例10-1的5个函数
分别存储在2个.C文件上,要求通过文件包含把它们联结起来。ctype.h 字符处理 math.h 与数学处理函数有关的说明与定
义stdio.h 输入输出函数中使用的有关说明和定义string.h 字符串函数的有关说明和定义 stddef.h 定义某些
常用内容 stdlib.h 杂项说明 time.h 支持系统时间函数 常用标准头文件编译预处理是C语言编译程序的组成部分,它用
于解释处理C语言源程序中的各种预处理指令。文件包含(#include)和宏定义(#define)都是编译预处理指令在形式上都以“#
”开头,不属于C语言中真正的语句增强了C语言的编程功能,改进C语言程序设计环境,提高编程效率10.3.5 编译预处理C程序的编译
处理,目的是把每一条C语句用若干条机器指令来实现,生成目标程序。由于#define等编译预处理指令不是C语句,不能被编译程序翻译,
需要在真正编译之前作一个预处理,解释完成编译预处理指令,从而把预处理指令转换成相应的C程序段,最终成为由纯粹C语句构成的程序,经编
译最后得到目标代码。编译预处理编译预处理的主要功能: 文件包含(#include) 宏定义(#define) 条件编译编译预处理功
能条件编译#define FLAG 1#if FLAG 程序段1#else 程序段2#endif编译预处理功能1
0.4 大程序构成 ——多文件模块的学生信息库系统 10.4.1 分模块设计学生信息库系统10.4.2 C程序文件模块
10.4.3 文件模块间的通信 10.4.1 分模块设计学生信息库系统 例10-8 请综合例9-1、例9-2、例9-3和例
9-4,分模块设计一个学生信息库系统。该系统包含学生基本信息的建立和输出、计算学生平均成绩、按照学生的平均成绩排序以及查询、修改学
生的成绩等功能。 函数建立为: 10.4.1 分模块设计学生信息库系统 由于整个程序规模较大,按照功能图,分成三个程序文件
模块,并把结构体定义等写成一个头文件。 头文件student.h 输入输出程序文件input_output.cvoid new_s
tudent (struct student students[ ])void output_student(struct stu
dent students[ ])计算平均成绩与平均成绩排序程序文件aver_sort.cvoid average(struct
student students[ ])void sort(struct student students[ ])查询修改程序文件
modify.cvoid modify(struct student students[ ])void search_studen
t(struct student students[ ], int num)10.4.1 分模块设计学生信息库系统 一共定义了三
个.c程序文件和一个.h头文件,它们各自独立,再通过主函数main()调用。主函数放在student_system.c文件中,各文
件存放在同一个文件夹下,相互间的连接采用文件包含的形式。 主函数程序文件student_system.c#include “stu
dent.h” #include “input _output.c”#include “aver_sort.c”#include
“modify.c”int Count = 0; / 全局变量,记录当前学生总数 /int main(void){
......... } / 主函数调用各函数 /10.4.2 C程序文件模块 结构化程序设计是编写出具有良
好结构程序的有效方法一个大程序最好由一组小函数构成如果程序规模很大,需要几个人合作完成的话,每个人所编写的程序会保存在自己的.c文
件中为了避免一个文件过长,也会把程序分别保存为几个文件。一个大程序会由几个文件组成,每一个文件又可能包含若干个函数。我们把保存有一
部分程序的文件称为程序文件模块 10.4.2 C程序文件模块 一个大程序可由几个程序文件模块组成,每一个程序文件模块又可能包含若
干个函数。程序文件模块只是函数书写的载体。 当大程序分成若干文件模块后,可以对各文件模块分别编译,然后通过连接,把编译好的文件模块
再合起来,连接生成可执行程序。问题:如何把若干程序文件模块连接成一个完整的可执行程序?文件包含 工程文件(由具体语言系统提供)10
.4.2 C程序文件模块程序-文件-函数关系小程序:主函数+若干函数 ? 一个文件大程序:若干程序文件模块(多个文件) ? 每个程序文件模块可包含若干个函数 ? 各程序文件模块分别编译,再连接整个程序只允许有一个main()函数10.4.3 文件模块间的通信 文件模块与变量外部变量 静态全局变量 文件模块与函数外部函数 静态的函数 10.4.3 文件模块间的通信 外部变量 全局变量只能在某个模块中定义一次,如果其他模块要使用该全局变量,需要通过外部变量的声明 外部变量声明格式为: extern 变量名表;如果在每一个文件模块中都定义一次全局变量,模块单独编译时不会发生错误,一旦把各模块连接在一起时,就会产生对同一个全局变量名多次定义的错误反之,不经声明而直接使用全局变量,程序编译时会出现“变量未定义”的错误。 10.4.3 文件模块间的通信 静态全局变量当一个大的程序由多人合作完成时,每个程序员可能都会定义一些自己使用的全局变量为避免自己定义的全局变量影响其他人编写的模块,即所谓的全局变量副作用,静态全局变量可以把变量的作用范围仅局限于当前的文件模块中即使其他文件模块使用外部变量声明,也不能使用该变量。 10.4.3 文件模块间的通信 文件模块与函数外部函数 如果要实现在一个模块中调用另一模块中的函数时,就需要对函数进行外部声明。声明格式为: extern 函数类型 函数名(参数表说明);静态的函数 把函数的使用范围限制在文件模块内,不使某程序员编写的自用函数影响其他程序员的程序,即使其他文件模块有同名的函数定义,相互间也没有任何关联,增加模块的独立性。 本章小结多函数程序的组织结构函数调用的层次结构多文件模块实现:文件包含合理运用变量在多文件模块、多函数间的关联程序文件模块:变量与文件模块、 函数与文件模块的关系递归函数构成要素:递归式子(重点)与递归出口运用递归函数解决特殊问题(如汉诺塔)编译预处理文件包含宏实质:编译预处理的替代带参的宏——不是函数
献花(0)
+1
(本文系小磊老师首藏)