Chap 5 函数 5.1 计算圆柱体积5.2 数字金字塔5.3 复数运算 本章要点函数的作用?如何确定函数功能?怎样定义函数?如何 调用函数?定义函数与声明函数有何区别?什么是函数的参数?怎样确定函数的参数?在函数调用时,参数是如何传递数据的?变量与函数有什么关 系?如何使用局部变量和全局变量?什么是静态变量?5.1 计算圆柱体积5.1.1 程序解析 5.1.2 函数的定义 5.1.3 函数的调用 5.1.4 函数程序设计5.1.1 程序解析-计算圆柱体积 例5-1 输入圆柱体的高和半径,求圆柱体积,vo lume=πr2h。 要求定义和调用函数cylinder (r, h )计算圆柱体的体积。 例5-1源程序/ 计算圆柱体积 /#include int main( void ){ double height, radius, v olume; double cylinder (double r, double h); / 函数声明/ pr intf ("Enter radius and height: "); scanf ("%lf%lf", &radius, &h eight); / 调用函数,返回值赋给volume / volume = cylinder (radius, he ight ); printf ("Volume = %.3f\n", volume); return 0;}例5-1源程序/ 定义求圆柱体积的函数 /double cylinder (double r, double h) { double resul t; result =3.1415926 r r h; / 计算体积 / return result; / 返回结果 /}Enter radius and height: 3.0 10Volume = 282.743例5 -1源程序#include int main( void ){ double height, radius , volume; double cylinder (double r, double h); / 函数声明/ 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 10Volume = 282.743问题: 函数是 如何运行的?5.1.2 函数的定义 函数是指完成一个特定工作的独立程序模块。库函数:由C语言系统提供定义如scanf()、pri ntf()等函数自定义函数:需要用户自己定义如计算圆柱体体积函数cylinder()main()也是一个函数,C程序由一个main ()或多个函数构成。程序中一旦调用了某个函数,该函数就会完成特定的计算,然后返回到调用它的地方。函数经过运算,得到一个明确的运算结 果,并需要回送该结果。例如,函数cylinder()返回圆柱的体积。5.1.2 函数定义函数类型 函数名(形参表) / 函数首部 /{ / 函数体 / 函数实 现过程 return 表达式;}把函数运算的结果回送给主函数只能返回一个值函数返回值的类型没有分号double cylind er (double r, double h){ double result; result = 3.1415926 r r h; return result;}double cylinder (double r, double h) / 函数首部 /{ / 函数体,写在一对大括号内 / double result; re sult =3.1415926 r r h; / 计算圆柱体积 / return result; / 返回运算结果/ }分析函数的定义函数类型函数名形参表与函数类型一致形参类型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;}5.1.3 函数的调用定义一个函数后,就可以通过程序来调用这个函数。调用标准库函数时,在程序的 最前面用#include命令包含相应的头文件。调用自定义函数时,程序中必须有与调用函数相对应的函数定义。1.函数调用的形式函数调用 的一般形式为: 函数名(实际参数表)对于实现计算功能的函数,函数调用通常出现在两种情况:赋值语句 volume = cylind er(radius, height );输出函数的实参 printf(“%f”, cylinder(radius, height ) );2. 函数调用的过程计算机在执行程序时,从主函数main开始执行,如果遇到某个函数调用,主函数被暂停执行,转而执行相应的 函数,该函数执行完后,将返回主函数,然后再从原先暂停的位置继续执行。函数遇return返回主函数分析函数调用的过程#include int main( void ){ double height, radius, volume; do uble cylinder (double r, double h); printf ("Enter radius and height: "); scanf ("%lf%lf", &radius, &height); volume = cylin der (radius, height ); printf ("Volume = %.3f\n", volume); retur n 0;}double cylinder (double r, double h) { double result; resul t =3.1415926 r r h; return result; }调用函数实参?形参执行函数中的语句返回调用它的地方3.参数传递函数定义时的参数被称为形式参数(简称形参) double cylinder (double r, double h);函数调用时的参数被称为实际参数(简称实参)volume = cylinder (radius, height);参数传递:实参?形参在参数传递过程中,实参把值复制给形参。形参和实参一一对 应:数量一致,类型一致,顺序一致形参:变量,用于接受实参传递过来的值实参:常量、变量或表达式单向传递4.函数结果返回完成确定的运算 ,将运算结果返回给主调函数。函数结果返回的形式:return 表达式;return (表达式);【例5-2】定义判断奇偶数的函 数even (n)定义一个判断奇偶数的函数even (n),当n为偶数时返回1,否则返回0。/ 判断奇偶数的函数 /int e ven (int n) / 函数首部 / { if(n%2 == 0) / 判别奇偶数 / r eturn 1; / 偶数返回1 / else return 0; / 奇数返回0 /} 如何调用该函数?#incl ude int main( void ){ int x,sum=0 ; 。。。。 if (even (x)==1) sum=sum+x ; printf(“%d”, sum); return 0;}5. 函数原型声明函数类型 函数名(参数表);double cylinder (double r, double h);void pyr amid (int n);函数必须先定义后调用,将主调函数放在被调函数的后面,就像变量先定义后使用一样。如果自定义函数在主调函数的 后面,就需要在函数调用前,加上函数原型声明。函数声明:说明函数的类型和参数的情况,以保证程序编译时能判断对该函数的调用是否正确。只 写函数定义中的第1行(函数首部),并以分号结束。§5.1.3 函数调用小结: 在执行函数调用时,实参把值计算出来,拷 贝 给相应位置的形参;函数执行完后,通过 return( ),可返回一个结果值。 实参与形参 有多个实参时 形参的改变个数相同、类型一致 后面的先计算 不影响实参 变量的值 只能返回一 个结果, 类型与函数定义时一致 要调用函数,必须先要声明!5.1.4 函数程序设计例5- 3 输入精度e,使用格里高利公式求π的近似值,精确到最后一项的绝对值小于e。要求定义和调用函数 funpi(e) 求π的近似值。什 么做参数?例5-4 源程序/ 用格里高利公式计算π的近似值,精度为e /#include #include int main (void){ double e, pi; double funpi (double e); 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 (fa bs (item) >= e){ item = flag 1.0 / denominator; sum = sum + item; flag = -flag; denominator = de nominator + 2; } return sum 4;}Enter e: 0.0001pi = 3.1418例5-4 判断素数的函数例5-5 求100以内的全部素数,每行输出10个。素数就是只能被1和自身整除的正整数,1不 是素数,2是素数。 要求定义和调用函数prime (m)判断m是否为素数,当m为素数时返回1,否则返回0。 算法 描述:对2~100之间的每个数进行判断,若是素数,则输出该数。for(m = 2; m <= 100; m++) if ( m是素数) printf("%d ", m); prime(m) != 0例5-4 源程序#inclu de #include int main(void){ int count, m; int prime (int 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 prim e (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; }5.2 数字金字塔5.2.1 程序解析 5.2. 2 不返回结果的函数 5.2.3 结构化程序设计思想 例5-5 输出5之内的数字金字塔。/ 输出数字金字塔 /#incl ude int main (void){ void pyramid (int n); / 函数声明 / 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); / 每个数字的前后各有一个空格 / put char (''\n''); }} 12 2 3 3 3 4 4 4 45 5 5 5 5 for (i = 1; i <= n; i++) { 一行的处理} 一行中的空格处理; 一行中的数字显示} for (i = 1; i <= n; i++) { for (j = 1; j <= n-i; j++) printf(“ ”) ; 一行中的数字显示}5.2.2 不返回运算结果的函数定义void 函数名(参数表) / 函数 首部 /{ / 函数体 / 函数实现过程 return; / 可以省略return /}这类函数通常用于屏幕输出等表示不返回结果不能省略, 否则函数类型被默认定义为int5 .2.2 不返回运算结果的函数定义由于函数没有返回结果,函数调用不可能出现在表达式中,通常以独立的调用语句方式,如pyramid (5);不返回结果的函数,在定义、调用、参数传递、函数声明上,思路完全与以前相同,只是函数类型变为void。它适用把一些确定的、相 对独立的程序功能包装成函数。主函数通过调用不同的函数,体现算法步骤各步骤的实现由相应函数完成简化主函数结构,以体现结构化程序设计思 想。5.2.3 结构化程序设计思想结构化程序设计(Structured Programming)程序设计技术C语言是结构化程序设 计语言强调程序设计的风格和程序结构的规范化,提倡清晰的结构基本思路是将一个复杂问题的求解过程划分为若干阶段,每个阶段要处理的问题都 容易被理解和处理。按自顶向下的方法对问题进行分析、模块化设计和结构化编码等3个步骤。1. 自顶向下的分析方法把大的复杂的问题分解成 小问题后再解决面对一个复杂的问题,首先进行上层(整体)的分析,按组织或功能将问题分解成子问题如果子问题仍然十分复杂,再做进一步分解 ,直到处理对象相对简单,容易处理为止。当所有的子问题都得到了解决,整个问题也就解决了。每一次分解都是对上一层的问题进行细化和逐步求 精,最终形成一种类似树形的层次结构,来描述分析的结果。学生成绩统计程序的层次结构图 模块用函数实现2. 模块化设计将模块组织成良好 的层次系统顶层模块调用其下层模块以实现程序的完整功能每个下层模块再调用更下层的模块,从而完成程序的一个子功能,最下层的模块完成最具 体的功能。遵循模块独立性的原则,即模块之间的联系应尽量简单。模块用函数实现。一个模块只完成一个指定的功能。模块之间只通过带参数的函 数进行调用。3. 结构化编码主要原则 经模块化设计后,每一个模块都可以独立编码。编程时应选用顺序、选择和循环三种控制结构对变量、函 数、常量等命名时,要见名知意,有助于对变量含义或函数功能的理解。在程序中增加必要的注释,增加程序的可读性。要有良好的程序视觉组织, 利用缩进格式程序要清晰易懂,语句构造要简单直接程序有良好的交互性,输入有提示,输出有说明 5.3 复数运算 5.3.1 程序解 析 5.3.2 局部变量和全局变量5.3.3 变量生命周期和静态局部变量例5-6 分别输入2个复数的实部与虚部,用函数实现计 算2个复数之和与之积。 分析若2个复数分别为: c1=x1+y1i , c2=x2+y2i,则: c1+c2 = ( x1+x2) + (y1+y2)i c1c2 = (x1x2-y1y2) + (x1y2+x2y1)i #inclu de float result_real, result_imag; / 全局变量,用于存放函数结果 / int main(void) { float imag1, imag2, real1, real2; / 两个复数的实、 虚部变量 / / 函数声明 / void complex_prod(float real1, float imag1, float real2, float imag2); void complex_add(float real1, float i mag1, float real2, float imag2); 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(r eal1, imag1, real2, imag2); / 求复数之和 / printf("addition of compl ex is %f+%fi\n", result_real, result_imag); complex_prod(real1, i mag1, real2, imag2); / 求复数之积 / printf("product of complex is %f +%fi\n", result_real, result_imag); return 0;}运行结果Enter 1st compl ex number(real and imaginary):1 1Enter 2nd complex number(real an d imaginary):-2 3addition of complex is -1.000000+4.000000iproduc t of complex is -5.000000+1.000000ivoid complex_add(float real1, float imag1, float real2, float imag2){ result_real = real1 + re al2; result_imag = imag1 + imag2;}void complex_prod(float real1, float imag1, float real2, float imag2){ result_real = real1real2 - imag1imag2; result_imag = real1imag2 + real2imag1;}5.3.2 局部 变量和全局变量局部变量在函数内定义的变量(包括形参)作用范围:本函数内部定义在复合语句内的变量作用范围:复合语句内部全局变量 在函 数以外定义的变量,不从属于任一函数。作用范围:从定义处到源文件结束(包括各函数)例5-6 在复合语句中定义局部变量。#inclu de int main (void){ int a; a = 1; { / 复合语句开始 / int b = 2; b = a + b; a = a + b; } / 复合语句结束 / p rintf ("%d " , a ); return 0;}b:小范围内的临时变量 输出:4改成b会如何?例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变量作用范围示例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=?【例5-8】用函数实现财务现金记账。先输入操作类型(1收入,2支出,0结束),再输入操作金额,计算现金剩余额,经 多次操作直到输入操作为0结束。要求定义并调用函数,其中现金收入与现金支出分别用不同函数实现。分析:设变量cash保存现金余额值,由 于它被主函数、现金收入与现金支出函数共用,任意使用场合其意义与数值都是明确和唯一的,因此令其为全局变量。 #include io.h> float cash; / 定义全局变量,保存现金余额 /int main(void) { int choic e; float value; void income(float number), expend(float number); / 函数声明 / 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 inco me(float number){ cash = cash + number; / 改变全局变量cash /} / 定义计算现金支出函数 /void expend(float number){ cash = cash - number; / 改变全局变量cash /}Enter operate choice(0--end, 1--income, 2--ex pend):1Enter cash value:1000current cash:1000.000000Enter operate choice(0--end, 1--income, 2--expend):2Enter cash value:456curren t cash:544.000000Enter operate choice(0--end, 1--income, 2--expen d):05.3.2 局部变量和全局变量讨论全局变量比局部变量自由度大,更方便 ?引起注意对于规模较大的程序,过多使用全局变量会带来 副作用,导致各函数间出现相互干扰。如果整个程序是由多人合作开发,各人都按自己的想法使用全局变量,相互的干扰可能会更严重。 因此在变 量使用中,应尽量使用局部变量,从某个角度看使用似乎受到了限制,但从另一个角度看,它避免了不同函数间的相互干扰,提高了程序质量。 变 量生命周期变量从定义开始分配存储单元,到运行结束存储单元被回收的整个过程。自动变量(auto): 普通的局部变量int x, y ; ?? auto int x, y;char c1; ?? auto char c1;函数调用时,定义变量,分配存储单元。 函数调用结束,收回存储单元。全局变量:从程序执行开始,到程序的结束,存储单元始终保持。5.3.3 变量生命周期和静态局部变量C程 序存储分布示意图(例5-6)static 类型名 变量表作用范围:局部变量生命周期:全局变量静态局部变量【例5-9】输入正整数n,输出1!~n!的值。要求定义并调用含静态变量的函数fact_s(n)计算n!。#include 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!的计算。静态局部变量自动变量如果没有赋初值,其存储单元中将是随机值。就静态变量而言,如果定义时没有赋初值,系统将自动赋0。赋初值只在函数第一次调用时起作用,以后调用都按前一次调用保留的值使用。静态局部变量受变量作用范围限制,不能作用于其他函数(包括主函数)。 静态局部变量静态变量与全局变量均位于静态存储区他们的共同点是生命周期贯穿整个程序执行过程。区别在于作用范围不同,全局变量可作用于所有函数,静态变量只能用于所定义函数,而不能用于其他函数。本章小结系统介绍函数的定义和函数调用学习如何针对具体问题,确定需要使用函数的功能要求,再将功能用函数程序实现考虑如何调用定义好的函数,实现主调函数与被调函数的连接确定参数功能,掌握参数的传递实现函数与变量间的关系,不同形式的变量在函数中起的作用不同。 局部变量、全局变量和静态变量 |
|