Presentation is loading. Please wait.

Presentation is loading. Please wait.

第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性.

Similar presentations


Presentation on theme: "第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性."— Presentation transcript:

1 第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性

2 学习目标 理解函数的作用以及分类; 熟练掌握函数的定义、声明、调用; 理解形参和实参的作用,以及三种参数传递方式的区别与使用;
掌握数组作函数参数声明、调用形式; 掌握默认值参数的定义与作用; 理解函数的递归调用,熟悉递归程序的编写; 理解并掌握函数重载的作用和形式; 理解局部变量和全局变量; 理解变量的存储类别,区分自动、静态、寄存器以及外部 变量的特点。

3 第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性

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; } 如何解决这种冗余呢?

5 函数的定义 函数 是程序代码的自包含单元,用于完成某一特定任务。 是C++的基本组成模块。 用于完成某种操作或计算某个数值。

6 函数的作用 函数 降低程序复杂度 增强代码的重用性和可维护性 提高程序开发效率

7 函数的分类 标准函数 定义角度 自定义函数 函数 无参函数 参数角度 有参函数

8 函数的分类 标准函数 是C++编译系统预定义的函数,也称库函数。 用户可以直接使用系统提供的库函数,使用时需在程 序中包含相应的头文件。
用户可以直接使用系统提供的库函数,使用时需在程 序中包含相应的头文件。 如 #include<iostream.h>等。 库函数有很多个,例如: 数学计算库函数 #include<math.h> 字符串处理库函数 #include<string.h> 标准输入输出库函数 #include<iostream.h>

9 函数的分类 自定义函数 无参函数 有参函数 程序设计中由用户自己定义函数。 是程序设计中的主要环节。 函数没有参数传入,执行后也没有返回值。
主要是为完成某种操作。 有参函数 函数有参数传入,执行后有返回值。 可完成操作或计算数值。

10 函数的定义方式 函数的定义: 函数类型 形参列表 函数类型 函数名(形参列表) { 函数体; } 函数执行后返回值的类型;
无返回值时,使用“void”; 形参列表 函数接收的数据; 若无数据接收则为空,或写为“void”; 否则格式为:(类型1 参数1 ,类型2 参数2 ,……) 函数类型 函数名(形参列表) { 函数体; }

11 函数的调用方式 函数的调用: 5 a 8 b 实参 x y 形参 5 8 实参列表的格式:实参1,实参2,… 没有参数时不能省略圆括号。
函数名(实参列表) 5 a 8 b 实参 x y 形参 5 8

12 函数的形参 形参—定义函数时给出的参数 形参只是虚拟的参数,在函数未调用之前,没有任何实际的值。 形参必须要定义类型。
未调用函数时,形参不占内存单元,只在函数开始调用时,形参才被分配内存单元。 调用结束后,形参所占用的内存单元被释放。 形参只作用于被调函数,可以在别的函数中使用相同的变量名。

13 函数的实参 实参—调用时给函数传递的实际参数。 实参对形参变量的传递是“值传递”,即单向传递。 在内存中实参、形参分占不同的单元。
实参的类型可以是常量、变量以及表达式;不管是哪种情况,在调用时实参必须是一个确定的值。 实参是按顺序逐个传递给形参的,其个数、类型必须与形参一致。 实参对形参变量的传递是“值传递”,即单向传递。 在内存中实参、形参分占不同的单元。

14 例: 形参实参类型相等,一一对应 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

15 例: 两个被调函数主要用于完成打印操作。 函数类型 函数名 函数形参 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! * * * * * * * * * * * 调用函数 调用函数 调用函数

16 函数的返回值 函数的返回值 通过return语句获得。 函数只能有唯一的返回值。 函数返回值的类型就是函数的类型。

17 函数的返回值 函数体 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; } 如果有函数返回值,返回值就是函数值,必须唯一。 函数体的类型、形式参数的类型必须在函数的定义中体现出来。 函数体 参数(多个) 函数值(唯一)

18 函数的返回值 若函数体内没有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语句,就一直执行到函数体的末尾,然后返回到主调函数的调用处

19 8.1 函数的定义与调用 调用另一函数(即被调用函数)需要具备哪些条 件呢? 被调用的函数在调用的时候必须是已存在的函数
调用另一函数(即被调用函数)需要具备哪些条 件呢? 被调用的函数在调用的时候必须是已存在的函数 如果使用库函数,必须用 #include < math.h> 函数调用遵循先定义、后调用的原则,即被调函数应出 现在主调函数之前。 如果使用用户自己定义的函数,而该函数与调用它的函 数(即主调函数)在同一个程序中且位置在主调函数之 后,则必须在调用此函数之前对被调用的函数作声明。

20 实参传递的是一个具体的值,不必说明参数类型
函数先定义后调用 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; } 定义之后再调用 实参传递的是一个具体的值,不必说明参数类型

21 函数先使用后定义时必须要有声明 定义是一个完整的函数单位,而原型说明仅仅是说明函数的返回值及形参的类型。 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; } 定义是一个完整的函数单位,而原型说明仅仅是说明函数的返回值及形参的类型。

22 函数的嵌套调用 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); } 嵌套调用

23 第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性

24 传值调用举例 输出: 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 输出:

25 8.2 函数的参数 函数的参数传递方式---(二)传地址调用 1、形参类型:指针变量 2、实参类型:数据的地址值
3、传递方式:将实参的地址传递给形参; 4、特点:函数中对形参的改变会影响实参的值。

26 传地址调用举例 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

27 传地址应用1—数组名作函数参数 数组名可以作函数的实参和形参,传递的是数组 的地址。这样,实参、形参共同指向同一段内存 单元,内存单元中的数据发生变化,这种变化会 反应到主调函数内。 在函数调用时,形参数组并没有另外开辟新的存 储单元,而是以实参数组的首地址作为形参数组 的首地址。这样形参数组的元素值发生了变化也 就使实参数组的元素值发生了变化。

28 (1)形参实参都用数组名 void main(void) { int array[10]; 形参数组,必须进行类型说明 ......
f(array, 10); ..... } 形参数组,必须进行类型说明 f(int arr[ ], int n) { ...... } 实参数组 用数组名作形参,因为接收的是地址,所以可以不指定具体的元素个数。

29 (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]

30 (2)实参用数组名,形参用指针变量 void main(void) { int a [10]; f(int *x, int n ) {
...... f(a, 10); ..... } f(int *x, int n ) { ...... } 形参指针 实参数组

31 (3) 形参实参都用指针变量 形参指针 void main(void) { int a [10],*p; f(int *x, int n )
p=a; ...... f(p, 10); ..... } f(int *x, int n ) { ...... } 实参指针 实参指针变量调用前必须赋值

32 (4)实参为指针变量,形参为数组名 void main(void) 形参数组 { int a [10],*p; p=a;
...... f(p, 10); ..... } 形参数组 f(int x[ ], int n ) { ...... } 实参指针

33 下面程序实现了将数组中的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’; }

34 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

35 传地址应用2— 字符串指针作函数参数 将一个字符串从一个函数传递到另一个函 数,可以用地址传递的办法。即用字符数 组名作参数或用指向字符串的指针变量作 参数。在被调函数中可以改变原字符串的 内容。

36 { 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’; }

37 也可以用字符指针来接受数组名 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 也可以用字符指针来接受数组名

38 8.2 函数的参数 函数的参数传递方式---(三)引用调用 1、形参类型:引用型的变量 2、实参类型:变量
3、传递方式:将实参变量名赋给形参引用 4、特点:函数中对形参的操作就是对实参的操作

39 引用调用举例 输出: 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 输出: 引用作为形参,实参是变量而不是地址,这与指针变量作形参不一样。

40 例: 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

41 函数的返回值为指针 char *strchr(char *str , char ch) {
while(*str != ch && *str != '\0') { str++ ; } return str ; }

42 第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性

43 函数的重载 函数重载的概念 同一个函数名定义多个不同函数的实现。 实现“一物多用”,即同一个函数名实现多种函 数功能。

44 例1: int add (int a, int b) { return (a+b); }
double add (double a, double b) { return(a+b); }

45 函数的重载 函数重载的原则 函数名相同 参数的类型或数目至少有其一个不同
编译系统根据函数实参的类型和数目从左向右进 行匹配,调用相应的函数。

46 例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

47 函数重载的原则 函数重载不以函数的返回值类型区分。 void move(int a); int move(int a); 重载非法
函数重载不以参数中的关键字区分。 在定义重载函数时,需要注意如果两个函数的函数名称、参数个数、参数类型相同,函数的返回值类型不同,编译时会出现错误,即函数重载不以函数的返回值类型区分。 函数重载不以参数中的关键字区分。 void move (const int a); void move(int a); 重载非法

48 重载函数 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)函数

49 函数的递归调用 函数的递归调用 函数递归调用的分类 函数 函数递归调用的作用 A函数 B函数 在函数体中直接或间接调用函数本身。 直接递归
直接递归函数直接调用其本身 间接递归A函数调用B函数,而B函数 又调用A函数。 函数递归调用的作用 使用函数递归能够简化复杂的数学运算 直接递归 函数 A函数 B函数 间接递归

50 例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

51 例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)数列 斐波那契( )

52 例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

53 第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性

54 作用域 作用域是指程序中所说明的标识符在哪一个区间 内有效,即在哪一个区间内可以使用该标识符。 在C++中,作用域共分为五类: 块作用域
作用域是指程序中所说明的标识符在哪一个区间 内有效,即在哪一个区间内可以使用该标识符。 在C++中,作用域共分为五类: 块作用域 文件作用域 函数原型作用域 函数作用域 类的作用域

55 块作用域 我们把用花括号括起来的一部分程序称为一个块。 在块内说明的标识符,只能在该块内引用,即其作 用域在该块内,开始于标识符的说明处,结束于块 的结尾处。 在一个函数内部定义的变量或在一个块中定义的变 量称为局部变量 具有块作用域的标识符在其作用域内,将屏蔽其他 同名标识符,即 变量名相同,局部更优先。

56 块作用域举例 int a = 0; for (int i = 0;i < 10;i++) { int b = i; b++; a += b; } 变量b具有块作用域,当程序的控制流离开了这个循环,就是for循环结束时,这个变量b就被销毁了,不存在了。在for循环的外部再引用变量b的话,程序就会报错:"变量b未定义".

57 函数作用域 在函数内或复合语句内部定义的变量,其作用域 是从定义的位置起到函数体或复合语句的结束。 形参也是局部变量。
在函数内或复合语句内部定义的变量,其作用域 是从定义的位置起到函数体或复合语句的结束。 形参也是局部变量。 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 有效

58 文件作用域 在函数外定义的变量称为全局变量。 全局变量的作用域称为文件作用域,即在整个文件中都是可以访问的。
其缺省的作用范围是:从定义全局变量的位置开始到该源程序文件结束。 当在块作用域内的变量与全局变量同名时,局部变量优先。

59 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有效

60 函数作用域 主函数main中定义的变量,也只在主函数中有 效,同样属于局部变量。
不同的函数可以使用相同名字的局部变量,它 们在内存中分属不同的存储区间,互不干扰。 void main(void) { int x=10; { int x=20; cout<<x<<endl; } x 10 x 20 20 10 60

61 生存期 程序区 静态存储区 动态存储区 全局变量 动态存储变量 作用域 生存期 局部变量 静态存储变量
静态存储:在文件运行期间有固定的存储空间,直到文件运行结束。 动态存储:在程序运行期间根据需要分配存储空间,函数结束后立即释放空间。若一个函数在程序中被调用两次,则每次分配的单元有可能不同。 程序区 静态存储区 动态存储区 静态局部变量 全局变量 动态局部变量

62 变量或函数的存储类型 默认存储方式是auto类型! auto static extern register 存贮位置 内存堆栈
CPU的寄存器 生存期 与函数同时 与文件同时 与程序同时 连结性 无连接性 本文件内部连结性 多文件全局连结性 默认存储方式是auto类型!

63 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是静态局部变量,在内存一旦开辟空间,就不会释放,空间值一直保留

64 第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性

65 思考题 编写程序,分别计算下列函数的值(x从键 盘输入) (1) (2) 当最后一项小于0.00001时,累加结束。
网络课程平台


Download ppt "第8讲 函数 8.1 函数的定义与调用 8.2 函数的参数 8.3 函数重载与递归 8.4 标识符作用域与变量的存储特性."

Similar presentations


Ads by Google