第五章 函 数
教学目标 (1) 了解函数在程序设计中的作用; (2) 掌握函数的定义方法; (3) 掌握函数调用和参数传递的机制和方法; (4) 了解变量的作用域和生存期的概念。
基本内容 5.1概述 5.2 函数的定义和调用 5.3 函数间参数传递
5.1 概述 函数是构成程序的基本模块,每个函数完成一个计算或执行一个特定动作,具有相对独立的功能。 5.1 概述 函数是构成程序的基本模块,每个函数完成一个计算或执行一个特定动作,具有相对独立的功能。 通过函数,可以把一个复杂任务分解成为若干个易于解决的小任务。充分体现结构化程序设计由粗到精,逐步细化的设计思想。 C/C++提供三种类型的函数: 主函数main( ) 标准库函数 用户自定义函数
函数调用层次关系 main()函数是程序执行的入口,它可以调用其他函数。而其他一般函数既可以调用也可以被调用。 main ( ) fun2( ) fun1( ) fun3( ) fun1_1( ) fun2_1( ) fun2_2( ) main()函数是程序执行的入口,它可以调用其他函数。而其他一般函数既可以调用也可以被调用。
5.2 函数的定义和调用 5.2.1 函数的定义 5.2.2 函数的调用 5.2.3 函数声明
5.2.1 函数的定义 函数必须先定义,后使用。 定义函数的一般格式为: 函数体 函数类型 函数名(形式参数表) { 说明语句; 执行语句; 函数类型 函数名(形式参数表) { 说明语句; 执行语句; return 表达式; } 函数体
函数类型 函数类型:即调用该函数后所得到的函数值的类型,它由函数体内部的return语句提供。 如果某一函数确没有返回值,则使用说明符void。 例如:主函数的另一种形式 void main() { … … } 注意:这时函数中不能出现有返回值的return语句。
参数说明 形式参数表 (形参 )表示将从调用函数中接收哪些类型的数据 参数说明格式为: 变量类型 形参1,变量类型 形参2,…,变量类型 形参n 例: int array[], int count, doulbe distance
函数体 函数体本身是一个分程序,由语句和其他分程序组成。 语句分为说明语句和执行语句两类。 对某具体变量来说,应先说明,后使用。
5.2.2 函数的调用 函数要先定义,后调用。 调用函数时要考虑到函数本身的参数: 实参与形参必须一一对应: “虚实结合” 调用标准库函数时,要包含相应的头文件 输入/输出函数 iostream 字符串函数 cstring 常用数学函数 cmath 调用自定义函数时,要定义相应的实参,并给这些实参赋值。 函数名 ( 实参列表 ) 实参与形参必须一一对应: “虚实结合” “类型一致、位置一致、个数一致”
例5-1 求阶乘n! 算法分析: n!= n×(n-1)×…×3×2×1, 且0!=1 计算公式为: 函数的计算结果要返回主调函数,故设一个变量result n的阶乘结果可由一重循环来求得
#include <iostream> using namespace std; int fac(int n) { int result = 1; if(n == 0) return 1; while (n>1) result = result * n; n--; } return result; int main() { int m, r; cout << "请输入一个正整数,以计算其阶乘:"; cin >> m; r = fac(m) ; cout << m << "! = " << r << endl; return 0; main( )函数 调用 fac(3) 函数 fac(3) 主程序后续语句 return 6
函数间的信息交换 调用函数和被调用函数之间的信息交换是通过参数的结合和return语句来实现的。 数据流程是: 在调用函数中,先给实参赋值 通过函数调用,将数据从调用函数带到被调用函数 形参带值后,被调用函数即可进行相应的数据处理 如果有返回值,通过return语句带回到调用函数
5.2.3 函数声明 函数(原型)声明 函数声明的一般格式为: 将某函数的定义放在调用它的函数之后时,必须在被调用到之前对该函数先做说明 函数类型 函数名 ( 形式参数表 );
例 5-2:函数声明的使用——绝对值函数 运行结果: |m| = 5 #include <iostream> using namespace std; int abs(int x); // 函数声明 int main() // 主函数 { int m = -5; cout << "|m| = " << abs(m) << endl; return 0; } int abs(int x) // 函数定义 return x>=0?x:-x; // 使用问号表达式直接计算并返回结果 运行结果: |m| = 5
5.3 函数间的参数传递 形参:在参数表中声明的参数(变量)叫做函数的形式参数。 实参:在调用函数时,一般须为每一个形参给出其实际数据,即实际参数。 实参与形参有3种结合方式: 值调用、引用调用和地址调用。
值调用 值调用 优点: 缺点: 在调用时仅将实参的值赋给形参 在函数中对形参的任何修改不会影响到实参的值。 减少了调用函数与被调用函数之间的数据依赖,增强了函数自身的独立性。 缺点: 被调用函数向调用函数传递的数据仅有一个返回值,有时显得不够用。
Before exchange:a=1,b=2 After exchange:a=1,b=2 //例 5-3:交换两个变量的值(值调用) #include <iostream> using namespace std; void swap(int x, int y) { int tmp; tmp = x; x = y; y = tmp; } // 测试函数 swap() 用的主函数 int main( ) { int a = 1, b = 2; cout << "Before exchange:a= " << a << ",b= " << b << endl; swap(a, b); cout << "After exchange:a= " << a << ",b= " << b << endl; return 0; 运行结果:(不成功) Before exchange:a=1,b=2 After exchange:a=1,b=2
5.4 变量的作用域和生存期 5.4.1 变量的作用域 5.4.2 变量的生存期
5.4.1 变量的作用域 根据作用域的不同,可将程序中的变量分为局部变量和全局变量 局部变量是在函数或分程序中说明的变量,只能在本函数或分程序的范围内使用。 全局变量说明于所有函数之外,可以为本源程序文件中位于该全局变量说明之后的所有函数共同使用。 全局变量可以在各个函数之间建立数据传输通道,但滥用会破坏程序的模块化结构。 如出现同名变量,遵循“地方保护主义”原则。
#include <iostream> using namespace std; int x; // 声明全局变量 int f1() // 在函数f1()使用全局变量x { return (x+5)*(x+5); } int f2(int y) int x =y+5; // 函数f2()中声明了一个名为x的局部变量 return x*x; int main() { x = 3; // 在主函数中为全局变量x赋值 cout<<"调用函数f1()的结果:"<<f1()<<endl; cout<<"调用函数f2()的结果:"<<f2(2)<<endl; cout<<"x = "<<x<<endl; return 0; 运行结果: 调用函数f1()的结果:64 调用函数f2()的结果:49 x = 3
5.4.2 变量的生存期 根据生存期的不同,可将程序中的变量分为自动变量和静态变量 自动变量(auto)的生存期是说明了自动变量的函数或分程序。它对存储空间的利用是动态的。其初值在每次为自动变量分配存储后都要重新设置。 静态变量(static)的生存期就是整个程序的运行期。在程序开始运行前就为其分配相应的存储空间,在程序的整个运行期间一直占用,直到结束。
例5-5:自动变量的使用 #include <iostream> using namespace std; int func() { int count = 0; // 定义自动局部变量并初始化 count++; return count; } int main() { // 分别调用10次func( ) 函数 for(int i=0; i<10; i++) cout<<func()<<"\t"; cout<<endl; return 0; 运行结果: 1 1 1 1 1 1 1 1 1 1
例5-6:静态局部变量的使用 #include <iostream> using namespace std; int func() { static int count = 0; // 声明静态局部变量并初始化 count++; return count; // 本函数每执行一次,变量值加1 } int main() { // 分别调用10次func( ) 函数 for(int i=0;i<10;i++) cout<<func()<<"\t"; cout<<endl; return 0; 运行结果: 1 2 3 4 5 6 7 8 9 10
扩展阅读 5.5 函数的嵌套和递归调用 5.6带默认形参值的函数 5.7函数重载 5.8函数模板
5.5 函数的嵌套和递归调用 5.5.1 函数的嵌套 5.5.2 递归调用
5.5.1 函数的嵌套
例5-7 求两个整数的最小公倍数 算法分析 两个正整数的最小公倍数可以由下面公式计算出来: 两个正整数的最小公倍数=两数乘积÷两数的最大公约数 求最大公约数的辗转相除法: 步骤1: 如果p < q,交换p 和q; 步骤2: 求p/q的余数r; 步骤3: 如果r ==0,则 q 就是所求的结果; 否则,反复做如下工作:令p = q,q = r,重新计算 p 和q 的余数r,直到r ==0为止,则 q 就是原来的两正整数的最大公约数。
#include <iostream> using namespace std; int gcd(int p, int q) //求最大公约数 { int r; if(p<q) { r = p; p = q; q = r;} r = p%q; while(r != 0) { p = q; q = r; r = p%q; } return q; int lcm(int p, int q) //求最小公倍数 { return p*q/gcd(p, q); int main() { int p, q; cout<< "请输入两个整数:" ; cin >> p >> q; cout << "两个整数的最小公倍数是:" << lcm(p, q) << endl; return 0;
5.5.2 递归调用 当定义一个函数时,如果其函数体内有调用其自身的语句,则该函数称为递归函数。 一个直接或间接地调用了自身的算法就是递归算法。
例5-8 采用递归算法求n! 算法: 由阶乘的概念可以写出其递归定义: 0! = 1 n! = n*(n-1)!
#include <iostream> using namespace std; int fac(int n) // 函数fac():求阶乘的递归函数 { if(n==0) // 0!=1 return 1; else return n*fac(n-1); // n!=n*(n-1)! } int main() int n; cout << "请输入一个整数n,以计算n!:"; cin >> n; cout << n << "! = " << fac(n) << endl; return 0;
递归函数的调用顺序 当n取5时,产生下列调用序列: 回代结果: fac(5)= 5 *fac(4) fac(5)=5*4*3*2*1
递归基本定理 一个问题是否可以转换为递归来处理必须满足以下条件: (1) 必须包含一种或多种非递归的基本形式; (1) 必须包含一种或多种非递归的基本形式; (2) 一般形式必须能最终转换到基本形式; (3) 由基本形式来结束递归。
5.6 带默认形参值的函数 在函数定义时,可在形参列表中预先给一些默认值。 当调用这种函数时,若为相应参数给出实参,则用实参初始化对应形参;如果没给出,则自动采用预先给定的默认形参值。 注意: 要保证所有的缺省参数均放在参数表的最后,即默认参数值必须按从右向左的顺序声明。如: void func(int x, int n1 = 1, int n2 = 2);
例 5-9:带默认形参值的函数 #include <iostream> using namespace std; void func(int x = 1, int y = 2, int z = 3) // 带默认形参值的函数 { cout << "参数x:"<<x<<endl; cout << "参数y:"<<y<<endl; cout << "参数z:"<<z<<endl; } int main() { cout << "未给出实参值的情况:"<<endl; func(); cout << "给出1个实参值的情况:"<<endl; func(10); cout << "给出2个实参值的情况:"<<endl; func(20, 30); cout << "给出3个实参值的情况:"<<endl; func(20, 30, 40); return 0;
5.7函数重载 函数重载: 注意: 一组参数和返回值不同的函数共用一个函数名。 重载函数之间必须在参数的类型或个数方面有所不同。只有返回值类型不同的几个函数不能重载。
#include <iostream> using namespace std; int max(int x, int y) //求两个整型数的最大值 { return (x>y)?x:y; } double max(double x, double y) //求两个双精度数的最大值 char max(char x, char y) //求两个字符的最大值 int main() { int m1=5, m2=3; double d1=12.5, d2=6.4; char c1='a', c2='b'; cout<<m1<<"和"<<m2<<"中的最大值是: "<<max(m1, m2)<< endl; cout<<d1<<"和"<<d2<<"中的最大值是: "<<max(d1, d2)<< endl; cout<<c1<<"和"<<c2<<"中的最大值是: "<<max(c1, c2)<< endl; return 0;
5.8 函数模板 函数模板用于定义一个抽象通用的函数,从而能够对不同类型的数据进行相同的处理。 定义一个函数模板的格式为: template < typename 类型参数> 函数类型 函数名 ( 形式参数表 ) { 函数体 }
例 5-11:求最大值的函数模板 #include <iostream> using namespace std; template <typename T> T tmax(T a, T b) //求两个数据最大值的函数模板 { return a>b?a:b; } int main() { int m1=5, m2=3; double d1=12.5, d2=6.4; char c1='a', c2='b'; cout<<m1<<"和"<<m2<<"中的最大值是: "<<tmax(m1, m2)<< endl; cout<<d1<<"和"<<d2<<"中的最大值是: "<<tmax(d1, d2)<< endl; cout<<c1<<"和"<<c2<<"中的最大值是: "<<tmax(c1, c2)<< endl; return 0;
上机指导 5. 9 Visual C++的跟踪调试功能
Debug工具栏 (1)Restart(快捷键:Ctrl+Shift+F5) (2)Stop Debugging(快捷键:Shift+F5) (3)Break Execution: (4)Apply Code Changes(快捷键:Alt+F10) (5)Show Next Statement(快捷键:Alt+Num *) (6)Step Into(快捷键:F11) (7)Step Over(快捷键F10) (8)Step Out(快捷键:Shift+F11) (9)Run To Cursor(快捷键:Ctrl+F10)
应用举例 例5-12 显示出杨辉三角形的前10行 算法讨论: 杨辉三角形的每一项也可以用二项式 的展开式的系数来表示。 杨辉三角形的通项公式 例5-12 显示出杨辉三角形的前10行 算法讨论: 杨辉三角形的每一项也可以用二项式 的展开式的系数来表示。 杨辉三角形的通项公式 求解杨辉三角形的步骤为: 编写出阶乘函数 在主函数中调用阶乘函数来构造杨辉三角形的通项公式,循环显示输出
#include <iostream> #include <cmath> using namespace std; int fac(int n) //求n!的函数 { int result=1; while(n>1) result =result*n; n=n-1; } return result; int main() for(int n=0; n<10; n=n+1) for(int m=0; m<=n; m=m+1) cout<<fac(n)/(fac(m)*fac(n-m))<<"\t"; cout<<endl; return 0;
例 5-13:找出100-200之间的所有素数 #include <iostream> using namespace std; bool isprime(int a) { for(int i=2; i<=a/2; i++) if(a%i == 0) return false; } return true; int main() for(int m=100; m<=200; m++) if(isprime(m)) cout<<m<<'\t'; return 0;
例 5-14 根据用户输入的宽和高的数目,显示一个由“*”号组成的矩形 #include <iostream> using namespace std; void rectangle(int w,int h) { int i, j; for(j=0; j<w; j++) cout<<"*"; // 显示矩形上面的边 cout<<endl; for(i=1; i<h-1; i++) // 显示矩形侧边 cout<<"*"; for(j=1; j<w-1; j++) cout<<" "; cout<<"*"<<endl; } for(j=0; j<w; j++) cout<<"*"; // 显示矩形下面的边 int main() int w, h; cout<<"请输入矩形的宽和高(要求为正整数值,中间使用空格隔开):"; cin>>w>>h; rectangle(w, h); return 0;
例 5-15:当用户输入一个整数和数字后,能够统计出该整数中包含这一数字的个数 #include <iostream> using namespace std; int count(int m,int n) { int sum=0; m = m>0?m:-m; // 若m为负整数,则转换为正整数形式 do if(m%10 == n)sum++; m = m/10; }while(m!=0); // 将整数逐位分解判断 return sum; } int main() int m,n; cout<<"请输入一个整数和一个数字:"; cin>>m>>n; cout<<"整数"<<m<<"中数字"<<n<<"的个数是"<<count(m, n)<<endl; return 0;
案例:金字塔图形 编写一个函数: void draw(int n) 可根据整数 n (0<n<14) ,输出由字母组成的金字塔图形。 请输入n值:6 A ABC ABCDE ABCDEFG ABCDEFGHI ABCDEFGHIJK 教学设问 1. 为什么n的值要小于14呢? 2. 无返回值函数如何调用? *更多案例见本书配套教材《C/C++语言程序设计案例教程 》罗建军等编著,清华大学出版社
问题分析 金字塔图形有以下特点: 输入的n表示要输出n行 每行都是从A开始,按字母顺序输出
//金字塔图形 #include <iostream> using namespace std; void draw(int n) { int i,j,m=0; char c; for(i=1;i<=n;i++) for(j=1;j<=n+1-i;j++) cout<<" "; for(c='A';c<='A'+m;c++) cout<<c; m=m+2; cout<<endl; } int main() int n; cout<<"请输入n的值:"; cin>>n; draw(n); return 0;
进一步思考 如果输出的金字塔图形都是由小写字母组成,如何修改程序? 如果只想输出金字塔图形的左半边,如何修改程序?
结 束 语 学好程序设计语言的唯一途径是 你的编程能力与你在计算机上投入的时间成 上机练习 正比