• 沒有找到結果。

第 5 章 函数

N/A
N/A
Protected

Academic year: 2021

Share "第 5 章 函数"

Copied!
24
0
0

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

全文

(1)

第 5 章 函数

5.1 教学要点

在学生具备初步使用函数的基础上,本章将系统介绍函数的定义和函数调用。学习如何 针对具体问题,确定需要使用函数的功能要求,再将功能用函数程序实现,并考虑如何调用 定义好的函数,实现主调函数与被调函数的连接。本章后半部分将讨论函数与变量间的关系,

局部变量、全局变量和静态变量,不同形式的变量在函数中起的作用将不同。本章难点是:

函数功能模块的确定;函数参数确定,函数参数与函数内局部变量的角色分配;函数参数的 传递是值传递,形参的改变不会影响实参的变化。

5.1 节以计算圆柱体体积为例,通过定义体积计算功能的函数和主函数调用的例子,引

出函数定义的一般形式:函数首部加函数体,且在函数结尾处通过return 语句返回结果。本

节要重点掌握两点:

(1)形参、实参及参数传递。形参只能用变量表示,在函数中起作用;实参可以是变 量、常量或表达式,在主调函数中。实参与形参要个数一致,类型一致。参数传递时,实参 把变量、常量的值或者表达式结果值传递给形参。

(2)函数定义首部与声明。二者形式相似,函数声明的目的主要是说明函数的类型和

参数的情况,以保证程序编译时对函数调用是否正确做出判断,声明位置通常在主函数前面。

而函数定义首部位于函数定义的第一行,与函数体连续书写组成函数定义。

接下来,通过3 个例子学习使用函数编写程序的基本方法。第 1 个例子定义的是判断函

数,用整数1 代表结果为真,0 代表假。第 2 个例子更像计算数学意义上的函数,有 π 的计

算结果。第3 个例子,介绍了判断素数的函数,该函数的写法较为经典,要求学生充分理解

算法,并能灵活掌握该函数的其他实现形式。

5.2 节以数字金字塔为例,介绍函数的另一种形式,即不返回结果的函数。不返回结果 的函数在定义、调用、参数传递、函数声明上,思路完全与以前相同,只是函数类型变为 void。重点介绍它适用的场合。

最后,强调函数的优点,即结构化程序设计的思路,包括按自顶向下的方法对问题进行

分析、模块化设计和结构化编码3 个步骤,并引入一些生活中的实例展开说明。

5.3 节以复数运算为例,介绍变量与函数的关系,主要掌握局部变量、全局变量和静态 变量三种形式。需要分清三种变量的作用与定义的位置,区分局部变量与全局变量的异同点,

局部变量与静态局部变量的异同点。最后引入两个实例,财务现金记账和求n 的阶乘,分别

介绍了全局变量和静态局部变量的典型应用。要求学生观察如何通过全局变量返回多个计算 结果,以及如何利用静态局部变量的生存周期保存函数多次调用的结果。

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

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

(2)

图 5.1 知识能力结构图

5.2 讲稿

1

Chap 5 函数

5.1 计算圆柱体积 5.2 数字金字塔 5.3 复数运算

本章分3 节。

2

本章要点

n函数的作用?如何确定函数功能?

n怎样定义函数?如何调用函数?定义函数与声 明函数有何区别?

n什么是函数的参数?怎样确定函数的参数?

n在函数调用时,参数是如何传递数据的?

n变量与函数有什么关系?如何使用局部变量和 全局变量?

n什么是静态变量?

提出本章的学习要点。

函数概念与定义

函数结构

变量与函数 关系

函数声明

函数定义首部

函数体

函数类型 函数参数

函数实现

函数返回

局部变量 /自动变量

全局变量 静态(局部)变量

能 够 根 据 问 题 确 定 函数功能

有 模 块 化 编程思想,

能 够 使 用 函 数 进 行 熟练编程

掌 握 三 种 变 量 的 作用与作用范围,

区 分 静 态 变 量 的

生命周期

(3)

3

5.1 计算圆柱体积

n 5.1.1 程序解析

n 5.1.2 函数的定义

n 5.1.3 函数的调用

n 5.1.4 函数程序设计

本章共分为4 小节。

4

5.1.1 程序解析-计算圆柱体积

例5-1 输入圆柱体的高和半径,求圆柱体积,

volume=π*r2*h。

要求定义和调用函数cylinder (r, h )计算圆 柱体的体积。

以简单例子入门,整体说明函数结构。

可以先请学生回顾第二章中学习过的函 数结构。

5 /* 计算圆柱体积 */ 例5-1源程序

#include <stdio.h>

double cylinder (double r, double h); /* 函数声明*/

int main( void ) {

double height, radius, volume;

printf ("Enter radius and height: ");

scanf ("%lf%lf", &radius, &height);

/* 调用函数,返回值赋给volume */

volume =

cylinder (radius, height );

printf ("Volume = %.3f\n", volume);

return 0;

}

主函数结构,重点关注函数声明与函数调 用形式

6

例5-1源程序

/* 定义求圆柱体积的函数 */

double cylinder (double r, double h) {

double result;

result =3.1415926 * r * r * h; /* 计算体积 */

return result;

/* 返回结果 */

}

Enter radius and height: 3.0 10 Volume = 282.743

Cylinder 函数的定义,重点关注函数首部 与函数声明的区别、函数结果返回的形式

(4)

7

例5-1源程序

#include <stdio.h>

double cylinder (double r, double h); /* 函数声明*/

int main( void )

{ double height, radius, volume;

printf ("Enter radius and height: ");

scanf ("%lf%lf", &radius, &height);

volume =

cylinder (radius, height );

printf ("Volume = %.3f\n", volume);

return 0;

}

double cylinder (double r, double h) {

double result;

result =3.1415926 * r * r * h;

return result;

}

Enter radius and height: 3.0 10 Volume = 282.743

问题:

函数是如何运行的?

重点介绍函数的调用过程:

l main 函数àcylinder 函数 l 实参à形参

l return 的结果返回。

再对比一下函数首部与函数声明。

8

5.1.2 函数的定义

n函数是指完成一个特定工作的独立程序模块。

¨库函数:由C语言系统提供定义 如scanf()、printf()等函数

¨自定义函数:需要用户自己定义 如计算圆柱体体积函数cylinder()

nmain()也是一个函数,C程序由一个main()或多个 函数构成。

n程序中一旦调用了某个函数,该函数就会完成特定 的计算,然后返回到调用它的地方。

¨函数经过运算,得到一个明确的运算结果,并需要回送 该结果。例如,函数cylinder()返回圆柱的体积。

概括一下到目前用过的函数:

main---主函数

printf、scanf---系统函数 cylinder---自定义函数 问 题 :函数到底有什么作用?

根据作用(是否有运算结果值)分成 2 类,下面根据这两类分别讨论函数定义

9

5.1.2 函数定义

函数类型 函数名(形参表) /* 函数首部 */

{

/* 函数体 */

函数实现过程

return 表达式;

}

把函数运算的结果回送给主函数

只能返回一个值 函数返回值的类型

没有分号 double cylinder (double r, double h) { double result;

result = 3.1415926 * r * r * h;

return result;

}

先介绍有结果值的函数,主要体现在:

1、 函数有确切类型---函数首部 2、 return 语句

函数类型与 return 返回的表达式类 型应一致。

10

double cylinder (double r, double h)

/* 函数首部*/

{

/* 函数体,写在一对大括号内*/

double result;

result =3.1415926 * r * r * h; /* 计算圆柱体积 */

return result; /* 返回运算结果*/

}

分析函数的定义

函数类型 函数名 形参表

与函数类型一致

仍关注函数首部----形参表:

建议和学生讨论一下什么是形参(函 数计算必须提供的已知条件),可以举一 个例子(比如1!+2!+...+n!),形参是什 么?再问:计算过程中要用到的循环变量 i、求和值 sum 是否也是形参?

提 醒 :形参表定义时每个参数名都需 要类型指定。

(5)

11

形参

类型1 参数1 ,类型2 参数2 ,……,类型n 参数n 参数之间用逗号分隔,每个参数前面的类型都必须分别写明

函数类型 函数名(形参表)

{

函数实现过程 return 表达式;

}

不能写成double r, h

double cylinder (double r, double h) { double result;

result =3.1415926 * r * r * h;

return result;

}

小结一下函数结构组成:

函数首部(函数类型、函数名、形参 表)----无分号,它不是一个完整的语句。

函数体(函数实现、函数返回---注意 类型)。

12

5.1.3 函数的调用

n定义一个函数后,就可以通过程序来调用这 个函数。

n调用标准库函数时,在程序的最前面用

#include命令包含相应的头文件。

n调用自定义函数时,程序中必须有与调用函 数相对应的函数定义。

本小节主线:函数调用(函参数传递)+

返回

什么是函数调用?

——可以从系统函数scanf 和 printf 解释;

任何函数调用都要有来源:

l include(系统函数),调用标准库函数

所需包含的头文件可参阅附录A

l 自定义函数定义

13

1.函数调用的形式

n函数调用的一般形式为:

函数名( 实际参数表)

n对于实现计算功能的函数,函数调用通常出 现在两种情况:

¨赋值语句

volume = cylinder(radius, height );

¨输出函数的实参

printf(“%f”, cylinder(radius, height ) );

函数调用形式:

l 有返回值

n 表达式调用形式 n 函数参数形式 l 无返回值

n 调用语句

1、 在函数调用时,实参前不要加数据类 型;

2、 函数调用可以直接作为输出参数

14

2. 函数调用的过程

n计算机在执行程序时,从主函数main开始 执行,如果遇到某个函数调用,主函数被 暂停执行,转而执行相应的函数,该函数 执行完后,将返回主函数,然后再从原先 暂停的位置继续执行。

n函数遇return返回主函数

结合函数调用的单步调试工具,用实 例演示程序流程

(6)

15 #include <stdio.h> 分析函数调用的过程

double cylinder (double r, double h);

int main( void )

{ double height, radius, volume;

printf ("Enter radius and height: ");

scanf ("%lf%lf", &radius, &height);

volume =

cylinder (radius, height );

printf ("Volume = %.3f\n", volume);

return 0;

}

double cylinder (double r, double h) {

double result;

result =3.1415926 * r * r * h;

return result;

}

调用函数 实参à形参

执行函数中的语句 返回调用它的地方

穿插介绍单步调试工具的使用。

16

3.参数传递

n函数定义时的参数被称为形式参数(简称形参)

double cylinder (double

r, double h);

n函数调用时的参数被称为实际参数(简称实参)

volume = cylinder (radius,height);

n参数传递:实参à形参

¨在参数传递过程中,实参把值复制给形参。

¨形参和实参一一对应:数量一致,类型一致,顺序一致

¨形参:变量,用于接受实参传递过来的值

¨实参:常量、变量或表达式 单向传递

参数传递是函数内容的难点:

1、 分清形参与实参的概念

2、 形参的形式——只能是变量,在函数 首部定义

3、 实参的形式可以是:变量、常量或表 达式

4、 参数传递:实参把结果值传给形参 5、 形参改变不影响实参变量(单向传递)

17

4.函数结果返回

n完成确定的运算,将运算结果返回给主调函 数。

n函数结果返回的形式:

¨return 表达式;

¨return (表达式);

本例子的函数调用是针对有返回值 函数——采用赋值表达式形式;sssb

对上一页讲到的执行流程在本例子 中给予具体解释,加深学生理解:当调用 cylinder 时,main 函数并未结束,只是暂 停,等cylinder 结束后,main 再继续

提醒学生:函数声明别忘

18

【例5-2】定义判断奇偶数的函数even (n)

定义一个判断奇偶数的函数even (n),当n为偶数时返回1,否 则返回0。

/* 判断奇偶数的函数 */

int even (int n) /* 函数首部 */

{

if(n%2 == 0) /* 判别奇偶数 */

return 1;

/* 偶数返回1 */

else

return 0;

/* 奇数返回0 */

}

如何调用该函数?

#include <stdio.h>

int main( void ) { int x,sum=0 ;

。。。。

if (

even(x)

==1) sum=sum+x ; printf(“%d”, sum);

return 0;

}

本例子的函数实现较简单。作为判 断 类型 的函数,需要对判断结果的真假自行定义 结果值,比如真 1/假 0,同时主函数调用 形式要与之相匹配:

if(even(x)= =1)

也可以简化为

if(even(x))——学生往往难理解 这样的判断函数实现方法可以适用于 素数判断等。

(7)

19

5.函数原型声明

函数类型 函数名( 参数表) ;

double cylinder (double r, double h);

void pyramid (int n);

n函数必须先定义后调用,将主调函数放在被调函 数的后面,就像变量先定义后使用一样。

n如果自定义函数在主调函数的后面,就需要在函 数调用前,加上函数原型声明。

n函数声明:说明函数的类型和参数的情况,以保 证程序编译时能判断对该函数的调用是否正确。

只写函数定义中的第1行(函数首部),并以分号结束。

强调两点:

1、函数原型声明的位置与形式(简单记 忆:即函数首部加分号)

2、函数原型声明的作用(同变量声明)

20

§5.1.3 函数调用

小结:

在执行函数调用时,实参把值计算出来,拷贝 给相应位置的形参;函数执行完后,通过 ret urn( ),可返回一个结果值。

实参与形参 有多个实参时 形参的改变

个数相同、类型一致 后面的先计算 不影响实参 变量的值

只能返回一个结果,

类型与函数定义时一致

要调用函数,

必须先要声明!

强调实参与形参之间的“三个一致”原则:

1、个数一致 2、类型一致 3、顺序一致

可借鉴例子演示当一致原则不符合时,程 序会出现什么结果,加深印象。

21

5.1.4 函数程序设计

例5-3 输入精度e,使用格里高利公式求π的 近似值,精确到最后一项的绝对值小于e。

要求定义和调用函数 funpi(e) 求π的近似 值。

… +

− +

= 7

1 5 1 3 1 1 4 π

什么做 参数?

本节通过 2 个例子学习函数程序设计方

法。

例5-3,多项式计算,首先分析函数功能、

参数、结果返回。

22 /* 用格里高利公式计算π的近5-3 源程序

似值,精度为e */

#include <stdio.h>

#include <math.h>

double funpi (double e);

int main (void) { double e, pi;

printf ("Enter e:");

scanf ("%lf", &e);

pi = funpi (e);

printf ("pi = %f\n", pi);

return 0;

}

double funpi (double e) { int denominator, flag;

double item, sum;

flag = 1;

denominator = 1;

item = 1.0;

sum = 0;

while (fabs (item) >= e){

item = flag * 1.0 / denominator;

sum = sum + item;

flag = -flag;

denominator = denominator + 2;

}

return sum * 4;

}

Enter e: 0.0001 pi = 3.1418

分析:

1、 明确函数形参与函数内局部变量。

2、 函数调用形式(赋值表达式)

3、 函数结果返回形式:表达式 4、 函数声明构成

(8)

23

例5-4 判断素数的函数

例5-4 求100以内的全部素数,每行输出10个。素 数就是只能被1和自身整除的正整数,1不是素数,

2是素数。

要求定义和调用函数prime (m)判断m是否为 素数,当m为素数时返回1,否则返回0。

n算法描述:对2~100之间的每个数进行判断,若 是素数,则输出该数。

for(m = 2; m <= 100; m++) if (m是素数)

printf("%d ", m);

prime(m) != 0

本例过程实现比even 函数复杂些。但首先 要确定函数功能:是只判素数还是实现

“100 以内的全部素数的输出”?——我 们选择素数判断,因为这是一个相对通用 的功能,被重复使用的机会多。

重点关注:

1、 自定义判断结果值;

2、 函数调用形式。

24

例5-4 源程序

#include <stdio.h>

#include <math.h>

int prime (int m);

int main(void) { int count, m;

count = 0;

for(m = 2; m <= 100; m++){

if ( prime(m) != 0 ){

printf("%6d", m );

count++;

if (count %10 == 0) printf ("\n");

} } printf ("\n");

}

int prime (int m) { int i, n;

if ( m == 1 ) return 0;

n = sqrt (m);

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

return 0;

} return 1;

}

prime 函数有 3 个 return 语句,意味着函数 有3 个返回点:

1、 if ( m == 1 ) 1 非素数

2、 if (m % i == 0) 被整除,非素数 3、 函数结尾处:确保 m 不是 1 也没

有被整除,是素数 讨论:

1、 只用一个 return 语句的实现;

2、 调用时不用!=0 该如何实现。

25

5.2 数字金字塔

n5.2.1 程序解析

n5.2.2 不返回结果的函数

n5.2.3 结构化程序设计思想

再介绍无结果值的函数,主要体现在:

1、 函数类型---void 2、 可以没有 return 语句

和学生讨论一下:无结果返回的函数的作 用---结构化思想

26

例5-5 输出5之内的数字金字塔。

/* 输出数字金字塔 */

#include <stdio.h>

void pyramid (int n); /* 函数声明*/

int main (void) {

pyramid(5); /* 调用函数,输出数字金字塔 */

return 0;

}

void pyramid (int n) /* 函数定义*/

{ int i, j;

for (i = 1; i <= n; i++){ /* 需要输出的行数 */

for (j = 1; j <= n-i; j++) /* 输出每行左边的空格 */

printf(" ");

for (j = 1; j <= i; j++) /* 输出每行的数字 */

printf(" %d ", i); /* 每个数字的前后各有一个空格 */

putchar ('\n');

} }

1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 for (i = 1; i <= n; i++) {

一行的处理 }

一行中的空格处理;

一行中的数字显示 }

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

printf(“ ”);

一行中的数字显示 }

向学生指出本例子主函数的简洁性--- 使用函数的优点;

二维输出格式一般需要二重循环,但 从思路上可以先讲一重(外循环),进 而对于一行的实现,又需要一层循环,

从而构成了二重循环。

(9)

27

5.2.2 不返回运算结果的函数定义

void

函数名(参数表) /* 函数首部 */

{ /* 函数体 */

函数实现过程

return; /* 可以省略return */

}

这类函数通常用于屏幕输出等 表示不返回结果

不能省略

, 否则

函数类型被默认定义为int

起计算作用的函数结果返回,学生比 较容易理解,但要注意类型;

对于无结果返回的函数,其作用是在 函数过程中完成(屏幕或变量的改变),

不使用return 时,以函数体右大括号返回 函数调用过程:执行流程改变需向学生交 待清楚。

对于无返回值的函数,可以不需要 return 语句,到函数结束时的最后一个大括号也 能返回。

28

5.2.2 不返回运算结果的函数定义

n由于函数没有返回结果,函数调用不可能出现在表达式 中,通常以独立的调用语句方式,如pyramid(5);

n不返回结果的函数,在定义、调用、参数传递、函数声 明上,思路完全与以前相同,只是函数类型变为void。

n它适用把一些确定的、相对独立的程序功能包装成函数。

¨主函数通过调用不同的函数,体现算法步骤

¨各步骤的实现由相应函数完成

¨简化主函数结构,以体现结构化程序设计思想。

解单介绍不返回结果函数的适用及常用 场合,通常不是为了计算得到某个结果,

而是要用程序产生某些效果,如打印屏幕 等。

需强调的是:不返回结果的函数可以没有 return 语句,但并不意味着不能返回,遇 到函数体最后的大括号可以自动返回主 调函数,可通过单步演示加深印象。

29

5.2.3

结构化程序设计思想

n结构化程序设计(Structured Programming)

¨程序设计技术

¨C语言是结构化程序设计语言

n强调程序设计的风格和程序结构的规范化,

提倡清晰的结构

¨基本思路是将一个复杂问题的求解过程划分为若 干阶段,每个阶段要处理的问题都容易被理解和 处理。

¨按自顶向下的方法对问题进行分析、模块化设计 和结构化编码等3个步骤。

可简单结合“软件工程”的概念介绍模块 化程序设计思想,以生活中的实际例子举 例说明。

30

1. 自顶向下的分析方法

n把大的复杂的问题分解成小问题后再解决

¨面对一个复杂的问题,首先进行上层(整体)

的分析,按组织或功能将问题分解成子问题

¨如果子问题仍然十分复杂,再做进一步分解,

直到处理对象相对简单,容易处理为止。

¨当所有的子问题都得到了解决,整个问题也就 解决了。

n每一次分解都是对上一层的问题进行细化和 逐步求精,最终形成一种类似树形的层次结 构,来描述分析的结果。

可结合“瀑布式”概念简单介绍。

(10)

31

学生成绩统计程序

成绩输入 数据计算 数据查找 输出成绩

计算学生平均分 计算课程平均分

学生成绩统计程序的层次结构图

模块用函数实现

要求学生掌握层次结构图的画法,体现在 实验报告中。

32

2. 模块化设计

n将模块组织成良好的层次系统

¨顶层模块调用其下层模块以实现程序的完整功能

¨每个下层模块再调用更下层的模块,从而完成程序的 一个子功能,

¨最下层的模块完成最具体的功能。

n遵循模块独立性的原则,即模块之间的联系应尽 量简单。

¨模块用函数实现。

¨一个模块只完成一个指定的功能。

¨模块之间只通过带参数的函数进行调用。

模块化设计有利于多人合作的大型软件 开发,强调团队精神,共享意识。

33

3. 结构化编码主要原则

n经模块化设计后,每一个模块都可以独立编码。编 程时应选用顺序、选择和循环三种控制结构

n对变量、函数、常量等命名时,要见名知意,有助 于对变量含义或函数功能的理解。

n在程序中增加必要的注释,增加程序的可读性。

n要有良好的程序视觉组织,利用缩进格式

n程序要清晰易懂,语句构造要简单直接

n程序有良好的交互性,输入有提示,输出有说明

强调代码的规范性、可读性、美观程度等,

尤其要重视注释的作用。

34

5.3 复数运算

5.3.1 程序解析

5.3.2 局部变量和全局变量

5.3.3 变量生命周期和静态局部变量

本节新的内容:全局变量与静态变量,它 们都是与函数相关的。

(11)

35

例5-6 分别输入2个复数的实部与虚部,用函 数实现计算2个复数之和与之积。

n分析

¨若2个复数分别为:

c1=x1+y1i , c2=x2+y2i

¨则:

c1+c2 = (x1+x2) + (y1+y2)i

c1*c2 = (x1*x2-y1*y2) + (x1*y2+x2*y1)i

该例的特殊之处在于,需要返回两个计算 结果,而通过常规函数调用是无法实现 的,需要考虑引入全局变量。

36

#include<stdio.h>

float result_real, result_imag; /* 全局变量,用于存放函数结果 */

/* 函数声明 */

void complex_prod(float real1, float imag1, float real2, float imag2);

void complex_add(float real1, float imag1, float real2, float imag2);

int main(void) {

float imag1, imag2, real1, real2; /* 两个复数的实、虚部变量 */

printf("Enter 1st complex number(real and imaginary): ");

scanf("%f%f", &real1, &imag1); /* 输入第一个复数 */

printf("Enter 2nd complex number(real and imaginary): ");

scanf("%f%f", &real2, &imag2); /* 输入第两个复数 */

complex_add(real1, imag1, real2, imag2); /* 求复数之和 */

printf("addition of complex is %f+%fi\n", result_real, result_imag);

complex_prod(real1, imag1, real2, imag2); /* 求复数之积 */

printf("product of complex is %f+%fi\n", result_real, result_imag);

return 0;

}

运行结果

Enter 1st complex number(real and imaginary):1 1 Enter 2nd complex number(real and imaginary):-2 3 addition of complex is -1.000000+4.000000i product of complex is -5.000000+1.000000i

将复数之积的实部与虚部定义为全局变 量,扩大其作用范围。

37

void complex_add(float real1, float imag1, float real2, float imag2)

{

result_real = real1 + real2;

result_imag = imag1 + imag2;

}

void complex_prod(float real1, float imag1, float real2, float imag2)

{

result_real = real1*real2 - imag1*imag2;

result_imag = real1*imag2 + real2*imag1;

}

让学生观察,result_real 和 result_imag 两 个变量在complex_add()和 complex_prod() 中是否有定义?体会全局变量与局部变 量定义位置的不同。

38

5.3.2 局部变量和全局变量

n局部变量

¨在函数内定义的变量(包括形参)

作用范围:本函数内部

¨定义在复合语句内的变量 作用范围:复合语句内部

n全局变量

在函数以外定义的变量,不从属于任一函数。

作用范围:从定义处到源文件结束(包括各函数)

前面使用的变量均为局部变量——在函 数内使用,这里再增加一种在复合语句内 使用的局部变量。

全局变量是相对于局部变量而言的——

不属于任意函数的,在函数外定义,包括 主函数。

(12)

39

例5-6 在复合语句中定义局部变量。

#include <stdio.h>

int main (void) {

int a;

a = 1;

{ /* 复合语句开始 */

int b = 2;

b = a + b;

a = a + b;

} /* 复合语句结束 */

printf ("%d " , a );

return 0;

}

b: 小范围内的临时变量 输出:

4

改成b会如何?

学习复合语句内使用的局部变量的使用,

注意变量b 的有效范围(复合语句内)

40

5-7 全局变量定义

#include "stdio.h"

int x; /* 定义全局变量x */

int f( ) {

int x = 4; /* x为局部变量 */

return x;

} int main(void) {

int a = 1;

x= a; /* 对全局变量 x 赋值 */

a = f( ); /* a的值为4 */

{ int b = 2;

b = a + b; /* b的值为4 */

x= x+ b; /* 全局变量运算 */

}

printf("%d %d" , a, x);

return 0;

}

若局部变量与全局变量 同名,局部变量优先

输出:

4, 7

注意全局变量的有效范围,一旦局部变量 与全局变量同名,局部变量优先

41

变量作用范围示例

int x=1;

void main( ) { int a=2;

……..

{ int b=3;

…..

} f( );

………..

} int t=4 ; void f( ) { int x=5, b=6;

…….

} int a=7;

x=? a=? b=?

b=?

x=5 b=6 t=4 a没定义 x=? b=? t=? a=?

静态变量x,t,a 所处位置不同,作用范围不

同。本例中 a 虽然是全局变量,但因位于

程序最后,未起任何作用。

42

n【例5-8】

¨用函数实现财务现金记账。先输入操作类型(1 收入,2支出,0结束),再输入操作金额,计算 现金剩余额,经多次操作直到输入操作为0结 束。要求定义并调用函数,其中现金收入与现 金支出分别用不同函数实现。

n分析:

¨设变量cash保存现金余额值,由于它被主函数、

现金收入与现金支出函数共用,任意使用场合 其意义与数值都是明确和唯一的,因此令其为 全局变量。

分析变量cash 被设置为全局变量的原因

(13)

43

#include<stdio.h>

float cash; /* 定义全局变量,保存现金余额 */

void income(float number), expend(float number); /* 函数声明 */

int main(void) { int choice;

float value;

cash = 0; /* 初始金额=0 */

printf("Enter operate choice(0--end, 1--income, 2--expend):");

scanf("%d", &choice); /* 输入操作类型 */

while (choice != 0){ /* 若输入类型为0,循环结束 */

if (choice == 1 || choice == 2) {

printf("Enter cash value:"); /* 输入操作现金额 */

scanf("%f", &value);

if (choice == 1) income(value); /* 函数调用,计算现金收入 */

else expend(value); /* 函数调用,计算现金支出 */

printf("current cash:%.2f\n", cash);

}

printf("Enter operate choice(0--end, 1--income, 2--expend):");

scanf("%d", &choice); /* 继续输入操作类型 */

} return 0;

}

/* 定义计算现金收入函数 */

void income(float number)

{ cash= cash + number; /* 改变全局变量cash */

}

/* 定义计算现金支出函数 */

void expend(float number)

{ cash= cash - number; /* 改变全局变量cash */

}

Enter operate choice(0--end, 1--income, 2--expend):1 Enter cash value:1000

current cash:1000.00

Enter operate choice(0--end, 1--income, 2--expend):2 Enter cash value:456

current cash:544.00

Enter operate choice(0--end, 1--income, 2--expend):0

同时强调全局变量不能滥用,及有可能带 来的副作用。

44

5.3.2 局部变量和全局变量

n讨论

¨全局变量比局部变量自由度大,更方便 ? n引起注意

¨对于规模较大的程序,过多使用全局变量会带来 副作用,导致各函数间出现相互干扰。如果整个 程序是由多人合作开发,各人都按自己的想法使 用全局变量,相互的干扰可能会更严重。

¨因此在变量使用中,应尽量使用局部变量,从某 个角度看使用似乎受到了限制,但从另一个角度 看,它避免了不同函数间的相互干扰,提高了程 序质量。

注意全局变量的有效范围,一旦局部变量 与全局变量同名,局部变量优先

45

n变量生命周期

变量从定义开始分配存储单元,到运行结束存储单元被回收 的整个过程。

n自动变量(

auto

: 普通的局部变量 int x, y; çè

auto

int x, y;

char c1; çè

auto

char c1;

¨函数调用时,定义变量,分配存储单元。

¨函数调用结束,收回存储单元。

n全局变量:从程序执行开始,到程序的结束,存储单 元始终保持。

5.3.3 变量生命周期和静态局部变量

变量生 命 周 期 与作 用 范 围 是两个不同概 念,它是指变量被系统分配单元到被回收 的时间。在生命周期内,变量可能处于作 用范围中,也可能不在作用范围中。但变 量不在生命周期内,必定不会有作用范 围。

46

C程序存储分布示意图(例5-6)

从内存空间分配引入静态变量概念:

内存分系统区和用户区,用户区又分程序 区与数据区,数据区再分静态与动态。

静态变量的生命期较动态变量长。

(14)

47

static

类型名 变量表

n作用范围:局部变量

n生命周期:全局变量

静态局部变量

静态变量定义及特征。

48

【例5-9】输入正整数n,输出1!~n!的值。要求定 义并调用含静态变量的函数fact_s(n)计算n!。

#include <stdio.h>

double fact_s(int n);

int main(void) {

int i, n;

printf("Input n:");

scanf("%d", &n);

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

printf("%3d!=%.0f\n", i, fact_s(i)); /* 输出i和i! */

return 0;

}

double fact_s(int n)

{ static double f = 1; /* 定义静态变量,第一次赋值为1 */

f = f * n; /* 在上一次调用时的值上乘n */

return(f);

}

fact_s()函数中并没有循环语句,

它是靠静态变量f保存着上次函数 调用时,计算得到的(n-1)!值,再 乘上n,实现n!的计算。

通过这个例子可以较好的理解静态变量

的使用。要解释静态变量 f 的初值规定、

第二次进入函数时f 的值

静态变量通常要多次调用函数才会显示 作用。

49

静态局部变量

n自动变量如果没有赋初值,其存储单元中将 是随机值。

n就静态变量而言,如果定义时没有赋初值,

系统将自动赋0。

n赋初值只在函数第一次调用时起作用,以后 调用都按前一次调用保留的值使用。

n静态局部变量受变量作用范围限制,不能作 用于其他函数(包括主函数)。

特点:

1、初值自动赋为 0 2、初值第一次有效

50

静态局部变量

n静态变量与全局变量均位于静态存储区

¨他们的共同点是生命周期贯穿整个程序执行过程。

¨区别在于作用范围不同,全局变量可作用于所有 函数,静态变量只能用于所定义函数,而不能用 于其他函数。

静态局部变量具有全局变量的生命周期、

局部变量的作用范围。

(15)

51

本章小结

n系统介绍函数的定义和函数调用

¨学习如何针对具体问题,确定需要使用函数的 功能要求,再将功能用函数程序实现

¨考虑如何调用定义好的函数,实现主调函数与 被调函数的连接

¨确定参数功能,掌握参数的传递实现 n函数与变量间的关系,不同形式的变量在

函数中起的作用不同。

¨局部变量、全局变量和静态变量

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

• 能够根据问题合理确定函数功能

• 能够使用函数结构进行熟练编程

• 能够重点掌握参数的定义、传递 能够合理运用全局变量、静态变量

5.3 练习与习题参考答案

5.3.1 练习参考答案

【练习5-1】使用函数求1到n之和: 输入一个正整数 n,输出 1~n 之和。要求定义和调用 函数sum(n)求 1~n 之和。若要计算 m~n(m<n)之和,又该如何定义函数?试编写相应程序。

解答:

#include <stdio.h>

int sum(int m, int n);

int main(void) {

int m, n;

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

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

return 0;

}

int sum(int m, int n) {

int result, i;

result = 0;

for(i = m; i <= n; i++) result = result + i;

return result;

}

(16)

#include <stdio.h>

int max(int a, int b);

int main(void) {

int a, b, maximum;

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

maximum = max(a, b);

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

return 0;

}

int max(int a, int b) {

if(a > b) return a;

else

return b;

}

【练习5-3】数字金字塔:输入一个正整数 n, 输出 n 行数字金字塔。试编写相应程序。

解答:

#include <stdio.h>

void pyramid(int n);

int main(void) {

int n;

scanf("%d", &n);

pyramid(n);

return 0;

}

void pyramid(int n) {

int i, j;

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

for(j = 1; j <= n - i; j++) printf(" ");

for(j = 1; j <= i; j++)

(17)

【练习5-4】思考: 若把例 5-9 中静态变量f定义成普通局部变量,还能实现计算n!吗?

请上机检验。若把f 换成全局变量又会如何?

解答:如果把静态变量f定义成普通局部变量,则每次进入函数时变量f 的值都会重新赋值

为1,不能保留上次调用的计算结果,那么就无法实现计算 n!。如果把 f 换成全局变量,也

可以实现计算n!。

5.3.2 习题参考答案 一、选择题

1 2 3 4 5 6

D D B A C B

二、填空题

1、2357

2、int fun1(int m); int m k=m%10; m=m/10 3、char zf printf(" "); i=1;i<=n;i++

三、程序设计题

1. 使用函数计算分段函数的值:输入 x,计算并输出分段函数 f(x)的值。要求定义和调用函 数sign(x)实现该分段函数。试编写相应程序。

解答:

#include <stdio.h>

int sign(int x);

int main(void) {

int x, y;

scanf("%d",&x);

y = sign(x);

printf("f(%d) = %d\n", x, y);

return 0;

}

int sign(int x) {

int y;

if(x > 0) y = 1;

else if(x == 0) y = 0;

else y = -1;

return y;

(18)

程序。

解答:

#include <stdio.h>

int even(int n);

int main(void) {

int n, sum;

scanf("%d",&n);

sum = 0;

while(n > 0){

if(even(n) == 0) sum = sum + n;

scanf("%d", &n);

}

printf("The sum of the odd numbers is %d.\n", sum);

return 0;

}

int even(int n) {

int y;

if (n % 2 == 0) y = 1;

else y = 0;

return y;

}

3. 使用函数计算两点间的距离: 给定平面任意两点坐标(x1, y1)和(x2, y2),求这两点之间 的距离(保留2位小数)。要求定义和调用函数dist(x1, y1, x2, y2)计算两点间的距离。试编 写相应程序。

解答:

#include <stdio.h>

#include <math.h>

double dist(double x1, double y1, double x2, double y2);

int main(void) {

double distance, x1, y1, x2, y2;

scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);

distance = dist(x1, y1, x2, y2);

(19)

double dist(double x1, double y1, double x2, double y2) {

return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

}

4. 利用函数计算素数个数并求和:输入2 个正整数 m 和 n(1<=m,n<=500),统计并输出 m 到 n 之间的素数的个数以及这些素数的和。素数就是只能被 1 和自身整除的正整数,1 不是素数,2 是素数。要求定义并调用函数 prime(m) 判断 m 是否为素数,当 m 为素数时 返回1,否则返回 0。

解答:

#include "stdio.h"

#include "math.h"

int main(void) {

int count, i, m, n, sum;

int prime(int m);

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

count = sum = 0;

for(i = m; i <= n; i++) if(prime(i) != 0){

count++;

sum = sum + i;

}

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

}

int prime(int m) {

int k, i;

if(m == 1) return 0;

k = sqrt(m);

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

return 1;

}

5.使用函数统计指定数字的个数:读入一个整数,统计并输出该数中"2"的个数。要求定义 并调用函数countdigit (number, digit),它的功能是统计整数number中数字digit的个数。例如, countdigit(12292, 2) 的返回值是3。试编写相应程序。

解答:

#include "stdio.h"

(20)

scanf("%d", &in);

count = countdigit(in, 2);

printf("Number %d of digit 2: %d\n", in, count);

return 0;

}

int countdigit(int number, int digit) {

int count;

if(number < 0) number = -number;

count = 0;

do{

if(number % 10 == digit) count++;

number = number / 10;

}while(number != 0);

return count;

}

6.使用函数输出水仙花数:输入 2 个正整数 m 和 n(1<=m,n<=1000),输出 m 到 n 之间 的 所 有 满 足 各 位 数 字 的 立 方 和 等 于 其 自 身 的 数 。 要 求 定 义 并 调 用 函 数 is(number)判断 number 的各位数字之立方和是否等于其自身,若相等则返回 1,否则返回 0。试编写相应 程序。

解答:

#include "stdio.h"

int is(int number);

int main(void) {

int i, m, n;

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

printf("result:\n");

for(i = m; i <= n; i++) if(is(i) != 0)

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

return 0;

}

int is(int number) {

(21)

digit = temp % 10;

temp = temp / 10;

sum = sum + digit * digit * digit;

}

return sum == number;

}

7. 使用函数求余弦函数的近似值:输入精度 e 和 x,用下列公式求 cos(x)的近似值,精确到 最后一项的绝对值小于e。

! ...

6 x

! 4 x

! 2 x

! 0 ) x x ( cos

6 4 2 0

+

− +

=

要求定义并调用函数 funcos(e,x)计算 cos(x) 的近似值。试编写相应程序。

解答:

#include "stdio.h"

#include "math.h"

double funcos(double e, double x);

int main(void) {

double e, sum, x;

scanf("%le%le", &e, &x);

sum = funcos(e, x);

printf("sum = %f\n", sum);

return 0;

}

double funcos(double e, double x) {

int flag, i;

double item, sum, tmp, power;

flag = 1;

power = 1;

tmp = 1;

i = 0;

item = 1;

sum = 0;

while(fabs(item) >= e){

item = flag * power / tmp;

sum = sum + item;

power = power * x * x;

(22)

return sum;

}

5.4 实验指导教材参考答案

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

(1)使用函数计算分段函数的值:输入 x,计算并输出下列分段函数 sign(x) 的值。要求 定义和调用函数 sign(x) 实现该分段函数。

1 x>0 sign(x) = 0 x=0 -1 x<0

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

(2)使用函数求奇数和:输入一批正整数(以零或负数为结束标志),求其中所有奇数的 和。要求定义和调用函数 even(n) 判断整数的奇偶性,当 n 为偶数时返回 1,否则返回 0。

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

(3)使用函数计算两点间的距离:给定平面任意两点坐标(x1,y1)和(x2,y2),求这两点之间 的距离(保留 2 位小数)。要求定义和调用函数 dist(x1, y1, x2,y2)计算两点间的距离。

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

( 4)使用函数计算素数个数并求和:输入两个正整数 m 和 n(1≤m,n≤500),统计并输出 m 和 n 之间的素数的个数以及这些素数的和。素数就是只能被 1 和自身整除的正整数,1 不是素数,

2 是素数。要求定义并调用函数 prime(m)判断 m 是否为素数,当 m 为素数时返回 1,否则返回 0。

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

(5)使用函数判断完全平方数:输入一个正整数 n,判断其是否为完全平方数,如果是,

则输出“YES”,如果不是,则输出“NO”。要求定义并调用函数 IsSquare(n),判断 n 是否 为完全平方数。

解答:

#include <stdio.h>

#include <math.h>

int IsSquare(int n);

int main(void) {

int n;

scanf("%d", &n);

(23)

}

return 0;

}

int IsSquare(int n) {

if (n < 0) { return 0;

}

int s = (int) sqrt((double) n);

if (s * s == n) { return 1;

} else { return 0;

} }

三、改错题

改正下列程序中的错误,求 1! + 2! + … + 10!,要求定义并调用函数 fact(n)计算 n!,函数 类型是 double。

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

出错信息: expected `,' or `;' before "int"

出错原因: double fact(int n) 函数声明后面少了一个分号

(2)改正编译错误后,程序又出现其他编译错误,请填写出错信息并分析原因。

出错信息: expected unqualified-id before '{' token 出错原因: double fact(int n); 函数定义首部多了一个分号

(3)改正编译错误后,程序还有其他编译错误,请填写出错信息并分析原因。

出错信息: non-lvalue in assignment 出错原因: 赋值运算符左边必须为一个变量 错误行号: 20 正确代码: result = result * i;

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

运行结果为: 1!+2!+…+10! = 1.#QNAN0 ,是否正确: 否

(5)请仔细分析错误产生的原因,模仿调试示例中的方法进行调试改错,简要说明你

的方法并给出正确语句:

方 法 : 在主程序第 9 行设置断点,执行到此处后观察 sum 的值,发现 sum 未初始化 为 0。改正后重新执行到该行语句,并跳入 fact 函数内部,发现 result 未初始化,将 result 初始值设为1 后重新执行到该行语句,计算阶乘正确,但是由于缺少 return 语句结果无法返

回。增加return 语句后返回到主函数,通过计算得到正确结果。最后将循环全部执行完,发

现循环控制变量i 的终值错误,原因是 for 循环的 i < 10 表达式错误,应该为 i <= 10,修改

(24)

错误行号: 8 正确语句: for (i = 1 ; i <= 10; i++) 错误行号: 14 正确语句: double fact(int n) 错误行号: 17 正确语句: double result=1;

错误行号: 20 正确语句: result = result * i;

错误行号: 21 正确语句: 增加语句 return result;

四、拓展编程题

(1)使用函数统计指定数字的个数:输入一个整数,统计并输出该数中 2 的个数。要求定 义并调用函数 countdigit(number,digit),它的功能是统计整数 number 中数字 digit 的个数。例如,

countdigit(10090,0)的返回值是 3。

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

( 2)使用函数输出水仙花数:输入两个正整数 m 和 n(1≤m,n≤1000),输出 m ~ n 之间的 所有满足各位数字的立方和等于它本身的数。要求定义并调用函数 is(number)判断 number 的各 位数字之立方和是否等于它本身。

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

(3)使用函数求余弦函数的近似值:输入精度 e 和 x,用下列公式求 cos(x)的近似值,精 确到最后一项的绝对值小于 e。要求定义和调用函数 funcos(e, x)求余弦函数的近似值。

= − + − + ⋅ ⋅ ⋅

! 6

! 4

! 2

! ) 0 cos(

6 4 2

0

x x x x x

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

(4)使用函数求最大公约数:输入两个正整数 x 和 y,要求定义并调用函数 gcd(x,y)求这 两个数的最大公约数。

解答:

#include <stdio.h>

int gcd(int x, int y);

int main() {

int x, y;

scanf("%d%d", &x, &y);

printf("最小公倍数是%d\n", (x*y)/gcd(x, y));

printf("最大公约数是%d\n", gcd(x, y));

return 0;

}

int gcd(int x, int y)

參考文獻

相關文件

[r]

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

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

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

一般说来, 对于一个区间上单调的函数的 图形都存在一个需要判别弧段位于相应的弦线

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

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

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