Presentation is loading. Please wait.

Presentation is loading. Please wait.

第5章 函数与模块化设计 学习目的与要求: 掌握函数的定义及调用方法 理解并掌握参数的传递方法 理解函数的嵌套与递归调用

Similar presentations


Presentation on theme: "第5章 函数与模块化设计 学习目的与要求: 掌握函数的定义及调用方法 理解并掌握参数的传递方法 理解函数的嵌套与递归调用"— Presentation transcript:

1 第5章 函数与模块化设计 学习目的与要求: 掌握函数的定义及调用方法 理解并掌握参数的传递方法 理解函数的嵌套与递归调用
理解并掌握全局变量与局部变量,并掌握函数中全局变量与局部变量的使用方法 了解变量的存储方式

2 函数是程序设计语言的重要里程碑之一,它标志着程序模块化设计和软件重用的开始。简单地说,模块化设计就是把一个复杂的问题按照功能划分为若干简单的功能模块,以模块为单位进行程序设计。模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。一般地,将复杂问题划分为不同的模块以后更适合软件的集体开发,不同的模块由不同的程序员设计,只需要确定好模块之间的接口关系,就可以实现模块之间的相互调用,而模块内部的具体实现可以由程序员自己设计。在C语言中,模块是通过函数来实现的。

3 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

4 概述 #include <stdio.h> int main() {
printf(“this is the first program.\n”); return 1; } 标准的库函数并不能满足用户所有的需求,用户还可以根据程序的需要自定义函数,通过对函数的调用来完成相应的功能。

5 概述 例5-1 编写程序,计算 。 int m,n,i,fac1, fac2, fac3;
例5-1 编写程序,计算 。 int m,n,i,fac1, fac2, fac3; printf("请输入两个整数n和m(n>m): "); scanf("%d%d",&n,&m); fac1=fac2=fac3=1; for(i=1; i<=m;i++) fac1=fac1*i; for(i=1;i<=n;i++) fac2=fac2*i; for(i=1;i<=n-m;i++) fac3=fac3*i; printf("计算结果为:%d\n", fac2/(fac1*fac3));

6 概述 这三次循环中,除了循环的次数(即终值)不同以外,其它的代码完全一样,为减少代码的重写,可以把这部分通用代码从程序中抽出来,利用函数来实现。 int fac(int n){ int i,result=1; for(i=1;i<=n;i++) result=result*i; return result; } int m,n; printf("请输入两个整数n和m(n>m): "); scanf("%d%d",&n,&m); printf("计算结果为:%d\n", fac(n)/(fac(m)*fac(n-m)));

7 概述 (1)从用户使用的角度看,函数包括两种:由系统提供的、无需用户定义的标准函数,这些函数根据不同的功能存放在不同的头文件中。用户根据需要编写具有特定功能的自定义函数。 (2)C语言中所有的函数都是平行的,它们之间都是互相独立的。函数只能嵌套调用,不能嵌套定义。 (3)一个源程序文件中,可以由一个或多个函数以及其它有关内容(如数据声明与定义)组成,但必有一个main函数。程序总是从main函数开始和结束的。

8 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

9 函数的定义 数据类型 函数名(数据类型 形式参数1, …, 数据类型 形式参数n) { 函数体 } int fac(int n){
int i,result=1; for(i=1;i<=n;i++) result=result*i; return result; } 数据类型 函数名(数据类型 形式参数1, …, 数据类型 形式参数n) { 函数体 }

10 函数的定义 例5-2 编写函数,判断某一年是否为闰年。要求如果是闰年,函数返回1,不是返回0。
解题思路:判断某一年是否闰年,只要判断该年是否能被400整除或能被4整除并且不能被100整除即可。将年作为函数的形式参数,在函数中对其进行判断。由于函数要求返回的值是1或者0,可以将函数的返回值定义为整型。 int judge_year(int year){ if ((year%4==0 && year%100!=0) ||(year%400==0)) return 1; else return 0; }

11 函数的定义 (1)在对函数的形式参数进行定义时,需要声明每一个变量的类型,不能像通常的变量声明那样,使用变量列表。
int max(int a, int b) //正确的参数定义 int max(int a,b) //错误的参数定义 (2)函数的返回类型原则上应与函数体内return语句中表达式的类型保持一致,如果二者出现不一致的情况,以函数的返回类型为准。当数据的类型是数值型时,系统会进行自动类型转换。

12 函数的定义 例5-3 编写函数,计算两个数的最大值。 int imax(float a, float b){ float c;
c=(a>b)? a:b; return c; } void main(){ float x,y; int z; printf("请输入两个实数:"); scanf("%f%f",&x,&y); z=imax(x,y); printf("两个数的最大值是:%d\n",z);

13 函数名(实际参数1, 实际参数2,…, 实际参数n);
函数的调用 函数名(实际参数1, 实际参数2,…, 实际参数n); (1)定义函数时使用的参数称作形式参数,调用函数时使用的参数称作实际参数。形式参数必须是变量,而实际参数可以是常量、变量、表达式或函数,但必须有确定的值。例如: c=max(9,5); //实参是常量 c=max(x,y); //实参是变量 c=max(x+2,y*3); //实参是表达式 c=max(max(x,y),z); //求三个数的最大值,实参是函数 (2)传递的实际参数与函数定义的形式参数顺序和类型应保持一致; (3)执行函数调用时,形式参数和实际参数是两个不同的存储单元。 (4)在一个函数中可以有多个return语句,但只要执行到其中一个return语句,就结束该函数的调用。

14 函数的调用 例5-4 输入一个整数,判断该整数是否满足如下条件:它加上100后是一个完全平方数,再加上168又是一个完全平方数。请编写函数实现。 解题思路:判断一个数是否是完全平方数,只需要判断该数开平方后得到的整数平方是否与该数相等,如果相等,这个数是完全平方数,如果不相等,这个数不是完全平方数。给函数传递一个整数,判断该数加上100后是否是一个完全平方数,若是,然后再加上168,判断新数是否还是一个完全平方数,条件满足返回值为1,否则返回值为0。 int judge(int n){ int x,y; x=(int)sqrt(n+100); y=(int)sqrt(n+268); if (x*x==n+100 &&y*y==n+268) return 1; else return 0; }

15 函数的声明 C语言中的被调函数或者库函数,或者是用户自定义的函数。 (1)如果是库函数, 则需要引入相应的头文件;
(2)如果是用户自定义的函数,有两种情况: ① 被调函数在主调函数之前进行定义,则不用进行函数声明,主调函数可以直接调用被调函数。 ② 被调用在主调函数之后进行定义,则在主调函数调用之前对被调函数进行函数声明。 函数类型 函数名(形参类型1 形参1,形参类型2 形参2,……); 函数类型 函数名(形参类型1,形参类型2,……);

16 函数的声明 例5-5 编写程序,计算一个整数各位数字之和,例如123,各位数字之和为1+2+3=6。
#include <stdio.h> int sum(int number); //函数声明 void main(){ int number, result; scanf("%d", &number); result=sum(number); printf("整数%d的各位数字之和是%d \n",number,result); } int sum(int number) { //函数定义 int result=0; while(number>0){ result=result+number%10; number=number/10; return result; 解题思路:首先编写求各位数字之和的sum函数,它的类型为int型,它有一个参数number,通过循环计算出number中每位数字的和result,其类型也应为int型。若sum函数在main函数之后进行定义,在调用函数之前,要对sum函数进行声明。

17 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

18 参数的传递——普通变量作为函数参数 在C语言对函数进行调用时,将主调函数的实参传给被调函数的形参,同时编译系统为形式参数分配内存单元。在函数调用结束后,编译系统会立即释放形参所占用的内存单元。这里有两点需要强调: (1)函数中数据传递是单向的,只能把实参传给形参; (2)函数中的形参只在函数内部有效,在函数调用过程中,形参值的改变不会影响实参。一旦函数调用结束,形参变量所占的内存空间被释放。

19 参数的传递——普通变量作为函数参数 例5-6 输入两个整数,编写函数实现两个整数值的交换。
解题思路:首先编写数值交换的swap函数,它有两个参数,是两个简单变量,由于函数的功能是实现两个数的交换,无需返回值,所以它的函数类型为void型,若swap函数在main函数之后进行定义,在调用函数之前,要对swap函数进行声明。 #include <stdio.h> int main(){ void swap(int, int); int a,b; printf("请输入两个整数: "); scanf("%d%d", &a,&b); printf("交换前:a=%d,b=%d\n",a,b); swap(a,b); printf("交换后:a=%d,b=%d\n",a,b); return 0; } void swap(int x,int y){ int temp; printf(“函数swap中,交换前: x=%d,y=%d\n",x,y); temp=x; x=y; y=temp; printf("函数swap中,交换后:x=%d,y=%d\n",x,y); }

20 参数的传递——数组元素作为函数参数 数组元素只能作为实参,不能作为形参。这是因为形参是在函数被调用时才分配存储空间,而数组中的元素是在定义数组时系统已经分配了存储空间,因而编译系统在函数调用时无法为某一个数组元素单独分配存储空间。而将数组元素作为实参传递给函数的形参时,只需将数组元素看成是一个具体的变量传递给函数的形参,形参的相关运算并不能改变实参的值。 C语言中,函数参数的传递有两种:值传递和地址传递。数组元素作函数参数实现的是值传递,数组名作函数参数实现的是地址传递。

21 参数的传递——数组元素作为函数参数 例5-7 给定一个长度为10的数组,要求输出该数组中的最大数及其在该数组中的位置。
int max(int x, int y) ; int a[10]={10,100,98,789,98, 0,121,981,78,191}; int i,maxNumber,index; maxNumber=a[0]; index=0; for( i=1;i<10;i++) if (max(maxNumber, a[i])>maxNumber){ maxNumber=a[i]; index=i; } printf("数组中的最大值是%d,是数组中第%d个元素\n",maxNumber,index+1); int max(int x, int y){ return x>y?x:y; 解题思路:若求数组中的最大值和最大值在数组中的位置,假设第一个元素为最大元素,编写求两个数的最大值函数max,该函数包含两个参数,其中一个参数是存储最大值的变量,一个参数是数组元素,然后扫描整个数组,不断更新最大元素以及其所在的位置,最终找出该数组中最大元素及其所在的位置。

22 参数的传递——数组名作为函数参数 在C语言中的函数中,除了数组元素可以作为参数外,数组名也可以作为参数。与数组元素作为参数不同的是,数组元素作为参数时是将数组元素的值传递给函数的形参,而数组名作为参数时,向形参传递的是数组首元素的地址。

23 参数的传递——数组名作为函数参数 例5-9 用选择法对数组中的N个整数按从小到大的顺序进行排序。
解题思路:所谓的选择排序法就是:取待排序序列中的第1个元素,依次与序列中的其它元素进行比较,找出最小值与该值在数组中的位置,将最小值与第1个元素进行交换。然后取第2个元素,依次与序列中从第3个元素开始的元素进行比较,找出次小值与第2个元素进行交换。重复执行上述操作,直到所有元素都排好序为止。 void sort(int a[],int n){ int i,j,k,temp; for(i=0;i<n-1;i++){ k=i; for(j=i+1;j<n;j++) if(a[k]>a[j]) k=j; if(i!=k){ temp=a[i]; a[i]=a[k]; a[k]=temp; }

24 void sort(int a[],int n);
参数的传递——数组名作为函数参数 (1)用数组名作为函数参数,需要在主调函数和被调函数中分别定义数组,不能只在一方定义。如上例中的数组a是形参数组名,它与主调函数中的实参数组名score相对应。 (2)在C语言的编译系统中,并不检查形参中数组的大小,只是将实参数组的首地址传给形参,编译系统不为形参分配新的存储空间,两个数组共同占有相同的存储单元。 (3)由于编译系统中并不检查形参中数组的大小,因此可以在形参中数组名后加一个空括号即可。如上例中的sort函数的定义方式: void sort(int a[],int n); (4)实参数组应与形参数组类型一致。

25 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

26 函数的嵌套与递归调用 在C语言中,所有的函数是互相平行、相互独立的。不能在一个函数中定义另一个函数,也就是说函数不能嵌套定义。但C语言允许函数之间互相调用,同时允许被调用的函数中再调用其它的函数。在C语言中,允许被调用的函数进一步调用其它函数,称为函数的嵌套调用。在函数调用中,若一个函数直接或间接地调用自身,称作函数的递归调用。

27 函数的嵌套调用 int max4(int a, int b, int c, int d) { int temp1, temp2, temp;
例5-12 编写程序,从输入的4个整数找出其中的最大数。 解题思路:编写求两个数的最大值函数max2,再编写求4个数的最大值函数max4,在主调函数传递4个整数给max4中的形参。在max4函数中首先将4个数分为两组,找出两组数中的最大值后,再找出这两个最大值的最大值。 int max4(int a, int b, int c, int d) { int temp1, temp2, temp; temp1=max2(a,b); temp2=max2(c,d); temp=max2(temp1,temp2); return temp; } int max2(int a, int b) return a>b?a:b;

28 函数的嵌套调用 int main() { int a,b,c,d; int maxValue; printf("请输入4个整数:\n ");
scanf("%d%d%d%d", &a,&b,&c,&d); maxValue=max4(a,b,c,d); printf("4个数的最大值是:%d\n", maxValue); return 0; }

29 函数的递归调用 一个递归的问题可以分为“回溯”和“递推”两个阶段。一般地,递归函数的定义需要解决两点:(1)函数的结束条件;(2)函数的递归方式。 函数的结束条件表示递归不能无限制运行下去,必须在有限步内执行结束。而函数的递归方式则表明规模较大的问题解与规模较小的问题之间的关系,通过规模较小的问题解逐渐得到规模较大的问题解。

30 函数的递归调用 例5-14 编程用递归方法计算阶乘函数。 #include <stdio.h> int fac(int n);
int main(){ int n,result; printf("请输入整数n: "); scanf("%d", &n); result=fac(n); printf("%d!=%d\n", n,result); return 0; } int fac(int n){ if (n==1) return 1; else return n*fac(n-1); }

31 函数的递归调用 int fac(int n){ if (n==1) return 1; else return n*fac(n-1); }

32 函数的递归调用 例5-15 Hanoi塔问题。相传印度教的天神在创造世界时,建了一座神庙,庙里竖立了3根柱子。天神将64个直径大小不一的金盘子,按照从大到小的顺序依次套放在第一根柱子上,形成了一座Hanoi塔。天神让庙里的僧侣们将第一根柱子上的盘子借助第2根柱子全部移到第3根柱子上。同时规定:每次只能移动一个盘子;盘子只能在3根柱子上来回移动而不能放在他处;在移动过程中,3根柱子上的盘子必须始终保持大盘在下,小盘在上。天神说当这64个盘子全部移到第三根柱子上后,世界末日就要到了。试编程实现Hanoi塔上盘子的移动步骤。

33 函数的递归调用 解题思路:该问题没有明显的递推规律,因此解决问题关键是找出递归函数的定义方式。如果只有一个盘子,只需要将该盘子从A移动到C即可。如果有多于一个盘子,最终的目标是将这个盘子从A移动到C,并且仍然按照原来的大小顺序。因此需要将A柱上最大的盘子移动到C柱上最下面,这就需要将上面的盘子移动到B柱上。即移动需要分为三步:(1)将上面的个盘子借助C柱移动到B柱,将最大的盘子从A柱移动到C柱,将B柱上的个盘子借助A柱移动到C柱。

34 函数的递归调用 #include <stdio.h>
void hanoi(int n, char a, char b, char c); int main(){ int n; printf("请输入要移动的盘子数: "); scanf("%d", &n); hanoi(n, 'A','B','C'); return 0; } void hanoi(int n, char a, char b, char c){ if (n==1) printf("%c---->%c\n",a,c); else{ hanoi(n-1, a, c, b); hanoi(n-1, b, a, c); }

35 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

36 全局变量与局部变量 在不同位置定义的变量,其作用域是不同的。例如,有的变量是在函数内部定义的,当函数被调用时,该变量被分配存储空间,而当函数调用结束后,该变量所占的存储空间被编译系统收回,此时再引用该变量就会出现编译错误。因此,C语言中的变量有着不同的作用域和生存周期。根据变量的作用域,可以将变量分为全局变量和局部变量;根据变量的生存周期,可以将变量分为静态存储方式与动态存储方式。

37 全局变量与局部变量 所谓变量的作用域,指的是变量的空间有效性。如果一个变量是在函数内部定义的,则该变量是局部变量。如果变量是在函数外部定义的,称该变量为全局变量。全局变量与局部变量的主要区别有: (1)全局变量在程序开始时分配存储空间,在程序结束时释放;局部变量在函数被调用时分配存储空间,在函数调用结束时释放存储空间。 (2)局部变量只在定义该变量的函数内部有效,不能在函数外部引用该变量;而全局变量在定义后可以在程序的所有地方引用。 (3)对局部变量而言,只能在定义该变量的函数内部对其进行修改;而全局变量在定义以后,程序的任何地方都可以修改。 (4)若不给变量初始化,全局变量的初值为0(数值型数据),局部变量的初值为任意值。

38 全局变量与局部变量

39 全局变量与局部变量 例5-18 写出下列程序的运行结果。 #include <stdio.h> int n=5;
void test(){ int n=3; //此时的n为局部变量 n=n+1; //修改局部变量的值 printf("local variable:n=%d\n",n); } int main(){ n++; //修改全局变量的值 printf("global variable:n=%d\n",n); test(); return 0;

40 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

41 变量的存储方式 根据变量的时间有效性,变量可以分为静态存储方式和动态存储方式。静态存储方式,是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式,是指在程序运行期间根据需要进行动态的分配存储空间的方式。当一个C语言程序编译完成后,内存中将会为编译好的程序分配一部分存储空间。一般地,这部分存储空间可以分为三部分: (1)程序区:存放编译好的程序代码; (2)静态存储区:用来存储全局变量,当程序开始运行时为全局变量分配存储空间,程序运行结束时释放; (3)动态存储区:用来存放局部变量、函数调用的返回地址等。当函数调用结束后,这些存储空间将被自动释放。

42 变量的存储方式——自动变量 自动变量是C语言中变量和函数默认的存储类别,它的特点是:在函数调用时为其分配存储空间,在调用完成后自动释放这些存储空间。 auto 数据类型 变量名; #include <stdio.h> int main() { int a,b; auto int c; printf("请输入两个整数: "); scanf("%d%d",&a,&b); c=a+b; printf("%d+%d=%d\n",a,b,c); return 0; } int a; auto int a;

43 变量的存储方式——静态(static)变量
静态变量与局部变量的区别在于当函数调用结束后,动态变量所占的存储空间被释放,而静态变量所占的存储空间不被释放,因而局部变量的值得以保留,在下一次调用该函数时,该局部变量的初始值为上一次调用结束时的值。 static 数据类型 变量名;

44 变量的存储方式——静态(static)变量
#include <stdio.h> int main(){ int f(int); int i; for(i=0;i<2;i++) printf("result=%d\n",f(i)); return 0; } int f(int a){ static int b=3; b++; return a+b;

45 变量的存储方式——静态(static)变量
例5-20 编程计算从整数1到输入的整数m的阶乘。 解题思路:由于1!=1,2!=2*1!,…,m!=m*(m-1)!,所以定义fac函数。函数中定义静态局部变量f,并赋初值为1,表示求得的某个数的阶乘。多次调用该函数,每调用一次,f保留上一次得到的值,而参数的值是从1到m。 #include <stdio.h> int main(){ int fac(int); int i,m, result; printf("请输入一个整数: "); scanf("%d",&m); for(i=1;i<=m;i++) printf("%d!=%d\n", i, fac(i)); return 0; } int fac(int n){ static int f=1; f=f*n; return f; }

46 变量的存储方式——静态(static)变量
(1)静态变量存储在静态存储区,每次函数调用后不释放所占用的存储空间,因此静态变量的值得以保留;而自动变量存储在动态存储区,每次函数调用后需要释放所占用的存储空间; (2)静态存储变量只进行一次初始化操作,在后续的调用过程中将不再进行静态变量的初始化工作;而在函数的每一次调用时都需要对自动变量进行初始化。如果静态变量不进行初始化,其初始值为0或者为空字符,而自动变量如果不进行初始化,它的值是不确定的; (3)静态变量从本质上仍然属于局部变量,因此除了定义它的函数可以引用它以外,其它函数并不能引用它或者对它的值进行修改; (4)由于静态变量需要一直占用程序的存储空间,同时会降低程序的可读性,因此在编写程序中,不提倡使用静态变量。

47 变量的存储方式——寄存器(register)变量
在计算机运行程序时,所有的变量都要放到CPU内部的寄存器中。由于CPU内部的寄存器数量是有限的,因此需要频繁地在内存和寄存器之间进行数据的存取。考虑到寄存器中数据的操作速度远高于内存中数据的操作速度,因此经常把使用频繁的局部变量声明为寄存器变量,将其存储在CPU的寄存器中,这样可以有效提高程序的运行效率。这样的变量称为寄存器变量。 register 数据类型 变量名;

48 变量的存储方式——寄存器(register)变量
例5-21 求n!,当n的值很大时,可以把循环变量作为寄存器变量,以节省程序的执行时间。 #include <stdio.h> int main() { int n; register int i,f; f=1; scanf("%d", &n); for(i=1;i<=n;i++) f=f*i; printf("%d!=%d.\n", n,f); return 0; } (1)由于计算机中寄存器的数量是有限的,因此不能定义任意多个寄存器; (2)只有自动变量和形式参数可以定义为寄存器变量,静态局部变量不能定义为寄存器变量。

49 变量的存储方式——外部(extern)变量
在程序中,全局变量的作用域是从定义它的位置开始,到整个程序结束。如果想在定义全局变量前的函数中引用这些变量,需要对全局变量进行声明,声明该全局变量为“外部变量”,表明该变量是一个已经定义的外部变量。 extern 数据类型 变量名;

50 变量的存储方式——外部(extern)变量
#include <stdio.h> int f(int, int); int main(){ extern int a,b; //对外部变量进行声明 printf("%d和%d的和是%d.\n", a,b,f(a,b)); return 0; } int a=12, b=23; int f(int x, int y){ return x+y;

51 变量的存储方式——外部(extern)变量
当一个C程序由多个文件组成时,也可以利用extern在一个文件中引用另一个文件中定义的外部变量。 #include <stdio.h> int x; //对函数进行外部函数声明 extern int pow(int m); int main() { int y,n; printf("输入两个整数x和n:\n"); scanf("%d%d",&x,&n); y=pow(n); printf("%d^%d=%d\n",x,n,y); return 0; } extern x; //对x进行外部变量声明 int pow(int n) { int result=1; int i; for(i=0;i<n;i++) result=result*x; return result; }

52 基本内容 概述 函数的定义与调用 参数的传递 函数的嵌套与递归调用 全局变量与局部变量 变量的存储方式 本章小结

53 本章小结 本章介绍了函数的定义、调用及函数的声明; 本章介绍了函数的值传递和地址传递两种方式; 本章介绍了函数的嵌套调用与递归调用;
本章介绍了全局变量和局部变量在程序中的作用; 本章介绍了变量的生存周期,介绍了自动变量、静态变量、寄存器变量和外部变量。

54 本章小结 本章结束


Download ppt "第5章 函数与模块化设计 学习目的与要求: 掌握函数的定义及调用方法 理解并掌握参数的传递方法 理解函数的嵌套与递归调用"

Similar presentations


Ads by Google