• 沒有找到結果。

第 10 章 函数与程序结构

N/A
N/A
Protected

Academic year: 2021

Share "第 10 章 函数与程序结构"

Copied!
31
0
0

加載中.... (立即查看全文)

全文

(1)

第 10 章 函数与程序结构

10.1 教学要点

本章主要介绍函数的组织、函数的嵌套调用和递归函数的概念与编程,其中递归函数是 本章的重点。本章还介绍了宏——能起简单函数功能的作用,但并不是函数。通过本章的学 习,应帮助学生在函数运用上更上一层楼。

10.1 节通过 5 个简单函数构成的一个示例程序——设计一个常用圆形体体积计算器,引 出了多函数组成程序的方式。教师在讲授时,先介绍和分析该示例程序的总体框架结构,函 数调用的三层结构,并要打消学生对长程序的恐惧心理,让学生理解多函数结构可以有效降 低程序复杂度。教师在教学中要抓住复杂问题需要多个函数解决这条主线。

10.2 节主要介绍递归函数。在介绍本节内容时,应抓住递归函数的两个要点:递归式子 和递归出口。学生对递归函数的掌握有一定难度,主要问题在递归式子上。像求类似阶乘的 例子,递归式子很明显,学生容易理解。但像汉诺塔、整数逆序输出等例子,如何归纳出递 归式子是教学重点。要培养学生递归思维,为后续其它编程方面课程打下基础。

10.3 节介绍编译预处理概念,包括文件包含、宏和条件编译的内容。宏是本节重点:宏 的概念、定义和使用。要结合示例向学生讲清楚宏在编译预处理时起作用,其实质是替换,

并不能像函数那样进行计算。宏能起到简单函数的作用,但它不是函数。像带参数的宏,学 生容易与函数混淆。

10.4 节通过

多文件模块的学生信息库系统的例子,向学生展示大程序构成

,为

学生以后编写规模较大程序建立初步概念。本节主要通过文件包含把多个文件模块连接起来,

上机实验时,对照实验指导书掌握 VC++工程文件的实现方式。外部变量、静态全局变量、

外部函数、静态函数等为多文件模块间的通信提供了手段。

讲授学时:4 学时,实验学时同讲授学时。

本章的知识能力结构图见图10.1。

图 10.1 知识能力结构图 多 个 函 数 构

成 的 程 序 结

递归函数

编译预处理

递归出口 递归式子 递归概念

递归函数定义

宏基本定义

掌握编译预处理概念 带参数的宏

能 够 对 相 对 复 杂 的 问题,合理定义程序 的多函数结构。

能 够 使 用 递 归 函 数 进行编程

掌 握 宏 的 基 本用法。

函数嵌套调用 文件包含 全局变量与外 部变量的使用

(2)

10.2 讲稿

1

Chap 10 函数与程序结构

10.1 圆形体积计算器 10.2 汉诺塔问题 10.3 长度单位转换 10.4 大程序构成

中心:函数与程序结构关系 本章分4 节:

1、﹅ 单文件模块内的函数嵌套调用 2、﹅ 递归函数

3、﹅ 宏为主要内容的编译预处理

4、﹅ 通过文件包含来实现稍大规模的多文 件模块程序

2

本章要点

n怎样把多个函数组织起来?

n怎样用结构化程序设计的思想解决问题?

n怎样用函数嵌套求解复杂的问题?

n怎样用函数递归解决问题?

n如何使用宏?

n如何使用多文件模块构建较大规模程序

提出本章的学习要点。

简单函数à多函数构建à递归函数à

函数组织——程序文件模块 编译预处理

3

使用结构化程序设计方法解决复杂的问题

¨把大问题分解成若干小问题,小问题再进一步分解成 若干更小的问题

¨写程序时,用main()解决整个问题,它调用解决小问 题的函数

¨这些函数又进一步调用解决更小问题的函数,从而形 成函数的嵌套调用

10.1 圆形体积计算器

对于复杂问题——本节的讨论核心,以结 构化的方法进行分解是一个有效解决办 法,虽然学生现在编写的都是小程序,但 是结构化思想一定要让学生建立起来。

4

main( )

函数1 函数2

函数m

函数 1_1

函数 1_2

函数

m_1 函数

m_n

程序结构

层次(树形)结构描述结构化思想。

(3)

5

例10-1 设计一个常用圆形体体积计算器,采用 命令方式输入1、2、3,分别选择计算球体、

圆柱体、圆锥体的体积,并输入计算所需相 应参数。

分析:

¨输入1、2、3选择计算3种体积,其他输入结束 计算

¨设计一个控制函数cal(),经它辨别圆形体的类型 再调用计算球体、圆柱体、圆锥体体积的函数

¨设计单独的函数计算不同圆形体的体积

10.1.1 程序解析-计算常用圆形体体积

提醒学生不要匆忙考虑具体程序实现,首 先应进行整体分析:功能分解与确定。

6

3层结构,5个函数

降低程序的构思、编写、调试的复杂度 可读性好

程序结构

main( ) cal ( )

vol_ball ( ) vol_cylind ( ) vol_cone ( )

直观考虑:

球体、圆柱体、圆锥体体积的计算分 别定义3 个函数,再定义主函数

主函数功能确定:

若把根据输入选择确定3 个体积函数 放在主函数中,会造成主函数规模偏大,

因此通过定义 cal 函数,使主函数功能简 洁明了。

7

例10-1源程序

#define PI 3.141592654 void cal ( 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;

}

while(1)为永真循环,循环结束靠循环体中 的break 语句实现。除去 5 个 printf 语句,

主函数非常简洁。

讨论:如果用while(条件),则条件是什 么?循环体需要修改吗?

8 /* 常用圆形体体积计算器的主控函数 */

void cal ( int sel ) { double vol_ball(void );

double vol_cylind(void );

double vol_cone(void );

switch (sel) {

case 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/3*PI*r*r*r */

double vol_ball( ) { double r ;

printf("请输入球的半径:");

scanf("%lf",&r);

return(4.0/3.0*PI*r*r*r);

}

/* 计算圆柱体积 V=PI*r*r*h */

double vol_cylind( ) { double r , h ;

printf("请输入圆柱的底圆半径和高:");

scanf("%lf%lf",&r,&h);

return(PI*r*r*h);

}

/* 计算圆锥体积 V=h/3*PI*r*r */

double vol_cone( ) { double r , h ;

printf("请输入圆锥的底圆半径和高:");

scanf("%lf%lf",&r,&h);

return(PI*r*r*h/3.0);

}

注意3 个体积计算的函数声明。函数调用

是以printf 的实参形式出现的。

问题:3 个体积计算的函数声明可以放到 别的地方吗?

(4)

9

10.1.2 函数的嵌套调用

n顺序调用

int main(void) { ……

y =fact(3);

……

z = mypow(3.5, 2);

……

}

double fact(int n) {

……

}

double mypow(double x, in n) {

……

}

main

fac t mypow

main fac t mypow

先讨论函数的顺序调用

10 n嵌套调用 函数的嵌套调用

int main(void) { ……

cal (sel);

……

}

void cal (int sel) { ……

vol_ball()

……

}

double vol_ball( ) {

……

}

main

c al

vol_ball main

c al vol_ball

对比顺序调用,嵌套调用的概念就比较清 楚。C 语言允许多层的嵌套调用。

11 int main(void) 例10-1 分析

{ ……cal (sel);

}void cal (int sel) { ……vol_ball();

vol_cylind();

vol_cone();

}double vol_ball( ) { ……

}double vol_cylind( ) { ……

}double vol_cone( ) { ……

}

main( ) cal ( )

vol_ball ( ) vol_cylind ( ) vol_cone ( )

例10-1 属于嵌套调用的结构。

12

函数的嵌套调用

在一个函数中再调用其它函数的情况称为函 数的嵌套调用。

如果函数A调用函数B,函数B再调用函数 C,一个调用一个地嵌套下去,构成了函数 的嵌套调用。

具有嵌套调用函数的程序,需要分别定义多 个不同的函数体,每个函数体完成不同的功 能,它们合起来解决复杂的问题。

结构化的程序设计方法一般都存在函数 的嵌套调用结构,用以解决复杂的问题。

(5)

13

结构化程序设计方法

n自顶向下,逐步求精,函数实现

¨自顶向下:程序设计时,应先考虑总体步骤,后考虑步 骤的细节;先考虑全局目标,后考虑局部目标。先从最 上层总目标开始设计,逐步使问题具体化。不要一开始 就追求众多的细节。

¨逐步求精:对于复杂的问题,其中大的操作步骤应该再 将其分解为一些子步骤的序列,逐步明晰实现过程。

¨函数实现:通过逐步求精,把程序要解决的全局目标分 解为局部目标,再进一步分解为具体的小目标,把最终 的小目标用函数来实现。问题的逐步分解关系,构成了 函数间的调用关系。

多个函数有效组织的目的之一——结构 化程序设计方法实现:

l 自顶向下 l 逐步求精 l 函数实现

虽然第五章介绍过结构化程序设计方法 的思想,现在重提,让学生有进一步的认 识

14

n 限制函数的长度。一个函数语句数不宜过多,既便于阅 读、理解,也方便程序调试。若函数太长,可以考虑把 函数进一步分解实现。

n 避免函数功能间的重复。对于在多处使用的同一个计算 或操作过程,应当将其封装成一个独立的函数,以达到 一处定义、多处使用的目的,以避免功能模块间的重复。

n 减少全局变量的使用。应采用定义局部变量作为函数的 临时工作单元,使用参数和返回值作为函数与外部进行 数据交换的方式。只有当确实需要多个函数共享的数据 时,才定义其为全局变量。

函数设计时应注意的问题

提出函 数 设 计 时 应 注 意 的 问 题

使学生在今后的函数编写中能遵循这些 原则

15

10.2 汉诺塔问题

10.2.1 程序解析

10.2.2 递归函数基本概念 10.2.3 递归程序设计

本节介绍递归函数,这是函数中一块不易 掌握的内容。先从有趣的例子入手,再讲 递归函数的基本定义与基本使用

16 汉诺塔是递归函数的经典例子,它很难用

非递归实现。由此也可以向学生说明递归 方法的重要性。

递归方法的优越性:

1、﹅ 能实现非递归很难实现的问题 2、﹅ 程序简洁

难点:

递归思想不容易建立。

10.2.1 Hanoi)

64 A B

A B C

A B C

(6)

17

分析

A B C

请学生说明搬动3 个盘子的过程,教师可

以ppt 非播放状态下移动图形。

18

分析

A B C

A B C

n

n-1

演示n 个盘子的实现过程,强调如果能把

n-1 个盘子搬开,就能解决 n 个盘子的问 题——这是递归的根本(递归式子)。

19

分析

A B C

A B C

n

演示

20

10.2.1 汉诺(Hanoi)塔问题解析

n递归方法的两个要点

¨(1)递归出口:一个盘子的解决方法;

¨(2)递归式子:如何把搬动64个盘子的问题简 化成搬动63个盘子的问题。

n把汉诺塔的递归解法归纳成三个步骤:

¨n-1个盘子从座A搬到座C

¨n号盘子从座A搬到座B

¨n-1个盘子从座C搬到座B

从汉诺塔例子归纳出递归方法的 2 个要

点:递归出口与递归式子

后面的例子都从这两点着手分析

(7)

21

算法: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为过渡) }

}

A B C

n-1

以算法形式给出汉诺塔的解法,这里注重 分析,不急于写出程序

22

10.2.2递归函数基本概念

n10-2 用递归函数实现求n!

¨递推法

n在学习循环时,计算n!采用的就是递推法:

n!= 1×2×3×…×n

n用循环语句实现:

result = 1;

for(i = 1; i <= n; i++) result = result * i;

¨递归法

nn!= n ×(n-1)! 当n>1 递归式子

= 1 当n=1或n=0 递归出口

n即求n!可以在(n-1)!的基础上再乘上n。如果把求n!写成函

数fact(n),则fact (n)的实现依赖于fact(n-1)。

相同问题采用不同的表示方式:递推法与 递归法。它将导致不同程序方法的实现:

n 第 4 章通过循环实现递推法 n 例 10-2 用递归法实现

23

10.2.2递归函数基本概念

例10-2 用递归函数求n!。

#include <stdio.h>

double fact(int n);

int main(void) { int n;

scanf ("%d", &n);

printf ("%f", fact (n) );

return 0;

}

double fact(int n) /* 函数定义 */

{ double result;

if (n==1 || n == 0) /* 递归出口 */

result = 1;

else

result = n * fact(n-1);

return result;

}

阶乘函数实现已经学习过,引导学生找出 递归函数实现与以前实现的不同:

n 自己调用自己

n 有 n=0||n=1 的特殊情况考虑

24

10.2.2 递归函数基本概念

递归调用的2 种方式:

n 直接调用自己 n 间接调用自己

(8)

25 递归函数的2 要素:

n 递归出口

n 递归式子(表示)

fact(n)=n*fact(n-1)不符合 C 语言的语法 规则,赋值号=左面只能是变量,表示保 存单元。而fact(n)是函数形式,表示函数 计算,最终是数值。

26

main() fact(3) fact(2) fact(1) { .... { .... { .... { ....

printf(fact(3)) f=3*fact(2) f=2*fact(1) f=1 } return(f) return(f) return(f)

} } }

递归函数fact( n )的实现过程

fact(3)= 3*fact(2)=

2*fact(1)=

fact(1)=1 2*1=2 3*2=6 同时有4个函数在运行,且都未完成

动态演示递归函数的调用与返回过程,从 而了解递归函数的计算过程。

递归函数的运算远比递推法的循环来的 复杂,像例子中,main 调用 fact(3),最

后有4 个函数同时打开,然后再一层层返

回,关闭函数,最终回到main。

27

10.2.3 递归程序设计

用递归实现的问题,满足两个条件:

n问题可以逐步简化成自身较简单的形式(递归式)

n! = n * (n-1)!

n n-1

Σi = n +Σ i

i=1 i=1

n递归最终能结束(递归出口)

两个条件缺一不可

解决递归问题的两个着眼点

小结递归函数的2 要素:递归出口和递归

式子。

举几个例子讨论递归出口和递归式子分 别是什么。

n! = n * (n-1)!

Σi = n +Σ i 等等。

28

10.2.3 递归程序设计

n例10-3 编写递归函数reverse(int n)实现将 整数n逆序输出。

分析:

¨将整数n逆序输出可以用循环实现,且循环次数 与n的位数有关。递归实现整数逆序输出也需要 用位数作为控制点。归纳递归实现的两个关键 点如下:

n递归出口:直接输出n,如果n<=9,即n为1位数

n递归式子:输出个位数n%10,再递归调用 reverse(n/10) 输出前n-1位,如果n为多位数

强调:问 题 可 以 逐 步 简 化 成 自 身 较 简 单 的 形 式

重点分析:自 身 较 简 单 的 形 式 着眼点:数据的位 数

10-2

n!

n! = n * (n-1)! n > 1)

n! = 1 n =

0,1)

#include <stdio.h>

double fact(int n) int main(void) { int n;

scanf ("%d", &n);

printf ("%f", fact (n) );

return 0;

}

double fact(int n) { double result;

if (n==1 || n == 0) result = 1;

else

result = n * fact(n-1);

return result;

}

fact(n)=n*fact(n-1);

(9)

29 展现程序简洁的形式

可以与以前循环实现进行对比

30

例10-4 汉诺(Hanoi)塔问题

A B C

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为过渡) } }

回到汉诺塔例子,进行程序实现 先回顾算法

31 /* 搬动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 number of disk: 3

the steps for 3 disk are:

a-->b a-->c b-->c a-->b c-->a c-->b a-->b

3 个盘子搬动的程序结果。

32

A B C

input the number of disk: 3

the steps for 3 disk are:

a-->b a-->c b-->c a-->b c-->a c-->b a-->b

针对结果,教师可以 ppt 非播放状态下移 动图形来说明结果。

10.2.3

! 

void

void reverse(int num) {

if (num<=9)

printf ("%d",num); /* / else {

printf ("%d",num%10);

reverse (num/10); /* / }

}

(10)

33

课堂练习:利用递归函数计算x的n次幂

int mi(int x, int n) {

if (n==1) return x;

else

return x*mi(x,n-1);

}

课堂练习

还是从递归的2 个要点检查学生掌握的情

34

10.3 长度单位转换

n10.3.1 程序解析

n10.3.2 宏基本定义

n10.3.3 带参数的宏定义

n10.3.4 文件包含

n10.3.5 编译预处理

宏是本节主要内容,文件包含再系统展现 一下,编译预处理了解一下即可

35

10.3.1 程序解析

n 例10-5 欧美国家长度使用英制单位,1英里=1609米,1英 尺=30.48厘米,1英寸=2.54厘米。请编写程序转换。

#include<stdio.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 inche s=%f centimeters\n", inch, inch * Inch_to_centimeter); /* 计算英寸的厘米数 */

return 0;

}

Input mile,foot and inch:1.2 3 5.1 1.200000 miles=1930.800077 meters 3.000000 feet=91.440000 centimeters 5.100000 inches=12.954000 centimeters

本例中虽然宏名定义得较长,但可读性非 常清晰

36

10.3 宏定义

#define 宏名标识符 宏定义字符串

编译时,把程序中所有与宏名相同的字符串,用宏定义字 符串替代

#define PI 3.14

#define arr_size 4 说明:

¨宏名一般用大写字母,以与变量名区别

¨宏定义不是C语句,后面不得跟分号

¨宏定义可以嵌套使用

#define PI 3.14

#define S 2*PI*PI 多用于符号常量

在符号常量使用的基础上,引入宏概念。

宏的定义、使用方法 宏定义不是C 语句

(11)

37

n宏定义可以写在程序中任何位置,它的作用范 围从定义书写处到文件尾。

n可以通过“#undef”强制指定宏的结束范围。

10.3.1 宏基本定义

与变量相同,宏定义也有作用范围

38

#define A “This is the first macro”

void f1() {

printf( “A\n” );

}

#define B “This is the second macro” A 的有效范围 void f2( )

{

printf( B ) ; B 的有效范围

}

#undef B int main(void) {

f1( );

f2( );

return 0;

}

宏的作用范围

通过例子演示宏定义的作用范围。本例子 仅作说明之用,例中A 与 B 的用法不具有 实用性。

39

带参数的宏定义实现简单的函数功能

例10-7 简单的带参数的宏定义。

#include <stdio.h>

#define MAX(a, b) (a) > (b) ? (a): (b)

#define SQR(x) (x) * (x) int 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;

}

本例说明宏能解决简单函数功能,但参数 引用上必须带括号。

40 通过例子说明带参数的宏定义的使用方

法。

带 参 数 的 宏 定 义 不 是 函 数 , 不 能 解 决 f(x+y)的问题——除非加上括号。其根本 原因是宏的替换作用,这点务必要让学生 理解。做习题时必须先替换,再计算。

10.3.3

define f(a) a*a*a int main (void) /* / { int i, x, y, z;

for (i = 1; i <1 000; i++) { x=i%10; y=i/10%10; z=i/100 ; if (x*x*x+y*y*y+z*z*z==i)

printf (“%d\n” ,i);

} return 0;

}

define f(a) (a)*(a)*(a)

x+y*x+y*x+y (f(x)+f(y)+f(z) == i)

f(x+y) = x+y)3 ?

(12)

41

#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  ; 编译时被替换

•带参数的宏定义不是函数,宏与函数是两种不同的概念

•宏可以实现简单的函数功能

示例 用宏实现两个变量值的交换

与函数的区别在哪里?

通过本例和学生讨论宏与函数的异同点:

宏能解决简单函数功能,但计算机运行本 质截然不同,宏是编译预处理时进行替 换,不存在类似函数的调用过程。

42

宏定义应用示例

n定义宏LOWCASE,判断字符c是否为小写字母。

#define LOWCASE(c) (((c) >= 'a') && ((c) <= 'z') ) n定义宏CTOD将数字字符(‘0’~‘9’)转换为相应的

十进制整数,-1表示出错。

#define CTOD(c) (((c) >= '0') && ((c) <= '9') ? c - '0' : -1)

宏有广泛应用方式——简单函数功能。

43

#define F(x) x -­‐ 2

#define D(x) x*F(x) int main()

{

printf("%d,%d", D(3), D(D(3))) ; return 0;

}

带宏定义的程序输出

本例学生较容易出错,可以先让学生计 算,然后通过计算机运行,教师再解释原 因。

44

n 阅读带宏定义的程序,先全部替换好,最后再统一计算 n 不可一边替换一边计算,更不可以人为添加括号

D(3) = x*F(x) 先用x替换展开

= x*x-2 进一步对F(x)展开,这里不能加括号

= 3*3-2 = 7 最后把x=3代进去计算

D(D(3)) = D(x*x-2) 先对D(3)用x替换展开,

= x*x-2* F(x*x-2) 拿展开后的参数对D进一步进行宏替换

= x*x-2* x*x-2-2 拿展开后的参数对F进一步进行宏替换

= 3*3-2*3*3-2-2 = -13 最后把x=3代进去计算 运行结果:7 -13

结果分析

分析出错原因:

其根本原因是宏的替换作用,而不是 函数计算,这点务必要让学生理解。做习 题时必须先 替 换 ,再计算。

(13)

45

10.3.4 文件包含

n系统文件以stdio.h、math.h等形式供编程 者调用

n实用系统往往有自己诸多的宏定义,也以.h 的形式组织、调用

n问题:如何把若干.h头文件连接成一个完整 的可执行程序?

¨文件包含include

文件包含是解决多个文件程序连接的有 效方法。

46

n格式

¨# include <需包含的文件名>

¨# include “需包含的文件名”

n作用

把指定的文件模块内容插入到 #include 所在的 位 置 , 当 程 序 编 译 连 接 时 , 系 统 会 把 所 有

#include 指定的文件拼接生成可执行代码。

n注意

¨编译预处理命令,以#开头。

¨在程序编译时起作用,不是真正的C语句,行尾 没有分号。

文件包含

系统文件夹 当前文件夹+系统文件夹

区分include 的两种写法,一般:

l <…> 适用于系统头文件 1) “…” 适用于多文件模块连接

47

例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厘米 */

主函数文件prog.c源程序

#include<stdio.h>

#include “length.h” /* 包含自定义头文件 */

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, inch * Inch_to_centimeter);

return 0;

}

本例把3 个宏定义单独形成一个.h 文件,

以作自定义头文件的示例

48

将例10-1的5个函数分别存储在2个.C文件上,要 求通过文件包含把它们联结起来。

头文件l engt h. h

#def i ne Mil e_t o_met er 1609

#def i ne Foot _t o_cent i met er 30. 48

#def i ne I nch_t o_cent i met er 2. 54

主函数文件pr og. c

#i ncl ude<s t di o. h>

#i ncl ude “l engt h. h”

i nt mai n( voi d) {

f l oat mil e, f oot , i nch;

……

r et ur n 0;

}

编译连接后生成的程序

… st di o. h的内容

#define Mile_to_meter 1609

#define Foot_to_centimeter 30.48

#define Inch_to_ centimeter 2.54 int main(void)

{

float mile,foot,inch;

……

return 0;

}

文件包含的作用:

在编译时,把需包含的文件插入到include 位置,插入后的程序规模将扩大。

可进一步引申到系统头文件,例如每个程 序都会用到的include <stdio.h>,其作用将 把系统头文件stdio.h 的所有程序插入到我 们编写的程序前,来确保pringf 等功能的 实现。

(14)

49

nctype.h 字符处理

nmath.h 与数学处理函数有关的说明与定义

nstdio.h 输入输出函数中使用的有关说明和定义

nstring.h 字符串函数的有关说明和定义

nstddef.h 定义某些常用内容

nstdlib.h 杂项说明

ntime.h 支持系统时间函数

常用标准头文件

可以结合教材后面附录说明。

50

n编译预处理是C语言编译程序的组成部 分,它用于解释处理C语言源程序中的各 种预处理指令。

n文件包含(#include)和宏定义(#define)都 是编译预处理指令

¨在形式上都以“#”开头,不属于C语言中真正 的语句

¨增强了C语言的编程功能,改进C语言程序设 计环境,提高编程效率

10.3.5 编译预处理

结合宏的运行,再强调编译预处理概念与 使用方式。

51

nC程序的编译处理,目的是把每一条C语句 用若干条机器指令来实现,生成目标程序。

n由于#define等编译预处理指令不是C语句,

不能被编译程序翻译,需要在真正编译之前 作一个预处理,解释完成编译预处理指令,

从而把预处理指令转换成相应的C程序段,

最终成为由纯粹C语句构成的程序,经编译 最后得到目标代码。

编译预处理

解释什么是编译,它与编译预处理的关 系。

52

n编译预处理的主要功能:

¨文件包含(#include)

¨宏定义(#define)

¨条件编译

编译预处理功能

(15)

53

n条件编译

#define FLAG 1

#if FLAG 程序段1

#else 程序段2

#endif

编译预处理功能

该内容了解即可。

54

10.4 大程序构成

——多文件模块的学生信息库系统

n10.4.1 分模块设计学生信息库系统

n10.4.2 C程序文件模块

n10.4.3 文件模块间的通信

本节将展示给学生处理规模稍大程序(多 函数)的方法,由于教材所限,示例程序 规模仍不够大,但思想需要掌握。

也希望学生最后能完整实现学生信息库 系统。

55

10.4.1 分模块设计学生信息库系统

学生信息库 系统 main()

计算平均成 average()

平均成绩排 sort()

修改 modify() 建立

new_student() 输出

output_student() 查询

search_student()

例10-8 请综合例9-1、例9-2、例9-3和例9-4,分模 块设计一个学生信息库系统。该系统包含学生基本 信息的建立和输出、计算学生平均成绩、按照学生 的平均成绩排序以及查询、修改学生的成绩等功能。

函数建立为:

本例来源于第九章多个例题,由于题目本 身已比较熟悉,学生可以把注意力放在集 成实现上。

56

10.4.1 分模块设计学生信息库系统

n由于整个程序规模较大,按照功能图,分成三个程 序文件模块,并把结构体定义也写成一个头文件。

¨头文件student.h

¨输入输出程序文件input_output.c

nvoid new_student (struct student students[ ]) nvoid output_student(struct student students[ ])

¨计算平均成绩与平均成绩排序程序文件aver_sort.c

nvoid average(struct student students[ ]) nvoid sort(struct student students[ ])

¨查询修改程序文件modify.c

nvoid modify(struct student students[ ])

nvoid search_student(struct student students[ ], int num)

程 序 分 成 三 个 程 序 文 件 模 块+一个头文 件 ( 含 结 构 体 定 义 )

(16)

57

10.4.1 分模块设计学生信息库系统

n一共定义了三个.c程序文件和一个.h头文件,它们 各自独立,再通过主函数main()调用。主函数放在 student_system.c文件中,各文件存放在同一个 文件夹下,相互间的连接采用文件包含的形式。

¨主函数程序文件student_system.c

#include “student.h”

#include “input _output.c”

#include “aver_sort.c”

#include “modify.c”

int Count = 0; /* 全局变量,记录当前学生总数 */

int main(void)

{ ... } /* 主函数调用各函数 */

主函数构成

58

10.4.2 C程序文件模块

n结构化程序设计是编写出具有良好结构程序的有 效方法

n一个大程序最好由一组小函数构成

n如果程序规模很大,需要几个人合作完成的话,

每个人所编写的程序会保存在自己的.c文件中

n为了避免一个文件过长,也会把程序分别保存为 几个文件。

n一个大程序会由几个文件组成,每一个文件又可 能包含若干个函数。

我们把保存有一部分程序的文件称 为程序文件模块

从结构化程序设计思想出发,解释例10-8 设计的依据,并引出程序文件模块的概念

59

10.4.2 C程序文件模块

n一个大程序可由几个程序文件模块组成,每一个程 序文件模块又可能包含若干个函数。程序文件模块 只是函数书写的载体。

n当大程序分成若干文件模块后,可以对各文件模块 分别编译,然后通过连接,把编译好的文件模块再 合起来,连接生成可执行程序。

n问题:如何把若干程序文件模块连接成一个完整的 可执行程序?

¨文件包含

¨工程文件(由具体语言系统提供)

解释把一个程序用几个文件模块实现的 原因——对复杂程序:

1、﹅ 便于分模块编译、连接与调试,降低 上机调试复杂度

2、﹅ 可实现多人合作开发程序——这样对 程序分头编译查错,十分方便

多文件模块通过文件包含连接,注意各模 块中不能有各自的#include stdio.h 等内 容,统一在main()文件中

60

10.4.2 C程序文件模块

n程序-文件-函数关系

¨小程序:主函数+若干函数 à一个文件

¨大程序:若干程序文件模块(多个文件)à 每 个程序文件模块可包含若干个函数 à 各程序 文件模块分别编译,再连接

¨整个程序只允许有一个main()函数

帮学生理清关系:

程 序 - 文 件 - 函 数 关 系

(17)

61

10.4.3 文件模块间的通信

n文件模块与变量

¨外部变量

¨静态全局变量 n文件模块与函数

¨外部函数

¨静态的函数

局部变量、静态局部变量均与具体函数相 关,与文件模块无关。

这里介绍函数模块与变量、函数间的关系

62

10.4.3 文件模块间的通信

n外部变量

¨全局变量只能在某个模块中定义一次,如果其他模 块要使用该全局变量,需要通过外部变量的声明

外部变量声明格式为:

extern 变量名表;

¨如果在每一个文件模块中都定义一次全局变量,模 块单独编译时不会发生错误,一旦把各模块连接在 一起时,就会产生对同一个全局变量名多次定义的 错误

¨反之,不经声明而直接使用全局变量,程序编译时 会出现“变量未定义”的错误。

在某一文件模块中定义的全局变量,想在 其他模块中使用,相关文件模块必须通过 外部变量声明后,方可使用。变量实体只 有一个,在定义处,外部变量声明只是告 诉编译系统,并没有分配单元。

63

10.4.3 文件模块间的通信

n静态全局变量

¨当一个大的程序由多人合作完成时,每个程 序员可能都会定义一些自己使用的全局变量

¨为避免自己定义的全局变量影响其他人编写 的模块,即所谓的全局变量副作用,静态全 局变量可以把变量的作用范围仅局限于当前 的文件模块中

¨即使其他文件模块使用外部变量声明,也不 能使用该变量。

静 态 全局变量可以使全局变量只限于本 文件模块中使用,而不能被其他文件引用

,即使其它文件模块声明了外部变量也无 法使用。

静 态 全局变量的作用是避免多文件模块 间的变量干扰——副作用。

64

10.4.3 文件模块间的通信

n文件模块与函数

¨外部函数

n如果要实现在一个模块中调用另一模块中的函数 时,就需要对函数进行外部声明。声明格式为:

extern 函数类型 函数名(参数表说明);

¨静态的函数

n把函数的使用范围限制在文件模块内,不使某程 序员编写的自用函数影响其他程序员的程序,即 使其他文件模块有同名的函数定义,相互间也没 有任何关联,

n增加模块的独立性。

类似外部变量的概念,多文件模块上的函 数相互间的调用,采用外部函数声 明 。声 明仅对编译起作用,说明要调用的函数在 外部文件模块,本地未定义。

类似静态全局变量的概念,静态函数可以 阻断多文件模块上的函数相互间的调用,

避免多文件模块间的函数干扰——副作 用。

(18)

65 回顾和总结本章的教学要点,对学生提出 能力要求:

• 能够对相对复杂的问题,合理定义程序 的多函数结构,能合理运用外部变量、

静态全局变量和静态函数。

• 建立递归思想,具备递归函数的编程能 力,并解决一些特殊问题。

• 掌握宏的基本用法,特别是带参数的宏。

10.3 练习与习题参考答案

10.3.1 练习参考答案

【练习 10-1】使用递归函数计算 1 到 n 之和:若要用递归函数计算 sum=1+2+3+...+n(n 为 正整数),请写出该递归函数的递归式子及递归出口。试编写相应程序。

解答:

#include <stdio.h>

int sum(int n);

int main(void) {

int n;

int result;

scanf("%d",&n);

result=sum(n);

printf ("%d\n", result);

return 0;

}

int sum(int n) {

int result;

if (n == 0) result = 0;

else

result = sum(n-1)+n;

return result;

}

【练习 10-2】请完成以下宏定义:

! 

" 

" 

" 

" 

! 

" 

" 

! 

" 

" 

"  ——

(19)

1)MIN(a, b):求 a, b 的最小值。

解答:#define MIN(a, b) (a) < (b) ? (a): (b) 2)ISLOWER(c):判断 c 是否为小写字母。

解答:#define ISLOWER(c) (((c) >= 'a') && ((c) <= 'z') ) 3)ISLEAP(y):判断 y 是否为闰年。

解答:#define ISLEAP(y) ((y) % 4 == 0 && (y) % 100 != 0) || ((y) % 400 = = 0) 4)CIRFER(r):计算半径为 r 的圆周长。

解答:#define PI 3.14159

#define CIRFER(r) 2*PI*(r)

【练习 10-3】分别用函数和带参数的宏实现从 3 个数中找出最大数,请比较两者在形式上 和使用上的区别。

解答:

(1) 使用宏实现

#include<stdio.h>

#define f(x,y,z) x>(y>z?y:z)?x:(y>z?y:z) int main(void)

{

int a,b,c;

printf("input a, b, c: ");

scanf("%d%d%d",&a,&b,&c);

printf("max=%d\n",f(a,b,c));

return 0;

}

(2) 使用函数实现

#include <stdio.h>

int max(int a, int b, int c) {

return ((a>b?a:b)>c? (a>b?a:b):c);

}

int main(void) {

int a, b, c, s;

printf("input a, b, c: ");

scanf("%d%d%d", &a, &b, &c);

s = max(a,b,c);

printf("max = %d\n", max(a,b,c));

return 0;

}

(20)

10.3.2 习题参考答案

一 、 选 择 题

1 2 3 4 5 6 7

C A B A D A A

二 、 填 空 题

1、文件包含 宏定义 条件编译 2、 5

3、int t0,t1,t; n>1 t0+t1 t

4、int p=1 p=p*m int s=0 s=s+power(i,k) 5、g=4,g=3,k=6

6、7,-13

三 、 程 序 设 计 题

1.判断满足条件的三位数:编写一个函数,利用参数传入一个3位数n, 找出 101~n

间所有满足下列两个条件的数:它是完全平方数,又有两位数字相同,如144、676 等,函

数返回找出这样的数据的个数。试编写相应程序。

解答:

#include <stdio.h>

#include <math.h>

int fun(int n);

int main(void) {

int n;

printf("input n: ");

scanf("%d", &n);

printf("total=%d\n", fun(n));

return 0;

}

int fun(int n) {

int i, d=0;

for(i=101; i<=n; i++)

if(((int)sqrt(i) * (int)sqrt(i)) == i) {

if (i/100==(i/10)%10|| i/100==i%10||(i/10)%10==i%10) d++;

} return d;

}

2.递归求阶乘和:输入一个整数n(n>0 且n≤10), 求1!+2!+3!+…+n!。

定义并调用函数fact(n)计算n!,函数类型是 double。试编写相应程序。

(21)

解答:

double fact(int n);

int main(void) {

int i,n;

double sum;

scanf("%d",&n);

sum=0;

for (i=1; i<=n; i++) sum += fact (i);

printf ("%.0f\n", sum);

return 0;

}

double fact(int n) {

double result;

if (n==1 || n == 0) result = 1;

else

result = n * fact(n-1);

return result;

}

3.递归实现计算xn :输入实数x和正整数n,用递归函数计算 xn的值。试编写相应程序。

解答:

#include <stdio.h>

double fun(double x,int n);

int main(void) {

int n;

double x, root;

scanf("%lf%d", &x,&n);

root = fun(x, n);

printf("Root = %0.2f\n", root);

return 0;

}

double fun(double x, int n) {

if(n == 1) return x;

(22)

else if(n == 0) return 1;

else

return x * fun(x, n-1);

}

4.递归求式子和:输入实数x和正整数n,用递归的方法对下列计算式子编写一个函数。

2 3 4 1

( , ) ... ( 1)

n n

f x n = − x x + xx + + −

× x

(n>0)

试编写相应程序。

解答:

#include <stdio.h>

#include <math.h>

double f(double x, int n) {

if(n==1) return x;

return pow(-1,n-1)*pow(x,n)+f(x, n-1);

}

int main(void) {

int n;

double x;

scanf("%lf%d", &x, &n);

printf("%lf\n", f(x,n));

return 0;

}

5.递归计算函数Ack(m,n):输入两个整数 m 和 n(m≥0 且 n≥0),输出函数 Ack(m,n)的值。

Ack(m,n)定义为(m≥0 且 n≥0):

n + 1 m=0

Ack(m, n)= Ack(m - 1, 1) n=0 && m>0 Ack(m - 1, Ack(m, n - 1) ) m>0 && n>0 试编写相应程序。

解答:

#include <stdio.h>

int Ack(int m, int n);

int main(void) {

int m,n;

int result;

(23)

scanf("%d%d", &m, &n);

result = Ack(m,n);

printf("Ackerman(%d,%d)=%d\n", m, n, result);

return 0;

}

int Ack(int m, int n) {

if (m==0) return n+1;

else if (n==0) return Ack(m-1, 1);

else return Ack(m-1, Ack(m, n-1));

}

6. 递归实现求Fabonacci 数列:用递归方法编写求斐波那契数列的函数,返回值为整型,

并写出相应的主函数。斐波那契数列的定义为:

f(0) = 0,f(1) = 1

f(n) = f(n - 2) + f(n - 1) (n>1)

解答:

#include <stdio.h>

int fib(int n);

int main(void) {

scanf("%d",&n);

printf("fib(%d)=%d\n",n,fib(n));

return 0;

}

int fib(int n) {

int res;

if(n==0) res=0;

else if(n==1) res=1;

else

res=fib(n-2)+fib(n-1);

return res;

}

7. 递归实现十进制转二进制:输入一个正整数 n,将其转换为二进制后输出。要求定义并调 用函数 dectobin(n),它的功能是输出 n 的二进制。试编写相应程序。

解答:

#include "stdio.h"

(24)

int main(void) {

int i,n;

void dectobin(int n);

scanf("%d",&n);

dectobin(n);

return 0;

}

void dectobin(int n) {

if(n<2)

printf("%d", n);

else{

dectobin(n/2);

printf("%d", n%2);

} }

8. 递归实现顺序输出整数:输入一个正整数n,编写递归函数实现对其进行按位顺序输

出。试编写相应程序。

解答:

#include <stdio.h>

void inorder(int n);

int main(void) {

int n;

scanf("%d",&n);

inorder(n);

return 0;

}

void inorder(int num) {

if(num < 10)

printf("%d\n",num);

else {

inorder(num/10);

printf("%d\n",num%10);

} }

(25)

9. 使用文件包含统计素数:输入 n(n<10)个整数,统计其中素数的个数。要求程序由两个文件

组成,一个文件中编写main 函数,另一个文件中编写素数判断的函数。使用文件包含的方

式实现。

解答:

文件prog.cpp

#include<stdio.h>

#include "prime.h"

int main(void) {

int i,n,count,x;

count=0;

scanf("%d",&n);

for(i=1;i<=n;i++) {

scanf("%d",&x);

if(IsPrime(x)) count++;

}

printf("count=%d\n",count);

return 0;

}

文件prime.h int IsPrime(int m) {

int i,n;

if(m==1) return 0;

n=m/2;

for(i=2;i<=n;i++) if(m%i==0)

return 0;

return 1;

}

10.三角形面积为  

area =   s×(s − a)×(s − b)×(s − c)      其中, s = (a + b + c)/2  

a、b 、c 分别为三角形的 3 条边。请分别定义计算 s 和 area 的宏。再使用函数实现,比较 两者在形式上和使用上的区别。

解答:

(1) 使用宏实现

#include <stdio.h>

#include <math.h>

#define S(a ,b, c) ((a)+(b)+(c))/2

(26)

#define AREA(s,a,b,c) sqrt((s)*((s)-(a))*((s)-(b))*((s)-(c))) int main(void)

{

double a, b, c, s;

printf("input a, b, c: ");

scanf("%lf%lf%lf", &a, &b, &c);

s = S(a,b,c);

printf("s = %lf, area = %lf\n", s, AREA(s,a,b,c));

return 0;

}

(2) 使用函数实现

#include <stdio.h>

#include <math.h>

double f1(double a, double b, double c) {

return (a+b+c)/2;

}

double f2(double s, double a, double b, double c) {

return sqrt(s*(s-a)*(s-b)*(s-c));

}

int main(void){

double a, b, c, s;

printf("input a, b, c: ");

scanf("%lf%lf%lf", &a, &b, &c);

s = f1(a,b,c);

printf("s = %lf, area = %lf\n", s, f2(s,a,b,c));

return 0;

}

10.4 实验指导教材参考答案

一 、 调 试 示 例 ( 略 ) 二 、 基 础 编 程 题

(1)判断满足条件的三位数:编写一个函数,利用参数传入一个 3 位数 number,找出 101~number 之间所有满足下列两个条件的数:它是完全平方数,又有两位数字相同,如 144、676 等,函数返回找出这样的数据的个数,并编写主函数。

输入输出示例(括号内为说明文字)

150 (number=150)

count=2

(27)

解答:参见《C 语言程序设计》习题 10 中的三、程序设计题,第 1 题。

(2)递归求阶乘和:输入一个整数 n(n>0 且 n≤10),求 1!+2!+3!+...+n!。定义并调用函 数fact(n)计算 n!,函数类型是 double。试编写相应程序。

解答:参见《C 语言程序设计》习题 10 中的三、程序设计题,第 2 题。

(3)递归实现计算 x^n:输入双精度浮点数 x 和整数 n (n≥1),调用函数求 x 的 n 次方,

并保留两位小数。

输入输出示例(括号内为说明文字)

2 (x=2)

3 (n=3)

Root = 8.00

解答:参见《C 语言程序设计》习题 10 中的三、程序设计题,第 3 题。

(4)递归求式子和:用递归的方法对下列计算式子编写一个函数,并写出相应主函数,

计算结果保留两位小数。

2 3 4 1

( , ) ... ( 1)

n n

f x n = − x x + xx + + −

× x

(n>0)

输入输出示例(括号内为说明文字)

2 (x=2)

4 (n=4)

f(2,4) = -10

解答:参见《C 语言程序设计》习题 10 中的三、程序设计题,第 4 题。

(5)递归计算函数 Ack(m,n):输入两个整数 m 和 n(m≥0 且 n≥0),输出函数 Ack(m,n) 的值。Ack(m,n)定义为(m≥0 且 n≥0):

n + 1 m=0

Ack(m, n)= Ack(m - 1, 1) n=0 && m>0 Ack(m - 1, Ack(m, n - 1) ) m>0 && n>0 输入输出示例(括号内为说明文字)

2 (m=2)

3 (n=3)

Ack(2, 3)=9

解答:参见《C 语言程序设计》习题 10 中的三、程序设计题,第 5 题。

(6)递归实现求 Fabonacci 数列:用递归方法编写求斐波那契数列的函数,返回值为整型,

并写出相应的主函数。斐波那契数列的定义为:

f(0) = 0,f(1) = 1

f(n) = f(n - 2) + f(n - 1) (n>1)

输入输出示例(括号内为说明文字)

6 (n=6)

(28)

fib(6)=8

解答:参见《C 语言程序设计》习题 10 中的三、程序设计题,第 6 题。

三 、 改 错 题

改正下列程序中的错误,输入一个整数n (n≥0)和一个双精度浮点数 x,输出函数 P(n,x) 的值(保留2 位小数)。(源程序 error10_2.cpp)

1 (n=0) P(n, x) = x (n=1) ((2*n-1)*P(n-1,x)-(n-1)*P(n-2,x))/n (n>1) 输入输出示例

Enter n, x: 10 1.7 P(10,1.70) = 3.05

源程序(有错误的程序)

1 #include <stdio.h>

2 int main(void)

3 {

4 int n;

5 double x, result;

6

7 printf(“Enter n, x: ”);

8 scanf("%d%lf", &n, &x);

9 result = p(n,x);

10 printf("P(%d,%.2lf) = %.2lf\n", n, x, result);

11

12 return 0;

13 } 14

15 double p(int n, double x) 16 {

17 p(n,x) = ((2 * n - 1) * p(n - 1,x) - (n - 1) * p(n - 2,x)) / n;

18

19 return p(n,x);

20 }

(1)初次编译后共有 2 个[Error],请填写出错信息并分析原因。

① `p' undeclared (first use this function) ,函数p 未声明 ② non-lvalue in assignment 赋值语句中左边缺少变量

(2)请填写修改后的正确语句。

改错汇总:

错误行号: 6 正确语句: double p(int n, double x);

错误行号: 16 正确语句: double result;

错误行号: 17 正确语句: result = ((2 * n - 1) * p(n - 1,x) - (n - 1) * p(n - 2,x)) / n;

错误行号: 19 正确语句: return result;

(3)改正上述编译错误后,再次编译无错误出现,运行程序。

运行输入测试数据为 10 1.7,运行结果为: 程序错误,无输出 ,是否正确: 否

參考文獻

相關文件

[r]

上述定理, 即 Dini, Lipschitz, Dirichlet 判别法, 给出函数能展开成 Fourier 级数的充分条件... 下面罗列几个例子,

微积分的创立是数学发展中的里程碑, 它的发展 和广泛应用开启了向近代数学过渡的新时期, 为研究 变量和函数提供了重要的方法和手段. 运动物体的瞬

下面我们将用 这一点将一元函数的 Taylor 展开推广到多元函数.. 我们希望借助此给出多元函数

笛卡儿企图通过坐标系给几何引进新方 法, 他的成就远远超出他的期望. 坐标系是数 学中的双刃剑, 使得几何的目的可以通过代 数达到, 反过来,

3.正弦函数y=Asin(ωx+φ)的图象之间的变换关系是学生最熟悉的一种伸缩变换.教 材从学生最熟悉的这一内容入手 ,分别比较了函数y=sin2x 和y=sinx 的图象之间的关

为此, 我们需要建立函 数的差商与函数的导数间的基本关系式, 这些关系式称为“微分学中值定理”...

[初等函数] 幂函数、指数函数、对数函数、三角函数、反三角函数通称为“ 基本初等函