计算机编程 信息管理与工程学院 2014年9月
目 录 概述 函数定义的一般形式 函数的参数和函数的值 函数的调用 函数的嵌套调用 函数的递归调用 数组作为函数参数 局部变量和全局变量 目 录 概述 函数定义的一般形式 函数的参数和函数的值 函数的调用 函数的嵌套调用 函数的递归调用 数组作为函数参数 局部变量和全局变量 变量的存储类型 内部函数和外部函数 如何运行一个多文件的程序
5.1 概述 一个C++程序可由一个主函数和若干个函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。 说明 例8.1 示意图 返回主菜单
说 明 (1)一个源文件由一个或多个函数组成。一个源程序文件是一个编译单位,即以源程序为单位进行编译,而不是以函数为单位进行编译。 说 明 (1)一个源文件由一个或多个函数组成。一个源程序文件是一个编译单位,即以源程序为单位进行编译,而不是以函数为单位进行编译。 (2)一个C程序由一个或多个源程序文件组成。对较大的程序 ,一般不希望全放在一个文件中,而将函数和其他内容(如预定义)分别放在若干个源文件中,再由若干源文件组成一个C程序。这样可以分别编写、分别编译,提高调试效率。一个源程序可以为多个C程序公用。 (3)C程序的执行从main函数开始,调用其他函数后流程返回到main函数,在main 函数中结束整个程序的运行。main函数是系统定义的。
如例8.1的printstar和print_message (4)所有函数都是平行的,即在定义函数时是相互独立的,一个函数并不从属于另一函数,即函数不能嵌套定义(这是和PASCAL不同的)。函数间可以互相调用,但不能调用main函数。 (5)从用户使用的角度看,函数有两种: 标准函数,即库函数。 用户自己定义的函数。 (6)从函数的形式看,函数分两类: 无参函数。在调用无参函数时,主调函数并将数据传送给被调用函数,一般用来执行指定的一组操作。无参函数可以带回或不带回函数值,但一般以不带回函数值得居多。 有参函数。在调用函数时,在主调函数和被调函数之间有数据传递。也就是说,主调函数可以将数据传给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。 系统自己定义的 如例8.1的printstar和print_message
main a b c d e f g h i
例8.1 简单的函数调用 调用printstar函数 main() {printstar(); print_message(); } {cout<< “ * * * * \n”; printf_message(); {cout<<“ How do you do! \n”; 调用printstar函数 调用print_message函数 调用printstar函数 程序运行后,结果显示: * * * * * * * * * * * * * * * * * How do you do! 调用printstar函数 调用print_message函数 运行程序
5.2 函数定义的一般形式 无参函数的定义形式 类型标识符 函数名() {声明部分 语句 } 有参函数定义的一般形式 类型标识符 函数名() {声明部分 语句 } 有参函数定义的一般形式 类型标识符 函数名(形式参数表列) 例如: int max(int x,int y) {int z; z=x>y?x:y; return(z);}
例如: dummy() {} 返回主菜单 可以有”空函数” 类型说明符 函数名() { } 对形参的声明的传统方式 类型说明符 函数名() { } 对形参的声明的传统方式 在老版本C语言中,对形参类型的声明是放在函数定义的第2行。 例如: int max(x,y) int x,y; {int z; z=x>y?x:y; return(z);} 返回主菜单
5.3 函数参数和函数的值 形式参数和实际参数 在定义函数时函数名后面括弧中的变量名称为 “形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为”实际参数”. 例8.2 说明 函数的返回值 通过函数调用使主调函数能得到一个确定的值,这就是函数的返回值。 返回主菜单 说明 例8.3
例8.2 调用函数时的数据传递 main() 输入: {int a,b,c; 7,8 cin>>a>>b; c=max(a,b); cout<<“Max is ”<< c; } max(int x, int y) {int z; z=x>y?x:y; return(z); 输入: 7,8 结果: Max is 8 运行程序
关于形参和实参的说明 (1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。 (2)实参可以是常量、变量或表达式。在调用时将实参的值赋给形参。 (3)在被定义的函数中,必须指定形参的类型。 (4)实参与形参的类型应相同或赋值兼容。 (5)C语言规定,实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元。 如: c=max(a,b) max(int x, int y) {int z; z=x>y?x:y; return(z);} 如:max(3,a+b)
调用时,a b 2 3 如: c=max(a,b) max(int x, int y) {int z; x=x+8;y=y+12; z=x>y?x:y; return(z);} x 2 3 y 调用结束后 a 2 b 3 15 x 10 y
说明 如: int max(float x, float y)(函数值为整形) 函数的值只能通过return语句返回主调函数。 return 语句的一般形式为: return 表达式; 或者为: return (表达式); 该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行,因此只能返回一个函数值。 函数值的类型和函数定义中函数的类型应保持一致。 如函数值的类型和return语句中表达式的值不一致,以函数类型为准。 如: int max(float x, float y)(函数值为整形) char letter(char c1,char c2)(函数值为字符型) 例8.3
为了明确表示“不带回值”,可以用“void”定义“无类型”(或称“空类型”)。 例如: Void printstar() {…} 如果被调用函数中没有return语句,并不带回一个确定的、用户所希望得到的函数值,但实际上,函数并不时不带回值,而只是不带回有用的值,带回的是一个不确定的值。 为了明确表示“不带回值”,可以用“void”定义“无类型”(或称“空类型”)。 例如: Void printstar() {…} 下面的用法是错误的 a=printstar();
例8.3返回值类型与函数类型不同 main() {float a,b; int c; cin>>a>>b; c=max(a,b); printf(“Max is %d”, c); } max(float x, float y) {float z; /* z为实型变量*/ z=x>y?x:y; return(z); 运行情况如下: 1.5,2.5 Max is 2 运行程序
5.4 函数的调用 调用无参函数,“实参列表”可以没有; 包括多个实参,各参数间用逗号隔开; 函数调用的一般形式 函数名(实参表列); 5.4 函数的调用 调用无参函数,“实参列表”可以没有; 包括多个实参,各参数间用逗号隔开; 函数调用的一般形式 函数名(实参表列); 函数调用的方式 1)函数语句 2)函数表达式 3)函数参数 对被调用函数的声明和函数原型 1)函数调用的条件 2 )函数原型 A.函数类型 函数名(参数类型1 ,参数类型2……); B.函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2 ……); 例8.4 如: c=2*max(a,b); 如: printstar(); 如: m=max(a,max(b,c)); 如: float add(float,float) 说明 返回主菜单
函数调用应具备的条件 被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。 如果使用库函数,一般还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。 如使用用户自己定义的函数,而且该函数与调用函数在同一文件中,一般应在主调函数中对被调用的函数作声明。 注意:定义和声明不是一回事。“定义”是对函数功能的确立,包括指定函数名,函数值类型、形参及类型等,是一个完整、独立的函数单位。 “声明”是把函数的名字、函数类型和形参的类型、个数以及顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。 如: #include <stdio.h>
说 明 如: 以前的C版本的函数声明方式不是采用函数原型,而只声明函数名和函数类型。 float add(); 说 明 如: float add(); 以前的C版本的函数声明方式不是采用函数原型,而只声明函数名和函数类型。 如在函数调用之前,没有对函数作说明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int型。 如果被调用函数的定义出现在主调用函数之前,可不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必对所调用的函数再作声明。
例 8.4 在main( )函数中调用例5-1的power函数,输出5的平方。 #include <iostream> using namespace std; double power (double x, int n); //函数声明;省略形参名亦可。 void main(void) { cout << "5 to the power 2 is " << power(5,2) << endl; //函数调用,实参的个数与形参个数相同,类型相符。 } 例 8.4 在main( )函数中调用例5-1的power函数,输出5的平方。 运行结果: 5 to the power 2 is 25 运行程序
5.5 函数的嵌套调用 1)C++语言的函数定义都是相互平行、独立的,也就是说在定义函数时,一个函数内部不能包含另一个函数。 5.5 函数的嵌套调用 1)C++语言的函数定义都是相互平行、独立的,也就是说在定义函数时,一个函数内部不能包含另一个函数。 2)C++语言不能嵌套定义, 但可以嵌套调用函数。 图形说明 例8.6 返回主菜单
a函数 b函数 main函数 调用a函数 调用b函数
例 8.6用弦截法求方程 f(x)=x3 -5x2 +16x -80=0根 方法如下: (1)取两个不同点x1、x2,如果 f(x1)和f(x2)符号相反, 则(x1,x2)区间内必有一个根。如果f(x1)与f(x2)同符号, 则应改变x1、x2,直到f(x1)与f(x2)异号为止。 (2)连接(x1,f(x1))和(x2,f(x2))两点,如图。 y f(x2) x1 x x2 x f(x1) f(x) x点的坐标可用下式求出: x=[x1• f(x2) – x2• f(x1) ] / [f(x2) - f(x1) ] 再从x求出f(x)
例 8.6用弦截法求方程 f(x)=x3 -5x2 +16x -80=0根。 (3)若f(x)与f(x1)同符号,则根必在(x,x2)区间内, 此时将x作为新的x1。如果f(x)与f(x2)同符号,则根必在 (x1,x)区间内,将x作为新的x2。 (4)重复步骤(2)和(3),直到︱f(x) ︱<a为止, a为一个很小的数,例如10-6。此时认为f(x) ≈0。 N-S流程图
输入x1,x2,求f(x1),f(x2) 直到f(x1)和f(x2)异号 y=f(x)与y1=f(x2) y 与y1同号 真 假 x1=x 求(x1,f(x1))与(x2,f(x2))连线与x轴的交点x y=f(x)与y1=f(x2) y 与y1同号 真 假 x1=x y1=y x2=x y2=y 直到∣y∣<a 输出x的值
例 8.6用弦截法求方程f(x)=x3 -5x2 +16x -80=0根。 #include<math.h> float f(float x) /*定义f函数,以实现f(x)=x3-5x2+16x-80=0*/ {float y; y=((x-5.0)*x+16.0)*x-80.0; return(y); } float xpoint(float x1,float x2) /*定义xpoint函数,求出弦与x轴交点*/ {float y; y=((x1*f(x2)-x2*(fx1))/(f(x2)-f(x1));
例 8.6用弦截法求方程 f(x)=x3 -5x2 +16x -80=0根。 float root(float x1,float x2) /*定义root函数,求近似根*/ {float x,y,y1; y1=f(x1); do { x=xpoint(x1,x2); y=f(x); if(y * y1>0) /*f(x1)与f(x2)同符号*/ {y1=y; x1=x; } else x2=x; }while(fabs(y)>=0.0001); return(x);
例 8.6用弦截法求方程 f(x)=x3 -5x2 +16x -80=0根。 main() {float x1,x2,f1,f2,x; do cout<<“input x1,x2:\n”; cin>>x1>>x2; f1=f(x1); f2=f(x2); }while(f1*f2>=0); x=root(x1,x2); cout<<“A root of equation is ”>>x; } 运行结果: input x1,x2; 从键盘上输入2,6 A root of equation is 5.0000 运行程序
例 8.6用弦截法求方程 f(x)=x3 -5x2 +16x -80=0根。 该题函数调用的示意图: root函数 xpoint函数 f函数 main函数 调用root 函数 调用xpoint函数 调用f函数 输出根x结束
5.6 函数的递归调用 递归调用 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。 直接调用本函数 如: int f(int x) {int y,z; z=f(y); return(2*z);} 递归调用 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。 直接调用本函数 间接调用本函数 f函数 f1函数 f2函数 调用f2函数 调用f1函数 调用f函数
5.6 函数的递归调用 例8.7 例8.8 返回主菜单
例 8.7 有5个人,第5个人说他比第4个人大2岁,第4个人说他对第3个人大2岁,第3个人说他对第2个人大2岁,第2个人说他比第1个人大2岁,第1个人说他10岁。求第5个人多少岁。 分析 age(5)=age(4)+2 age(4)=age(3)+2 age(3)=age(2)+2 age(2)=age(1)+2 age(1)=10 用数学公式表示: 10 (n=1) age(n)= age(n-1)+2 (n>1) 归纳
例 8.7 求第五个人年龄的过程 求解过程示意图 age(int n) /*求年龄的递归函数*/ {int c; /*c用作存放函数的返回 例 8.7 求第五个人年龄的过程 求解过程示意图 age(int n) /*求年龄的递归函数*/ {int c; /*c用作存放函数的返回 值的变量*/ if(n==1) c=10; else c=age(n-1)+2; return(c); } main() {cout>>age(5); 运行程序 运行结果: 18
age(5) =18 age(5) =age(4)+2 age(4) =16 age(4) =age(3)+2 age(3) =14 age(3) age(2) =age(2)+2 =12 age(2) =age(1)+2 age(1) 10
input an intege number:10 n·(n-1)! (n>1) float fac(int n) {float f; if(n<0){cout>>“n<0,dataerror!”} else if(n==0||n==1) f=1; else if=fac(n-1)*n; return(f);} main() {int n; float y; cout>>“input an integer number:”; cin>>n; y=fac(n); cout<<n<<“!=”<<y;} 运行程序 运行情况: input an intege number:10 10!= 3628800
例8.9 Hanoi(汉诺)塔问题 古代有一个梵塔,塔内有3个座A、B、C,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上(如下图)。有一个老和尚相吧这64个盘子从A座移到C座,但每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B座。 B A C
有关全局变量的图示说明 全局变量p和q的作用范围 全局变量c1、 c2的作用范围 int p=1,q=5; /*外部变量*/ float f1(int a) /*定义函数f1*/ {int b,c; ….. } char c1,c2; /*外部变量*/ char f2(int x,int y) /*定义函数f2*/ {int i,j; ……. main( ) /*主函数*/ {int m,n; …. 全局变量c1、 c2的作用范围 全局变量p和q的作用范围
几点说明 1)设全局变量的作用是增加了函数间数据联系的渠道。文件中 各个函数都能访问全局变量,可为各个函数之间增加了数据联系 的渠道,被调函数通过全局变量将结果告知主调函数。 2)全局变量不应设置过多,以降低函数之间相互影响,符合现代 模块化设计风格。 3)若全局变量与局部变量同名,则在局部变量作用的范围内, 局部变量有效(即局部变量起作用)。
5.9 变量的存储类型 动态存储和静态存储方式 auto变量 用static声明局部变量 register变量 用extern声明外部变量 关于变量的声明和定义 存储类别小结 返回主菜单
5.9.1 动态存储方式与静态存储方式 定义 1)静态存储方式是指在程序运行期间分配固定的存储空间的 方式。 2)动态存储方式是在程序运行期间根据需要进行动态的分配 存储空间的方式。 从内存中供用户使用的存储空间的情况来分析存储方式 存储空间分为三部分: 1)程序区 2)静态存储区 3)动态存储区
5.9.1 动态存储方式与静态存储方式 供用户适用的 内存空间可分 为三个区域: 1)程序区存放程序的机器码; 程序区 2)数据存放在静态存储区和动态存储区。 3)静态存储区存放全局变量和静态局部变 量; 4)动态存储区存放: 自动局部变量 形式参数 函数调用时的被保护的返回地址(断 点)和需保护的现场信息 程序区 静态存储区 动态存储区
5.9.2 auto变量 1)变量和函数有两个属性:数据类型、存储类别。 变量的存储类别指的是变量在内存中存放的方式--静态存储还是动态存储。分为4种auto、static、register、extern。 2)auto变量 函数中的形参和在函数中定义的变量,都属于此类。在调用该函数时系统会给它们分配存储空间;在调用该函数结束时自动释放这些存储空间。 例如: int f(int a) /*定义f函数,a为形参*/ {auto int b,c=3; /定义b、c为自动变量*/ ……}
8.9.2 auto变量 实际上,关键字“auto”可以省略,则隐含确定为”自动存储类别”, 属于动态存储方式。 例如: auto int b,c=3; int b,c=3 两者等价
5.9.3 用static声明局部变量 变量所在的函数在调用结束后,该变量所占用的存储单元不释放 ,在下一次该函数调用时,该变量已有值,就是上一次函数调用 结束时的值。
说 明 1)静态局部变量属于静态存储类别,在静态存储区内分配存储 单元。在程序整个运行期间都不释放。而自动变量属于动态存储 说 明 1)静态局部变量属于静态存储类别,在静态存储区内分配存储 单元。在程序整个运行期间都不释放。而自动变量属于动态存储 类别,占动态存储空间而不占静态存储区空间,函数调用结束即 释放。 2 )对静态局部变量是在编译时赋初值的。以后每次调用函数时 不再重新赋初值而只是保留上次函数调用结束时的值。而对自动 变量赋初值,不是在编译时进行的,而是在函数调用时进行,每 调用一次函数重新给一次初值,相当于执行一次赋值语句。 3)如在定义局部变量时不赋初值的话,而对静态局部变量来说, 编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。 而对自动变量来说,如果不赋初值则它的值是一个不确定的值。 这是因每次函数调用结束后存储单元已释放,下次调用时又重新 另分配存储单元,而所分配的单元中的值是不确定的。
4)虽然静态局部变量在函数调用结束后还存在,但其他函数是不能引用它的。 5)需要使用局部静态变量的情形“: A 需要保留函数上一次调用结束时的值。 B 如果初始化后,变量只被引用而不改变其值,则这时用静态变量比较方便。
5.9.4 register 变量 如果有一些变量使用频繁,则为存取变量的值要花不少时间。为提高执行效率,C语言允许将局部变量的值放在CPU中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。这种变量叫“寄存器变量”(register)。
关于register变量使用说明 1)只有局部自动变量和形式参数可以作为寄存器变量; 2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量; 3)局部静态变量不能定义为寄存器变量。 如:下面书写是错误的 register static int a,b,c;
5.9.5 用extern声明外部变量 外部变量(即全局变量)是在函数的外部定义的,它的作用域为 从变量的定义处开始,到本程序文件的末尾。在此作用域内,全 局变量可以为程序中各个函数所使用。编译时将外部变量分配在 静态存储区。 有时需要用extern来声明外部变量,以扩展外部变量的作用域。 1)在一个文件内声明外部变量 如果在定义点之前的函数要引用外部变量,则在引用前用关键字 extern对该变量作”外部变量声明“。 2)在多个文件的程序中声明外部变量
5.9.6 关于变量声明和定义 定义性说明 引用性说明 1)函数的声明和定义的区别 对函数的声明是放在声明部分中的,而函数的定义是一个独立的 模块。 2)对变量的声明和定义 A 在声明部分出现的变量的两种情况 第一种:需要建立存储空间(如:int a;) 第二种:不需要建立存储空间(如:extern a;) B 外部变量的定义和声明 外部变量的定义只一次,位置在所有函数之外,而同一文件的 声明可以有许多次,位置在函数内。 外部变量的初始化只能在 “定义”时进行。 定义性说明 引用性说明
5.9.7 关于变量的声明和定义 C 用static来声明一个变量的作用
5.9.8 存储类别小结 局部变量 外部变量 register 局部static auto 外部static 外部 存储类别 静态 动态 存储方式 静态存储区 动态区 存储区 寄存器 自动赋初值0或空字符 不确定 未赋初值 作用域 定义变量的函数或复合语句内 本文件 其它文件 编译时赋初值,只赋一次 每次函数调用时 赋初值 程序整个运行期间 函数调用开始至结束 生存期
5.10 内部和外部函数 static int fun(int a,int b) extern int fun(int a,int b 根据函数能否被其他源文件调用,将函数区分为内部和外部函数。 内部函数 static 类型标识符 函数名(形参表) 外部函数 (1)在定义函数时,如果在函数首部的最左端冠以关键字extern, 则表示此函数是外部函数,可供其他文件调用。 (2)在需要调用此函数的文件中,用extern声明所用的函数是 外部函数。 static int fun(int a,int b) extern int fun(int a,int b 返回主菜单
5.11 如何运行一个多文件的程序 用Turbo C集成环境 在MS C上运行编译连接 用#include 命令 返回主菜单
The end