第 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。
图 5.1 知识能力结构图
5.2 讲稿
1
Chap 5 函数
5.1 计算圆柱体积 5.2 数字金字塔 5.3 复数运算
本章分3 节。
2
本章要点
n函数的作用?如何确定函数功能?
n怎样定义函数?如何调用函数?定义函数与声 明函数有何区别?
n什么是函数的参数?怎样确定函数的参数?
n在函数调用时,参数是如何传递数据的?
n变量与函数有什么关系?如何使用局部变量和 全局变量?
n什么是静态变量?
提出本章的学习要点。
函数概念与定义
函
数
函数结构
变量与函数 关系
函数声明
函数定义首部
函数体
函数类型 函数参数
函数实现
函数返回
局部变量 /自动变量
全局变量 静态(局部)变量
能 够 根 据 问 题 确 定 函数功能
有 模 块 化 编程思想,
能 够 使 用 函 数 进 行 熟练编程
掌 握 三 种 变 量 的 作用与作用范围,
区 分 静 态 变 量 的
生命周期
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 函数的定义,重点关注函数首部 与函数声明的区别、函数结果返回的形式
。
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()
nmain()也是一个函数,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 是否也是形参?
提 醒 :形参表定义时每个参数名都需 要类型指定。
11
形参
类型1 参数1 ,类型2 参数2 ,……,类型n 参数n 参数之间用逗号分隔,每个参数前面的类型都必须分别写明
函数类型 函数名(形参表)
{
函数实现过程 return 表达式;
}
不能写成double r, hdouble 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返回主函数
结合函数调用的单步调试工具,用实 例演示程序流程
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))——学生往往难理解 这样的判断函数实现方法可以适用于 素数判断等。
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、 函数声明构成
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 数字金字塔
n5.2.1 程序解析
n5.2.2 不返回结果的函数
n5.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(“ ”);
一行中的数字显示 }
向学生指出本例子主函数的简洁性--- 使用函数的优点;
二维输出格式一般需要二重循环,但 从思路上可以先讲一重(外循环),进 而对于一行的实现,又需要一层循环,
从而构成了二重循环。
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每一次分解都是对上一层的问题进行细化和 逐步求精,最终形成一种类似树形的层次结 构,来描述分析的结果。
可结合“瀑布式”概念简单介绍。
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 变量生命周期和静态局部变量
本节新的内容:全局变量与静态变量,它 们都是与函数相关的。
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全局变量
在函数以外定义的变量,不从属于任一函数。
作用范围:从定义处到源文件结束(包括各函数)
前面使用的变量均为局部变量——在函 数内使用,这里再增加一种在复合语句内 使用的局部变量。
全局变量是相对于局部变量而言的——
不属于任意函数的,在函数外定义,包括 主函数。
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 被设置为全局变量的原因
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)
从内存空间分配引入静态变量概念:
内存分系统区和用户区,用户区又分程序 区与数据区,数据区再分静态与动态。
静态变量的生命期较动态变量长。
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静态变量与全局变量均位于静态存储区
¨他们的共同点是生命周期贯穿整个程序执行过程。
¨区别在于作用范围不同,全局变量可作用于所有 函数,静态变量只能用于所定义函数,而不能用 于其他函数。
静态局部变量具有全局变量的生命周期、
局部变量的作用范围。
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;
}
#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++)
【练习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;
程序。
解答:
#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);
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"
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) {
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;
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);
}
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,修改
错误行号: 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)