510 likes | 628 Views
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 程序解析-计算圆柱体积.
E N D
Chap5 函数 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】输入圆柱体的高h和半径r,求圆柱体积, volume=π*r2*h。要求定义和调用函数cylinder (r, h)计算圆柱体的体积。 定义函数cylinder()如下: double cylinder(double r, double h) { return 3.1415926 * r * r * h ; }
例5-1程序 /* 计算圆柱体积 */ #include <stdio.h> 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) /* 函数定义*/ { return 3.1415926 * r * r * h ; } 运行结果: Enterradius and height: 3.0 10 Volume = 282.743
5.1.2 函数的定义 • 函数是指完成一个特定工作的独立程序模块。 • 函数分类 • 从函数定义的角度: 自定义函数(用户定义)和系统库函数(系统定义) • 从主调与被调函数之间数据传送的角度: 有参函数和无参函数 • 从有无返回值的角度: 有返回值函数和无返回值函数 • main()也是一个函数,C程序由一个main()或多个 函数构成。
5.1.2 函数的定义 1. 函数定义的一般格式: 函数类型函数名(形式参数说明表) /*函数首部*/ { 函数体} /* 函数体 */ 函数类型:指函数返回值的类型,如:int float double char void等。缺省为int,无返回值的 函数,函数类型应使用 void。 函数名:函数的名称,即一个自定义标识符。 形参说明表:说明形式参数,格式为: 类型1 形参1, 类型2 形参2, ..., 类型n 形参n 函数体:用大括号括起来的若干语句,体现函数 的实现过程。
5.1.2 函数的定义 2. 有返回值的函数的定义 • 函数的返回值是通过函数中用return语句实现: return 表达式; 或 return (表达式); • return 语句的功能是结束本函数的执行,将表达式的 值返回给主调函数。 只能返回一个值。 • 如果表达式缺省,则无直接的返回值。 • 如果函数类型和return语句中表达式的值类型不一致, 则以函数类型为准。 • 函数终止执行有两种情况:一是遇到return语句或其他 终止函数执行的语句;二是执行到函数体的右大括号。
5.1.2 函数的定义 • 有返回值函数的函数类型应定义为要返回的运算 结果的值类型,一般不能是void。 • 有返回值函数中至少有一条,也可以有多条含有 返回值表达式的return语句。 例如,判断一个整数是否为素数的函数prime(): int prime(int m) { int i; if(m < 2) return 0; for(i = 2; i < m; i++) if(m%i == 0) return 0; return 1; }
5.1.2 函数的定义 3. 无返回值的函数的定义 • 无返回值函数的函数类型应定义为 void,表示无 返回值,不应缺省,因为缺省默认为 int。 • 无返回值函数中可以没有,也可以有不含返回值 表达式的return语句。 • 无返回值函数虽然不直接返回一个值,但它的作 用通常以屏幕输出等方式或其他途径体现。
5.1.3 函数的调用 1. 函数调用的形式 函数调用的一般形式为: 函数名(实参表) • 实参可以是常量、变量和表达式。 • 实参的类型,个数和顺序应与定义时的形参一致。 2. 函数调用的过程 当主调函数(如 main() 函数)执行到某个函数调用, 主调函数就暂停执行,将实参的值传递给形参并 转而执行该被调函数,该被调函数执行完后,将 返回主调函数,再从原先暂停的位置继续执行。
5.1.3 函数的调用 3. 参数传递 • 函数定义时的参数称为形式参数,简称形参。 double cylinder (double r, double h) { …… } • 函数调用时的参数称为实际参数,简称实参。 volume = cylinder (radius, height); • 形参:必须是变量,用于接受实参传递过来的值。 相同变量名的实参变量与形参是不同的变量,它 们分别只有效于各自所在的主调函数和被调函数。 • 实参:可以是常量、变量或表达式(包括有返回值 函数调用表达式)。
5.1.3 函数的调用 • 函数调用时,发生参数传递,即把实参的值复制 给对应的形参。 • 参数传递是一种单向的值的传递,因此,函数执 行中形参值的改变不会影响实参的值。 4. 函数结果返回 • 函数返回的两种情况 • 完成确定的运算,有一个运算结果返回给主调 函数。 • 完成指定工作,没有确定的运算结果需返回给 主调函数。
5.1.3 函数的调用 • 函数结果返回的形式: • return 表达式; • return (表达式); • 【例5-2】定义判断奇偶数的函数even (n),当n为 偶数时返回1,否则返回0。 int even (int n) { if(n%2 == 0) return 1; else return 0; }
5.1.3 函数的调用 5. 函数原型声明 • 调用函数必须遵循“先定义或声明后调用”的原则。 • 要调用库函数,就用#include命令把包含该库 函数原型声明的头文件包含在源程序前部。 • 函数定义在先调用在后,就不需函数声明。 • 函数定义在后调用在先,就必须先行函数声明。 函数声明的一般形式: 函数值类型 函数名(形参说明表); 其中的形参说明表可以与定义时的相同,也可以 缺省其中的形参名。
5.1.4 函数程序设计 【例5-3】输入精度e,使用格雷戈里公式求π的近 似值,精确到最后一项的绝对值小于e。要求定义 和调用函数 funpi(e) 求π的近似值。 分析: 与【例4-1】(使用格雷戈里公式求π的近似值)的 差别是:精度需输入;要定义和调用函数 funpi(e)。
例5-3程序 double funpi(double e) { int den = 1, f = 1; double item,sum=0; do { item = 1.0/den; sum = sum + f * item; f = -f; den = den + 2; } while(item >= e); return sum*4; } #include <stdio.h> int main (void) { double e, pi; double funpi(double); printf ("Enter e:"); scanf ("%lf", &e); pi = funpi (e); printf ("pi = %f\n", pi); return 0; } 运行结果: Enter e:0.0001 pi = 3.1414
5.1.4 函数程序设计 【例5-4】求100以内的全部素数,每行输出10个。 要求定义和调用函数prime(m)判断m是否为素数, 当m为素数时返回1,否则返回0。 分析: 与【例4-10】(求100以内的全部素数)的差别仅是 要求定义和调用函数prime()。 for(m = 2; m <= 100; m++) if (prime(m) != 0) printf("%d ", m);
例5-4程序 int prime (int m) {int i; if (m<2) return 0; for( i=2; i<m; i++) if (m%i == 0) return 0; return 1; } #include <stdio.h> int main(void) {int count, m, prime(int); count = 0; for(m=2; m<=100; m++) if(prime(m)) {printf("%5d", m); count ++; if(count%10 == 0) printf("\n"); } return 0; } 运行结果: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 57 71 73 79 83 89 97
5.2 数字金字塔 【例5-5】输出5行的数字金字塔。 5.2.1 程序解析 5.2.2 不返回结果的函数 5.2.3 结构化程序设计思想
5.2.1 程序解析 1 22 333 4444 55555 【例5-5】输出5行的数字金字塔。 分析: • 外循环:控制输出行数,行号从1开始。 • 内循环1:输出每行前导的空格,个数为: 总行数-行号。 • 内循环2:输出每行的数字,该数字及个数为行号。 • 每行最后须换行。
例5-5程序 #include <stdio.h> 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("%-2d", i); /* 每个数字宽2左对齐*/ printf("\n"); /* 换行 */ } } int main (void) { pyramid(5); /* 用实参5调用函数*/ return 0; }
5.2.2 不返回结果的函数 • 无返回值函数的函数类型应定义为 void,表示无 返回值,不应缺省,因为缺省默认为 int。 • 无返回值函数中可以没有,也可以有不含返回值 表达式的return语句。 • 无返回值函数虽然不直接返回一个值,但它的作 用通常以屏幕输出等方式或其他途径体现。 • 由于函数没有返回结果,函数调用不可能出现在 表达式中,通常以独立的调用语句方式,如 pyramid(5);
5.2.3 结构化程序设计思想 • 结构化程序设计 • 是一种程序设计技术 • C语言是结构化程序设计语言 • 结构化程序设计强调程序设计的风格和程序结构 的规范化,提倡清晰的结构。 • 它的基本思路是将一个复杂问题的求解过程划分为若 干阶段,每个阶段要处理的问题都容易被理解和处理。 • 按自顶向下的方法对问题进行分析、模块化设计和结 构化编码等3个步骤。
5.2.3 结构化程序设计思想 1. 自顶向下的分析方法 • 把大的复杂的问题分解成小问题后再解决。 • 面对一个复杂的问题,首先进行上层(整体)的分 析,按组织或功能将问题分解成子问题。 • 如果子问题仍然很复杂,再做进一步分解,直到处 理对象相对简单,容易处理为止。 • 当所有的子问题都得到了解决,整个问题也就解决 了。 • 每一次分解都是对上一层的问题进行细化和逐步 求精,最终形成一种类似树形的层次结构,来描 述分析的结果。
学生成绩统计程序 数据计算 数据查找 输出成绩 成绩输入 计算学生平均分 计算课程平均分 5.2.3 结构化程序设计思想 • 一个学生成绩统计程序的例子
5.2.3 结构化程序设计思想 2. 模块化设计 • 将模块组织成良好的层次系统 • 顶层模块调用其下层模块以实现程序的完整功能。 • 每个下层模块再调用更下层的模块,从而完成程序的一个子功能。 • 最下层的模块完成最具体的功能。 • 遵循模块独立性的原则,即模块之间的联系应尽量简单 • 模块用函数实现。 • 一个模块只完成一个指定的功能。 • 模块之间只通过带参数的函数进行调用。
5.2.3 结构化程序设计思想 3. 结构化编码 • 经模块化设计后,每一个模块都可以独立编码。 编程时应选用顺序、选择和循环三种控制结构 • 对变量、函数、常量等命名时,要见名知意,有助于对变量含义或函数功能的理解。 • 在程序中增加必要的注释,增加程序的可读性。 • 要有良好的程序视觉组织,利用缩进格式 • 程序要清晰易懂,语句构造要简单直接 • 程序有良好的交互性,输入有提示,输出有说明
5.3 复数运算 【例5-6】分别输入2个复数的实部与虚部,用函数 实现计算2个复数之和与之积。 5.3.1 程序解析 5.3.2 局部变量和全局变量 5.3.3 变量生命周期和静态局部变量
5.3.1 程序解析 【例5-6】分别输入2个复数的实部与虚部,用函数 实现计算2个复数之和与之积。 分析: 若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
例5-6程序 #include<stdio.h> float real, imag; /*全局变量,用于存放函数结果 */ int main(void) { float i1, i2, r1, r2; /* 两个复数的实、虚部变量 */ void complex_prod(float r1,float i1,float r2,float i2); void complex_add(float r1,float i1,float r2,float i2); printf("Enter 1st complex number(real and imaginary): "); scanf("%f%f", &r1, &i1); /* 输入第1个复数 */ printf("Enter 2nd complex number(real and imaginary): "); scanf("%f%f", &r2, &i2); /* 输入第2个复数 */ complex_add(r1, i1, r2, i2); /* 求复数之和 */ printf("addition of complex is %f+%fi\n",real,imag);
例5-6程序 complex_prod(r1, i1, r2, i2); /* 求复数之积 */ printf("product of complex is %f+%fi\n", real, imag); return 0; } void complex_add(float r1, float i1, float r2, float i2) { real = r1 + r2; imag = i1 + i2; } void complex_prod(float r1, float i1, float r2, float i2) { real = r1*r2 – i1*i2; imag = r1*i2 + r2*i1; } 运行结果: 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
例5-6程序 运行结果: 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 #include<stdio.h> 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 imag1, 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(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; }
例5-6程序 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; }
5.3.2 局部变量和全局变量 • 局部变量 • 在函数内定义的变量(包括形参) 作用范围:本函数内部 • 在复合语句内定义的变量 作用范围:复合语句内部 • 函数调用结束或复合语句执行完毕,为其局部 变量分配的内存便释放,但静态局部变量除外。 • 使用局部变量可以避免各函数间的变量相互干 扰,尤其是同名变量。
5.3.2 局部变量和全局变量 • 当函数的局部变量和复合语句的局部变量同名 时,则在复合语句的局部变量的作用范围内由 复合语句的局部变量起作用,函数的局部变量 被“屏蔽”,即不起作用。
作用范围 a bb 5.3.2 局部变量和全局变量 【示例】在复合语句中定义局部变量。 #include <stdio.h> int main (void) { inta, b = 2; a = 1; {a = a + b; int b = 10; b = a + b; a = a + b; printf ("a=%d,b=%d\n" , a, b); } printf ("a=%d,b=%d\n" , a, b); return 0; } 运行结果: a=16,b=13 a=16,b=2 /* a=1+2=3 */ /* b=10 */ /* b=3+10=13 */ /* a=3+13=16 */
5.3.2 局部变量和全局变量 • 全局变量 • 在函数外部定义的变量,不从属于任一函数。 • 全局变量的作用范围是从定义处起始到所在程序文件的结束。 • 全局变量和局部变量同名时,则在局部变量的作用范围内由局部变量起作用,全局变量被“屏 蔽”,即不起作用。 【例5-7】全局变量定义。
xbax 例5-7程序 x : 0 a : 1 x : 4 b : 2 1 7 4 #include "stdio.h" int x; int fun( ) { int x = 4; return x; } int main(void) { int a = 1; x = a; a = fun( ); { int b = 2; b = a + b; x = x + b; } printf("%d %d" , a, x); return 0; } 6 运行结果: 47
5.3.2 局部变量和全局变量 【例5-8】用函数实现财务现金记账。先输入操作类 型(1收入,2支出,0结束),再输入操作金额,计 算现金剩余额,经多次操作直到输入操作为0结束。 要求定义并调用函数,其中现金收入与现金支出分 别用不同函数实现。 分析: 设变量cash保存现金余额值,由于它被主函数、现金 收入与现金支出函数共用,任意使用场合其意义与数值 都是明确和唯一的,因此令其为全局变量。
例5-8程序 #include<stdio.h> float cash;/* 定义全局变量,保存现金余额 */ void income(float number) /* 定义计算现金收入函数 */ { cash = cash + number; } /* 改变全局变量cash */ void expend(float number) /* 定义计算现金支出函数 */ { cash = cash - number; } /* 改变全局变量cash */ int main(void) { int choice; 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); /* 输入操作类型 */
例5-8程序 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; } 运行结果: Enter operate choice(0--end, 1--income, 2--expend):1 Enter cash value:1000 current cash:1000.000000 Enter operate choice(0--end, 1--income, 2--expend):2 Enter cash value:456 current cash:544.000000 Enter operate choice(0--end, 1--income, 2--expend):0
5.3.2 局部变量和全局变量 • 讨论 • 全局变量比局部变量自由度大,更方便 ? • 引起注意 • 对于规模较大的程序,过多使用全局变量会带来副 作用,导致各函数间出现相互干扰。如果整个程序 是由多人合作开发,各人都按自己的想法使用全局 变量,相互的干扰可能会更严重。 • 因此在变量使用中,应尽量使用局部变量,从某个 角度看使用似乎受到了限制,但从另一个角度看, 它避免了不同函数间的相互干扰,提高了程序质量。
5.3.3 变量生存周期和静态局部变量 • 变量生存周期 变量从定义开始分配存储单元,到运行结束存储单元被回收,整个过程称为变量的生存周期。 • 变量的存储类别 • 动态存储:程序运行期间按运行的需要动态地分配和释放存储空间。此类局部变量用 auto关键字定义(一般都缺省),称自动局部变量。 • 静态存储:程序运行期间分配固定的存储空间。此类局部变量用static关键字定义,称静态局部变量,全局变量默认为静态的。
程序代码区 静态存储区 (全局变量,静态局部变量) 动态存储区 (自动变量等) 5.3.2 变量生存周期和静态局部变量 • 变量的存储类别与其生存周期相关 • 静态存储的变量的生存周期是到程序运行结束。 • 动态存储的变量的生存周期是到函数到其所在函数体或复合语句结束。 • C程序存储分布:
5.3.2 变量生存周期和静态局部变量 • 自动变量 • 局部变量在定义时,如果没有指定存储类别,或使用了auto说明符,则都被认为其具有自动类别,即为自动变量。例如下面的x和y变量: int x, y; auto int x, y; int auto x, y; • 自动变量一定是局部变量。 • 自动变量在动态存储区分配存储单元 • 自动变量的生存周期从定义处开始,到其所在函数体或复合语句结束为止。
5.3.2 变量生命周期和静态局部变量 • 静态局部变量 • 定义形式 static 类型名 变量表; • 生存周期延长到程序运行结束,类似全局变量。 但并不影响其局部变量的作用范围。 • 与全局变量相似,在静态存储区分配存储单元, 定义时的初始化(若未赋初值则赋0)只在第一 次调用函数时进行,以后若反复调用函数是保 留上次调用函数后的值。 • 示例:写出下列程序的输出结果。
静态变量a 输出 fun调用 示例程序 #include <stdio.h> void fun (int k); int main (void) { int k; for (k = 1; k <= 3; k++) fun (k); return 0; } void fun(int k) { static int a; printf ("%d, ", a); a += k ; }
5.3.2 变量生命周期和静态局部变量 【例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; f = f * n; return(f); } 运行结果: Input n: 7 1!=1 2!=2 3!=6 4!=24 5!=120 6!=720
本章小结 • 系统介绍函数的定义和函数调用 • 学习如何针对具体问题,确定需要使用函数的 功能要求,再将功能用函数程序实现 • 考虑如何调用定义好的函数,实现主调函数与 被调函数的连接 • 确定参数功能,掌握参数的传递实现 • 函数与变量间的关系,不同形式的变量在函数中 起的作用不同。 • 局部变量、全局变量和静态变量