第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性
学习目标 理解函数的作用以及分类; 熟练掌握函数的定义、声明、调用; 理解形参和实参的作用,以及三种参数传递方式的区别与使用; 掌握数组作函数参数声明、调用形式; 掌握默认值参数的定义与作用; 理解函数的递归调用,熟悉递归程序的编写; 理解并掌握函数重载的作用和形式; 理解局部变量和全局变量; 理解变量的存储类别,区分自动、静态、寄存器以及外部 变量的特点。
第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性
8.1 函数的定义与调用 如何解决这种冗余呢? 引入—为什么需要函数? void main() { int m,n,i,sum; cout<<"m="; cin>>m; for(i=1,sum=0;i<=m;i++) sum=sum+i; cout<<"1+…+"<<m<<"="<<sum<<endl; cout<<"n="; cin>>n; for(i=1,sum=0;i<=n;i++) cout<<"1+…+"<<n<<"="<<sum<<endl; } 如何解决这种冗余呢?
函数的定义 函数 是程序代码的自包含单元,用于完成某一特定任务。 是C++的基本组成模块。 用于完成某种操作或计算某个数值。
函数的作用 函数 降低程序复杂度 增强代码的重用性和可维护性 提高程序开发效率
函数的分类 标准函数 定义角度 自定义函数 函数 无参函数 参数角度 有参函数
函数的分类 标准函数 是C++编译系统预定义的函数,也称库函数。 用户可以直接使用系统提供的库函数,使用时需在程 序中包含相应的头文件。 用户可以直接使用系统提供的库函数,使用时需在程 序中包含相应的头文件。 如 #include<iostream.h>等。 库函数有很多个,例如: 数学计算库函数 #include<math.h> 字符串处理库函数 #include<string.h> 标准输入输出库函数 #include<iostream.h>
函数的分类 自定义函数 无参函数 有参函数 程序设计中由用户自己定义函数。 是程序设计中的主要环节。 函数没有参数传入,执行后也没有返回值。 主要是为完成某种操作。 有参函数 函数有参数传入,执行后有返回值。 可完成操作或计算数值。
函数的定义方式 函数的定义: 函数类型 形参列表 函数类型 函数名(形参列表) { 函数体; } 函数执行后返回值的类型; 无返回值时,使用“void”; 形参列表 函数接收的数据; 若无数据接收则为空,或写为“void”; 否则格式为:(类型1 参数1 ,类型2 参数2 ,……) 函数类型 函数名(形参列表) { 函数体; }
函数的调用方式 函数的调用: 5 a 8 b 实参 x y 形参 5 8 实参列表的格式:实参1,实参2,… 没有参数时不能省略圆括号。 函数名(实参列表) 5 a 8 b 实参 x y 形参 5 8
函数的形参 形参—定义函数时给出的参数 形参只是虚拟的参数,在函数未调用之前,没有任何实际的值。 形参必须要定义类型。 未调用函数时,形参不占内存单元,只在函数开始调用时,形参才被分配内存单元。 调用结束后,形参所占用的内存单元被释放。 形参只作用于被调函数,可以在别的函数中使用相同的变量名。
函数的实参 实参—调用时给函数传递的实际参数。 实参对形参变量的传递是“值传递”,即单向传递。 在内存中实参、形参分占不同的单元。 实参的类型可以是常量、变量以及表达式;不管是哪种情况,在调用时实参必须是一个确定的值。 实参是按顺序逐个传递给形参的,其个数、类型必须与形参一致。 实参对形参变量的传递是“值传递”,即单向传递。 在内存中实参、形参分占不同的单元。
例: 形参实参类型相等,一一对应 void fun(int x, int y) { x=x*10; y=y+x; cout<<x<<‘\t’<<y<<endl; } void main(void) { int a=2, b=3; fun(a+b,a*b); cout<<a<<‘\t’<<b<<endl; 形参实参类型相等,一一对应 x y 50 5 56 6 5 6 2 3 a b 50 56 2 3
例: 两个被调函数主要用于完成打印操作。 函数类型 函数名 函数形参 void printstar (void ) { cout<<“* * * * * * * * * * *\n”; } void print_message (void) { cout<<“ How do you do! \n”; } void main(void ) { printstar ( ); print_message ( ); printstar( ); } 输出: * * * * * * * * * * * How do you do! * * * * * * * * * * * 调用函数 调用函数 调用函数
函数的返回值 函数的返回值 通过return语句获得。 函数只能有唯一的返回值。 函数返回值的类型就是函数的类型。
函数的返回值 函数体 int max ( int x, int y) { int z; z=x>y?x:y; return z; } 如果有函数返回值, 函数的类型就是返回值的类型 int max ( int x, int y) { int z; z=x>y?x:y; return z; } 如果有函数返回值,返回值就是函数值,必须唯一。 函数体的类型、形式参数的类型必须在函数的定义中体现出来。 函数体 参数(多个) 函数值(唯一)
函数的返回值 若函数体内没有return语句,就一直执行到函数体的末尾,然后返回到主调函数的调用处 int max ( int x, int y) { if (x>y) return x ; else return y; } int add ( int a, int b) { return (a+b); } 函数的返回值为唯一值 函数的返回值为表达式 若函数体内没有return语句,就一直执行到函数体的末尾,然后返回到主调函数的调用处
8.1 函数的定义与调用 调用另一函数(即被调用函数)需要具备哪些条 件呢? 被调用的函数在调用的时候必须是已存在的函数 调用另一函数(即被调用函数)需要具备哪些条 件呢? 被调用的函数在调用的时候必须是已存在的函数 如果使用库函数,必须用 #include < math.h> 函数调用遵循先定义、后调用的原则,即被调函数应出 现在主调函数之前。 如果使用用户自己定义的函数,而该函数与调用它的函 数(即主调函数)在同一个程序中且位置在主调函数之 后,则必须在调用此函数之前对被调用的函数作声明。
实参传递的是一个具体的值,不必说明参数类型 函数先定义后调用 float max(float x, float y) { float z; z=(x>y)? x : y ; return z; } 形参必须说明参数类型 被调函数先定义 void main (void) { float a,b, c; cin>>a>>b; c=max (a+b , a*b) ; cout<<“The max is”<<c<<endl; } 定义之后再调用 实参传递的是一个具体的值,不必说明参数类型
函数先使用后定义时必须要有声明 定义是一个完整的函数单位,而原型说明仅仅是说明函数的返回值及形参的类型。 void main (void) { float a,b, c; float max (float, float); cin>>a>>b; c=max (a,b) ; cout<<“The max is”<<c<<endl; } 函数原型声明 函数定义 float max (float x, float y) { float z; z=(x>y)? x : y ; return z; } 定义是一个完整的函数单位,而原型说明仅仅是说明函数的返回值及形参的类型。
函数的嵌套调用 C++语言中,所有函数都是平行独立的,无主次 、相互包含之分。函数可以嵌套调用,不可嵌套 定义。 int min ( int a, int b) { return ( a<b? a: b); } int max ( int a, int b) { int c; int min ( int a, int b) { return ( a<b? a: b); } c=min(a,b); return ( a>b? a : b); 嵌套定义 平行定义 int max ( int a, int b) { int c; c=min(a,b); return ( a>b? a : b); } 嵌套调用
第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性
传值调用举例 输出: 7 6 7 2 7 5 void main(void) x 7 j 6 void main(void) { int i=2, x=5, j=7; void fun(int,int); fun ( j, 6); cout<<i<<‘\t’<< j<<‘\t’<< x<<endl; } void fun ( int i, int j) { int x=7; cout<<i<<‘\t’<< j<<‘\t’<<x<<endl; j x i 7 7 6 输出: 7 6 7 2 7 5
8.2 函数的参数 函数的参数传递方式---(二)传地址调用 1、形参类型:指针变量 2、实参类型:数据的地址值 3、传递方式:将实参的地址传递给形参; 4、特点:函数中对形参的改变会影响实参的值。
传地址调用举例 void prt(int *x , int *y , int *z) { cout<< ++*x<<‘,’<< ++*y<<‘,’<<*(z++)<<endl; } int a=10, b=40, c=20; void main(void) prt (&a, &b, &c); 11, 41, 20 12, 42, 20 x &a a 10 ++*x: *x=*x+1 *(z++): *z ; z=z+1
传地址应用1—数组名作函数参数 数组名可以作函数的实参和形参,传递的是数组 的地址。这样,实参、形参共同指向同一段内存 单元,内存单元中的数据发生变化,这种变化会 反应到主调函数内。 在函数调用时,形参数组并没有另外开辟新的存 储单元,而是以实参数组的首地址作为形参数组 的首地址。这样形参数组的元素值发生了变化也 就使实参数组的元素值发生了变化。
(1)形参实参都用数组名 void main(void) { int array[10]; 形参数组,必须进行类型说明 ...... f(array, 10); ..... } 形参数组,必须进行类型说明 f(int arr[ ], int n) { ...... } 实参数组 用数组名作形参,因为接收的是地址,所以可以不指定具体的元素个数。
(1)形参实参都用数组名 指向同一存储区间 array[0] array[1] array[2] array[3] array[4] 2000H array[0] 2004H array[1] 2008H array[2] 200CH array[3] 2010H array[4] 2014H array[5] 201CH array[6] 2020H array[7] 2024H array[8] 2028H array[9]
(2)实参用数组名,形参用指针变量 void main(void) { int a [10]; f(int *x, int n ) { ...... f(a, 10); ..... } f(int *x, int n ) { ...... } 形参指针 实参数组
(3) 形参实参都用指针变量 形参指针 void main(void) { int a [10],*p; f(int *x, int n ) p=a; ...... f(p, 10); ..... } f(int *x, int n ) { ...... } 实参指针 实参指针变量调用前必须赋值
(4)实参为指针变量,形参为数组名 void main(void) 形参数组 { int a [10],*p; p=a; ...... f(p, 10); ..... } 形参数组 f(int x[ ], int n ) { ...... } 实参指针
下面程序实现了将数组中的n个数按相反顺序存放。 void inv(int x[ ], int n) { int t, i, j, m=(n-1)/2; for (i=0;i<=m; i++) { j=n-1-i; t=x[i]; x[i]=x[j]; x[j]=t; } x与a数组指向同一段内存 2 4 5 7 6 11 9 x[9] x[8] x[7] x[6] x[5] x[4] x[3] x[2] x[1] x[0] a[9] a[8] a[7] a[6] a[5] a[4] a[3] a[2] a[1] a[0] x, a 3 void main(void) { int i, a[10]={3,7,9,11,0,6,7,5,4,2}; inv(a,10); for (i=0;i<10; i++) cout<<a[i]<<‘\t’; }
i p j void inv(int *x, int n) { int *p, t, *i, *j, m=(n-1)/2; i=x; j=x+n-1; p=x+m; for (; i<=p; i++,j--) { t=*i; *i=*j; *j=t; } 用指针变量来接受地址 2 4 5 7 6 11 9 x[9] x[8] x[7] x[6] x[5] x[4] x[3] x[2] x[1] x[0] a[9] a[8] a[7] a[6] a[5] a[4] a[3] a[2] a[1] a[0] x, a 3 i j p void main(void) { int i, a[10]={3,7,9,11,0,6,7,5,4,2}; inv(a,10); for (i=0;i<10; i++) cout<<a[i]<<‘\t’; } 34
传地址应用2— 字符串指针作函数参数 将一个字符串从一个函数传递到另一个函 数,可以用地址传递的办法。即用字符数 组名作参数或用指向字符串的指针变量作 参数。在被调函数中可以改变原字符串的 内容。
{ char a[ ]={“I am a teacher”}; char b[ ]={“You are a student”}; 将字符串a复制到字符串b。 from a void main(void) { char a[ ]={“I am a teacher”}; char b[ ]={“You are a student”}; copy_string(a , b); cout<<a<<endl; cout<<b<<endl; } to b from与a一个地址,to与b一个地址 copy_string( char from[],char to[]) { int i; for (i=0; from[i]!=‘\0’; i++) to[i]=from[i]; to[i]=‘\0’; }
也可以用字符指针来接受数组名 for(; *from++=*to++; ) ; 将字符串a复制到字符串b。 copy_string(char *from, char *to) { for ( ; *from!=‘\0’; ) *to++=*from++; *to=‘\0’; } for(; *from++=*to++; ) ; void main(void) { char a[ ]={“I am a teacher”}; char b[ ]={“You are a student”}; copy_string(a,b); cout<<a<<endl; cout<<b<<endl; } a from b to 也可以用字符指针来接受数组名
8.2 函数的参数 函数的参数传递方式---(三)引用调用 1、形参类型:引用型的变量 2、实参类型:变量 3、传递方式:将实参变量名赋给形参引用 4、特点:函数中对形参的操作就是对实参的操作
引用调用举例 输出: 5 3 引用作为形参,实参是变量而不是地址,这与指针变量作形参不一样。 void change(int &x, int &y)//x,y是实参a,b的别名 { int t; t=x; x=y; y=t; } void main(void) { int a=3,b=5; change(a,b); //实参为变量 cout<<a<<‘\t’<<b<<endl; a b 5 5 3 3 y x t 3 输出: 5 3 引用作为形参,实参是变量而不是地址,这与指针变量作形参不一样。
例: void f1( int *px) {*px+=10;} void f2(int &xx) { xx+=10;} void main(void) { int x=0; cout<<"x="<<x<<endl; f1(&x); f2(x); } x=0 x=10 x=20
函数的返回值为指针 char *strchr(char *str , char ch) { while(*str != ch && *str != '\0') { str++ ; } return str ; }
第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性
函数的重载 函数重载的概念 同一个函数名定义多个不同函数的实现。 实现“一物多用”,即同一个函数名实现多种函 数功能。
例1: int add (int a, int b) { return (a+b); } double add (double a, double b) { return(a+b); }
函数的重载 函数重载的原则 函数名相同 参数的类型或数目至少有其一个不同 编译系统根据函数实参的类型和数目从左向右进 行匹配,调用相应的函数。
例2: int fun(int a, int b) void main(void) { { return a+b; } void main(void) { cout<<fun(3,5)<<endl; cout<<fun(5)<<endl; } int fun (int a) { return a*a; } 8 25
函数重载的原则 函数重载不以函数的返回值类型区分。 void move(int a); int move(int a); 重载非法 函数重载不以参数中的关键字区分。 在定义重载函数时,需要注意如果两个函数的函数名称、参数个数、参数类型相同,函数的返回值类型不同,编译时会出现错误,即函数重载不以函数的返回值类型区分。 函数重载不以参数中的关键字区分。 void move (const int a); void move(int a); 重载非法
重载函数 double Add(double one, double two); int Add(int one, int two); int main() { cout<<Add(2.51,3.652); cout<<endl; cout<<Add(2,5); cout<<Add(5); return 0; } 调用double Add(double one,double two)函数 函数重载是多个函数具有相同的名称,但函数的参数不同。在调用函数时,编译器会根据参数的个数、参数的类型区分调用哪个函数。下面根据“函数重载实现多态性”来举例说明。 调用int Add(int one,int two)函数 调用int Add(int one)函数
函数的递归调用 函数的递归调用 函数递归调用的分类 函数 函数递归调用的作用 A函数 B函数 在函数体中直接或间接调用函数本身。 直接递归 直接递归函数直接调用其本身 间接递归A函数调用B函数,而B函数 又调用A函数。 函数递归调用的作用 使用函数递归能够简化复杂的数学运算 直接递归 函数 A函数 B函数 间接递归
例3: 1 1 1 2 2 3 6 4 24 5 120 int factorial(int n); int main() { cout<<factorial(5)<<endl; return 0; } int factorial(int n) if ((n==0)||(n==1)) return 1; else return n*factorial(n-1); n factorial() 1 1 1 2 2 3 6 4 24 5 120
例4: 假设:一对刚出生的小兔一个月后就能长成大兔,再 过一个月就能生下一对小兔,并且此后每个月都生一 对小兔,一年内没有发生死亡。 问:一对刚出生的兔子,一年内繁殖成多少对兔子? 月份 1 2 3 4 5 6 7 8 9 10 11 12 对数 13 21 34 55 89 144 F1=1 (n=1) F2=1 (n=2) Fn=Fn-1+Fn-2 (n>=3) 斐波那契(Fibonacci)数列 斐波那契(1175-1250)
例4: int fib(int n); void main() { int n; cout<<“Please input n:”; cin>>n; cout<<“fib(“<<n<<”)=”<<fib(n)<<endl; } int fib(int n) if (n==1||n==2) return 1; else return fib(n-2)+fib(n-1); n fib(n) 12 fib(12) 11 fib(11) 10 fib(10) 9 fib(9) 8 fib(8) 7 fib(7) 6 fib(6) 5 fib(5) 4 fib(4) 3 fib(3) 2 fib(2)=1 1 fib(1)=1
第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性
作用域 作用域是指程序中所说明的标识符在哪一个区间 内有效,即在哪一个区间内可以使用该标识符。 在C++中,作用域共分为五类: 块作用域 作用域是指程序中所说明的标识符在哪一个区间 内有效,即在哪一个区间内可以使用该标识符。 在C++中,作用域共分为五类: 块作用域 文件作用域 函数原型作用域 函数作用域 类的作用域
块作用域 我们把用花括号括起来的一部分程序称为一个块。 在块内说明的标识符,只能在该块内引用,即其作 用域在该块内,开始于标识符的说明处,结束于块 的结尾处。 在一个函数内部定义的变量或在一个块中定义的变 量称为局部变量 具有块作用域的标识符在其作用域内,将屏蔽其他 同名标识符,即 变量名相同,局部更优先。
块作用域举例 int a = 0; for (int i = 0;i < 10;i++) { int b = i; b++; a += b; } 变量b具有块作用域,当程序的控制流离开了这个循环,就是for循环结束时,这个变量b就被销毁了,不存在了。在for循环的外部再引用变量b的话,程序就会报错:"变量b未定义".
函数作用域 在函数内或复合语句内部定义的变量,其作用域 是从定义的位置起到函数体或复合语句的结束。 形参也是局部变量。 在函数内或复合语句内部定义的变量,其作用域 是从定义的位置起到函数体或复合语句的结束。 形参也是局部变量。 float f1( int a) { int b,c; ..... } void main(void ) { int m, n; ..... } a,b,c有效 m,n有效 float f2( int x, int y) { int i, j; ..... } x,y,i,j 有效
文件作用域 在函数外定义的变量称为全局变量。 全局变量的作用域称为文件作用域,即在整个文件中都是可以访问的。 其缺省的作用范围是:从定义全局变量的位置开始到该源程序文件结束。 当在块作用域内的变量与全局变量同名时,局部变量优先。
int p=1, q=5; float f1( int a) { int b,c; ..... } 全局变量 a,b,c有效 局部变量 p,q有效 char c1,c2; main( ) { int m, n; ..... } c1,c2有效 m,n有效
函数作用域 主函数main中定义的变量,也只在主函数中有 效,同样属于局部变量。 不同的函数可以使用相同名字的局部变量,它 们在内存中分属不同的存储区间,互不干扰。 void main(void) { int x=10; { int x=20; cout<<x<<endl; } x 10 x 20 20 10 60
生存期 程序区 静态存储区 动态存储区 全局变量 动态存储变量 作用域 生存期 局部变量 静态存储变量 静态存储:在文件运行期间有固定的存储空间,直到文件运行结束。 动态存储:在程序运行期间根据需要分配存储空间,函数结束后立即释放空间。若一个函数在程序中被调用两次,则每次分配的单元有可能不同。 程序区 静态存储区 动态存储区 静态局部变量 全局变量 动态局部变量
变量或函数的存储类型 默认存储方式是auto类型! auto static extern register 存贮位置 内存堆栈 CPU的寄存器 生存期 与函数同时 与文件同时 与程序同时 连结性 无连接性 本文件内部连结性 多文件全局连结性 默认存储方式是auto类型!
5 9 int fun(int a) { int c; static int b=3; c=a+ b++; return c; } void main(void) { int x=2, y; y=fun(x); cout<<y<<endl; y=fun(x+3); 只赋一次初值 5 2 4 5 3 9 5 a a b c c 输出: 5 2 9 5 9 x y 变量b是静态局部变量,在内存一旦开辟空间,就不会释放,空间值一直保留
第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性
思考题 编写程序,分别计算下列函数的值(x从键 盘输入) (1) (2) 当最后一项小于0.00001时,累加结束。 网络课程平台http://course.cn/G2S/Template/View.aspx?action=view&courseType=0&courseId=2272