640 likes | 778 Views
计算机程序设计. 第五章 函数. 计算机学院 613 教研室 唐晋韬. 本章内容. 模块化程序设计 预定义函数 自定义函数(原型、定义、调用) 函数重载 存储类别和作用域 递归函数 利用函数进行结构化程序设计. 5.1 模块化程序设计. …… …… 求 a 、 b 、 c 中最大者 将其值赋值给 m ……. …… …… m=max(a, b, c); ……. int max( int a…) { int x=(a>b)?a:b; x =(x>c)?x:c; return x; }.
E N D
计算机程序设计 第五章 函数 计算机学院613教研室 唐晋韬
本章内容 计算机程序设计 2014 模块化程序设计 预定义函数 自定义函数(原型、定义、调用) 函数重载 存储类别和作用域 递归函数 利用函数进行结构化程序设计
5.1 模块化程序设计 …… …… 求a、b、c中最大者 将其值赋值给m …… …… …… m=max(a, b, c); …… int max(int a…) { int x=(a>b)?a:b; x =(x>c)?x:c; return x; } 计算机程序设计 2014 • 结构化程序设计方法将程序控制分解为顺序、选择、循环结构 • 都放在一个main函数中可以吗? • 将其中相对独立的功能提取出来:函数 • 用一条语句替代该模块:函数调用
认识函数 参数 结果 计算方法 f 参数 返回值 数学中的函数 一般表达: 计算机的计算 计算机程序设计 2014
5.1 模块化程序设计 计算机程序设计 2014 • 函数是C++程序构成的基础 • 任何C++程序至少包含一个函数——main()函数 • 函数也是类的方法的实现手段(面向对象) • 函数的作用 • 实现系统中按功能分解的各小任务 • 实现类对象的方法
5.1 模块化程序设计 函数一般用来完成一个相对独立的功能 main 函数 fun1 函数 fun2 函数 int main() { … fun1(); … return 0; } void fun1() { … int x =fun2(); … } int fun2() { int z; … return z; } 计算机程序设计 2014
5.1 模块化程序设计 计算机程序设计 2014 • 两类函数 • 预定义函数: C++提供了丰富的库函数,程序员可以在自己的程序中直接使用 • 用户自定义函数:用户根据需要编写的函数 • 一个C++程序可包含多个文件,一个文件可包含多个函数 • 所有函数是并列在一个层次的 • 所有函数通过main函数直接或间接调用
5.2 预定义函数的使用 计算机程序设计 2014 • C++ 中的预定义库 • C++ 中提供了丰富的预定义函数,称为“库函数” • 这些函数的声明都放在相应的头文件中 • 函数的声明包含如下部分 • 函数的名字 • 函数的参数数目及类型 • 函数返回值的类型
5.2 预定义函数的使用 计算机程序设计 2014 • 要使用预定义函数,必须先包括相应的头文件 • 如:数学运算函数的声明包含在math.h中,要使用这些函数,程序中必须加上 #include <math.h> …… m = sqrt(900.0);
5.3 函数定义与函数原型 计算机程序设计 2014 • 函数的优点 • 分而治之 • 有利于程序开发 • 避免代码重复 • 软件重用 • 重用已有的函数来构造新的程序 • 抽象-隐蔽内部的细节
5.3 函数定义与函数原型 <返回值类型><函数名>(<参数列表>) <函数体> 如果不写返回值类型? 默认为int 类型要一致 计算机程序设计 2014 • 函数定义格式 • 返回值类型:对函数返回值进行要求 • void - 函数没有返回值 int max ( int x, int y) { if ( x > y ) return x; return y; }
函数定义需注意的问题 计算机程序设计 2014 • 函数体内不能定义另外一个函数 • 所有的函数的定义都是并列在一个层次的 • 关于 return 语句 • 函数执行时,只要遇到 return语句,就会返回 • return 语句后面的<表达式>的值即为函数的返回值 • 函数返回值的类型必须要与<表达式>的类型匹配 • 所有的控制分支都要有 return 语句
函数定义需要注意的问题 void somefunc (int a, int b) { int c=(a+b)/2; if (c>=2) return c; } 计算机程序设计 2014 • 下面的函数定义存在什么问题? • 注意观察此时 VC 的编译信息 • 需要返回类型与实际返回类型不匹配 • 不是所有的控制分支都有返回值
函数定义需要的问题 char somefunc (int a, int b) { int c=(a+b)/2; if (c>=2) somefunc=c; else somefunc=-c; } 计算机程序设计 2014 • 函数定义中出现的标识符 • 函数中的形式参数都是局部变量 • “函数名”不是变量,不能够被赋值 • 指出如下函数定义中的问题
函数定义需要注意的问题 intsomefunc (inta) { intc; return c+a; } 计算机程序设计 2014 • 局部变量初始化问题 • 对于函数中新声明的局部变量,除了遵循“先声明、后使用”的原则外,还应遵循“使用前先初始化”的原则 • 对于任何一个变量,第一次出现在表达式右边出现之前,其值或由被用户输入,或者由程序赋值 • 观察下面函数的执行结果
例1:因子和 intsumOfFactors(int m) { int sum=0; cout << "The factors are :"; for (inti=1; i<=m; i++) if (m%i==0) { sum = sum+i; cout << i << " ; "; } cout << endl; return sum; } 现在,交换main函数和sumOfFactors函数的位置 • 设计一个函数,求一个正整数的所有因子之和。 • 如12的因子有1、2、3、4、6,则该函数返回16。 计算机程序设计 2014
5.3 函数定义与函数原型 数据输入 形式参数 数据处理 功能:函数名 返回值 数据输出 计算机程序设计 2014 • 函数原型 • 告诉编译器会有一个什么样的函数:名字叫什么,如果调用,需要什么参数,执行完后会给你一个什么结果 • 先声明后使用 int max ( int x, int y);
5.3 函数定义与函数原型 <返回值类型> <函数名>(<参数列表>); 计算机程序设计 2014 • 函数原型的构成 • 只需将“函数定义”中的<函数体>部分换为一个分号即可 • 注意:参数列表中形参的名字可以省略 • 函数原型的程序设计风格 • 用以声明函数,以符合“先声明、后使用”的原则 • 习惯上,一般将主函数定义放在前,被调用函数放在后,并将其原型放在主函数之前
5.4 函数调用 <函数名>(<实在参数列表>); 计算机程序设计 2014 • 函数调用的概念 • C++ 程序中,除了 main() 函数之外,任何一个函数都是通过直接或者被主函数调用执行 • 函数调用的形式 • 函数调用的步骤 • 传递参数 • 执行被调函数体 • 返回结果给调用者
5.4 函数调用 max (int , int); int max (3,5); max(3,5); int max (int , int); 计算机程序设计 2014 • 函数调用时的注意事项 • 实在参数与对应的形式参数类型要匹配、个数相同 • 对于无形参函数,调用该函数时圆括号不能少 • 练习 • 下面哪些调用是合法的?
5.4 函数调用 m = max(3, 5); int max ( int x, int y) { return (x>y)?x:y; } 5 计算机程序设计 2014 • 调用函数时一般要传递参数 • 传值和传引用 • 传值 • 把实在参数的值传递给函数的形式参数 • 函数的执行对实在参数没有影响 • 函数不需要修改实在参数时,用传值方式 • 避免意外改变
例子2:数据交换(传值) 计算机程序设计 2014 • 设计一个函数swap,实现两个变量值的交换 • void swap(int x, int y); • 传给它两个值,能实现交换吗?
例子2:参数传递(传值) int main() { int a=3, b=5; swap(a,b); cout<<a<<endl; cout<<b<<endl; return 0; } void swap(int x, int y) { int z = x; x = y; y = z; } 计算机程序设计 2014
例子2:参数传递(传值) swap(a, b); … void swap(int x, int y) { int z = x; x = y; y = z; } 3 5 a 3 x 5 b 5 y 3 z 3 计算机程序设计 2014 调用发生后的具体过程(传值)
参数传递 void swap(int &x, int &y) { int z = x; x = y; y = z; } 计算机程序设计 2014 • 引用型参数 • 为使被调函数访问调用它的实参,C++ 还提供了另外一种类型的参数 —— 引用型参数
5.4 函数调用 计算机程序设计 2014 • 传引用 • 形式参数采用引用参数 • 形参和实参共享存储空间——实参的存储空间 • 函数的执行可以改变实在参数——通过修改形式参数 • 要求实在参数有“左值性质”——实参必须有存储空间
参数传递(传引用) void swap(int &x, int &y) { int z = x; x = y; y = z; } swap(a, b); … a 5 3 x b 5 y 3 z 3 计算机程序设计 2014 调用发生后的具体过程(传引用)
5.4 函数调用 • 需要返回多个值怎么办? • 哥德巴赫猜想的验证:所有大于2的偶数都可以分解为两个素数之和。 • 函数:?goldbach(n),需要返回一个布尔值表示对于偶数n该猜想是否成立,但也需要返回两个素数的值,怎么办? 计算机程序设计 2014
5.4 函数调用 boolgoldbach(intevenNum, int & result1, int &result2) { for(inti=1;i<evenNum/2;i++){ if(isPrime(i) && isPrime(evenNum-i)){ .... } } } 计算机程序设计 2014 • 传引用的一个用处 • 在某些时候,可以引用型参数替代返回值 • 当函数有多个返回值时,可以使用引用型参数
5.4 函数调用 计算机程序设计 2014 • 默认参数 • 可以为形式参数提供默认值 • 函数调用时提供了实在参数则使用实在参数 • 没有则使用默认参数
5.4.3 函数的默认参数 intcubeVolume(int , intwid=1, inthei=1); Why? 计算机程序设计 2014 • 注意事项 • 默认参数应在函数名第一次出现时指定 • 函数原型,或者函数定义(没有函数原型时) 如在函数声明时使用默认参数 • 默认参数一定是参数列表中最右边的参数 • 个数可为1个、多个或全部 • 但一定是最右边的连续多个
. 默认参数举例 intcubeVolume(intlen=1 , intwid=1, inthei); intcubeVolume(int , intwid=1, inthei=1); cubeVolume(2, 3, 4); // 24,正常调用 cubeVolume(2, 3); //6,缺省最后一个参数 cubeVolume(2); //2,缺省最后两个参数 计算机程序设计 2014 • 例5-4:使用默认参数计算长方体体积 • 下面函数调用都是正确的
实践练习(Part 1) 计算机程序设计 2014 • 写一个函数将以时、分、秒格式的耗时数转换为以秒为单位的耗时数,并在main函数中调用它。 • 比如:1小时15分20秒的耗时数的转换结果是:4500秒 • 编写一个函数,判断一个整数是否是素数。 • 然后依托该函数验证哥德巴赫猜想:任何一个大于等于6的偶数都可以表示成两个素数之和 • 输入任意大于6的偶数,如果可以分解,输出两个素数,否则输出提示字符
5.5 函数重载 int main(){ cout << abs(-2) << endl; cout << abs(2.54f) << endl; double d = -3.3E-8 cout << abs(d) << endl; } 计算机程序设计 2014 • C++允许定义多个同名函数表示类似的操作——函数重载 • 这些函数必须有不同的参数(参数的个数、类型或顺序不同) • 调用重载函数时,编译器通过检查实在参数的个数、类型和顺序来确定相应的被调用函数 int abs(inti); float abs(float f); long abs(long l); double abs(double d);
5.6 存储类别和作用域 计算机程序设计 2014 auto,register,extern,static 存储期:在内存中的存在期 作用域:在程序中可以引用该变量的区域 连接:变量在哪些源文件中能够被识别
5.6 存储类别和作用域 计算机程序设计 2014 • 自动存储 • 在定义的程序块中建立、存在和撤销 • 没有缺省值 • auto: 局部变量(包括形式参数)的缺省说明符 • auto double x, y; • register: 为变量分配高速寄存器 • 只能用于auto变量 • register int counter = 1;
5.6 存储类别和作用域 static int a; extern intoutVar; 计算机程序设计 2014 • 静态存储:变量在整个程序运行期间都存在 • 缺省值为0 • static: 可以用于函数中的局部变量 • 函数运行结束,变量的值依然存在 • 只能在定义变量的函数中访问该变量 • extern: 全局变量和函数名的缺省定义 • 在所有的函数中都能访问 • 多个文件使用同一个全局变量时,需要显式说明
5.6 存储类别和作用域 计算机程序设计 2014 • 四种作用域 • 文件作用域 • 函数作用域 • 块作用域 • 函数原型作用域
5.6 存储类别和作用域 #include <stdio.h> int x = 1; /* global variable */ int main() { ... } 计算机程序设计 2014 • 文件作用域 • 在函数外面定义的标识符,能够被从标识符说明到文件结束之间的所有函数引用 • 全局变量、函数定义、函数原型
5.6 存储类别和作用域 #include <stdio.h> int main() { ... goto L1; ... L1: printf(...); ... } 计算机程序设计 2014 • 函数作用域 • 只能在函数体中引用 • 标号是唯一具有函数作用域的标识符
5.6 存储类别和作用域 计算机程序设计 2014 • 块作用域 • 在程序块中声明的标识符具有块作用域 • 块作用域开始于声明处,结束于程序块的右花括号 • 函数的形式参数、函数的局部变量、在复合语句中声明的变量具有块作用域 • 如果外层块和内层块有相同名字的变量,外层说明会被内层说明屏蔽(最近嵌套原则)
5.6 存储类别和作用域 #include <stdio.h> int main() { int x = 5; printf("local x is %d\n", x ); { int x = 7; printf( "x in inner scope is %d\n", x); } printf("local x is %d\n", x ); return 0; } 计算机程序设计 2014 块作用域示例
5.6 存储类别和作用域 void a(int param1, int param2); void b(float param1); int main() { int pram1; ... } 计算机程序设计 2014 • 函数原型作用域 • 函数原型参数表中的参数名标识符具有函数原型作用域 • 函数原型中的参数名可以省略
课堂练习 计算机程序设计 2014 (数据处理)C语言将浮点数转换成整数时采用直接截断的方法,也就是说4.9转换成整数是4。编写函数round,将浮点数转换成整数,采用4舍5入的方式。并编写主函数测试它。 如果保留n(n>=0)位小数进行四舍五入呢?Round函数该怎么编写?
5.8 递归函数 计算机程序设计 2014 • 递归的定义 • 所谓“递归”,是指函数直接或者间接调用自身的行为 • 递归的作用 • 实际中有许多问题,其解决过程依赖于类似问题的解决,只不过后者规模更小 • 示例 • 从另一个角度看
5.8 递归函数 long factorial (long number) { if (number <=1) return 1; else return number *factorial(number-1); } 计算机程序设计 2014 • 通过解决简单的问题,达到解决整个问题的目的
factorial(number) number: 1 { ... return 1 ... } factorial(number) number: 2 { ... return (2*factorial(1)) ... } 1 factorial(number) number: 3 { ... return (3*factorial(2)) ... } 2 factorial(number) number: 4 { ... return (4*factorial(3)) ... } 6 24 factorial(number) number: 5 { ... return (5*factorial(4)) ... } 120 计算机程序设计 2014
5.8 递归函数(练习) 计算机程序设计 2014 已知“菲波那契”数列为0,1,1,2,3,5, 8...,定义如下 试用递归实现计算该数列第 n 项的函数
f( 3 ) return f( 2 ) + f( 1 ) return f( 1 ) f( 0 ) return 1 + return 1 return 0 5.8 递归函数(练习) intfibonacci(unsigned int n) { if (n < 2) returnn; else returnfibonacci(n-1)+fibonacci(n-2); } 计算机程序设计 2014 Fibonacci数列:0,1,1,2,3,5, 8…
5.8 递归函数 计算机程序设计 2014 • 递归函数设计的原则 • 分解问题结构:准确分解问题的递归结构,分清楚哪些是当前能够直接处理的,哪些是当前不能处理、但能分解为和原问题相似,但结构简单的问题 • 根据问题的结构特征规划递归算法:当前能直接解决的问题就设计相应的算法解决;不能直接解决的,同归递归调用算法自身来解决