770 likes | 862 Views
第 7 章 函数. 第 7 章 函数. 7.1 函数的基本概念 7.2 使用函数编写程序 7.3 数组作为函数参数 7.4 变量的生命周期与作用域 7.5 递归与递归函数. 本章要点. 怎样定义函数?如何调用函数?如何声明函数? 什么是函数的参数?在函数调用时,参数是如何传递的? 在使用函数的过程中,要注意哪些问题? 如何使用局部变量和全局变量? 什么是静态局部变量? 递归的定义与调用. 为什么使用函数. int main() { :::::: x=x*x*x; y=y*y*y; z=z*z*z;
E N D
第7章 函数 7.1 函数的基本概念 7.2 使用函数编写程序 7.3 数组作为函数参数 7.4 变量的生命周期与作用域 7.5 递归与递归函数
本章要点 • 怎样定义函数?如何调用函数?如何声明函数? • 什么是函数的参数?在函数调用时,参数是如何传递的? • 在使用函数的过程中,要注意哪些问题? • 如何使用局部变量和全局变量? • 什么是静态局部变量? • 递归的定义与调用
为什么使用函数 int main() { :::::: x=x*x*x; y=y*y*y; z=z*z*z; ans1=x+y+z; a=a*a*a; b=b*b*b; c=c*c*c; ans2=a+b+c; ::::: } int main() { :::::: ans1=cube(x,y,z); ans2=cube(a,b,c); ::::: } 重复多次的同一计算类型 int cube(int a,int b, int c) { int ans; ans=(a*a*a)+(b*b*b)+(c*c*c); return ans; } ans 函数 main函数 函数可以把相对独立的某个功能抽象出来,使之成为程序中的一个独立实体。可以在同一个程序或其他程序中多次重复使用
函数机制的优点 • 使程序变得更简短而清晰 • 有利于程序维护 • 可以提高程序开发的效率 • 提高了代码的重用性
标准库函数和自定义函数 标准库函数: • 由C语言系统提供; • 用户无须定义,也不必在程序中作类型说明; • 只需在程序前包含有该函数定义的头文件。 自定义函数: • 用户在程序中根据需要而编写的函数。
7.1 函数的基本概念 7.1.1 求最大值 7.1.2 函数的定义 7.1.3 函数的调用 7.1.4 参数的按值传递机制
7.1.1 求最大值 试试看:从键盘输入三个整数a、b、c,求出其中的最大值。定义一个计算最大值的函数max(a,b),返回a,b的最大值。 思路分析:三个整数求最大值,两两判断。 if( a > b) max = a; else max = b;
回顾我们在第三章所学的内容 int main() { int a,b,c,z,maxi; scanf("%d%d%d",&a,&b,&c); if( a>b) z=a; else z=b; if( z>c) maxi=z; else maxi=c; printf("max=%d\n",maxi); return 0; } 程序7-1 #include "stdio.h" int main(void) { int a, b, c, z, maxi; int max(int a, int b);scanf("%d%d%d",&a,&b,&c); z = max(a, b); //函数调用maxi = max(z, c); printf("max=%d\n", maxi); return 0; } 定义一个计算最大值的函数max(a,b),返回a,b的最大值。 int max (int a, int b) /* 函数首部 */ { if( a > b) return a; else return b; }
7.1.2 函数的定义 • 函数是指完成一个特定工作的独立程序模块,包括有标准库函数和用户自定义函数。 • 库函数:由C语言系统提供定义 如scanf()、printf()、sqrt( )等函数 • 自定义函数:需要用户自己定义 如求最大值的函数max(a,b) • main()也是一个函数,C程序由一个或多个函数构成,有且仅有一个main()函数。
程序中一旦调用了某个函数,该函数就会完成一些特定的工作,然后返回到调用它的地方。程序中一旦调用了某个函数,该函数就会完成一些特定的工作,然后返回到调用它的地方。 • 有返回值函数:函数经过运算,得到一个明确的运算结果,并需要回送该结果。例如,函数max(a,b)返回a,b的最大值。 • 无返回值函数:函数完成一系列操作步骤,不需要回送任何运算结果。
函数类型 函数名(形参表)/* 函数首部 */ { /* 函数体 */ 函数实现过程 return 表达式; } 1.有返回值的函数定义 函数返回值的类型 没有分号 只能返回一个值 把函数运算的结果回送给主函数
类型1 参数1 ,类型2 参数2 ,……,类型n 参数n 参数之间用逗号分隔,每个参数前面的类型都必须分别写明 形参 int max (int a, int b) /* 函数首部 */ { if( a > b) return a; else return b; } 函数类型 函数名(形参表){ 函数实现过程 return 表达式; }
int max (int a, int b) /* 函数首部*/ {/* 函数体,写在一对大括号内 */ if( a>b) return a; else return b; /* 返回运算结果*/ } 分析函数的定义 函数类型 函数名 形参表 与函数类型一致
void函数名(参数表) /* 函数首部 */ { /* 函数体 */ 函数实现过程 return; /* 可以省略return */ } 2.无返回值的函数定义 这类函数通常用于屏幕输出等 表示不返回结果 不能省略,否则函数类型被默认定义为int
试试看:输出一个n行的直角三角形 * ** *** **** ***** void prt (int n) { int i; for (i = 1; i <= n; i++) printf("*"); putchar ('\n'); } 程序7-2 #include <stdio.h> int main (void) { int i, n; void prt (int n); scanf("%d", &n); for(i = 1;i <= n;i++) prt(i); return 0; }
7.1.3 函数的调用 • 定义一个函数后,就可以在程序中调用这个函数。 • 调用标准库函数时,在程序的最前面用#include命令包含相应的头文件。 • 调用自定义函数时,程序中必须有与调用函数相对应的函数定义。 • 切记:实参的个数、类型和顺序,应该与被调用函数所要求的参数个数、类型和顺序一致,才能正确地进行数据传递。
使用返回值: z=max(a,b); 完成操作: prt(i); 1.函数调用的形式和过程 函数名(实参表) 常量、变量、表达式 常用于有返回值的函数的调用 常用于void类型函数的调用
计算机在执行程序时,从main函数开始执行,如果遇到某个函数调用,main函数被暂停执行,转而执行相应的函数,该函数执行完后,将返回main函数,然后再从原先暂停的位置继续执行。计算机在执行程序时,从main函数开始执行,如果遇到某个函数调用,main函数被暂停执行,转而执行相应的函数,该函数执行完后,将返回main函数,然后再从原先暂停的位置继续执行。 • 函数遇return或最后的右大括号,返回主函数。
分析函数调用的过程 #include <stdio.h> int main() { int a,b,c,z,maxi; int max(int a,int b); scanf("%d%d%d",&a,&b,&c); z=max(a,b); maxi=max(z,c); printf("max=%d\n",maxi); return 0; } int max (int a, int b) { if( a>b) return a; else return b; } 到VC中单步执行该程序 注意F10和F11的区别:F11可进入子函数跟踪,F10把子函数作为一个语句一次执行完。 调用函数 实参形参 执行函数中的语句 返回调用它的地方
函数嵌套调用 void f1() { :::::::: f2(); :::::::: } #include<stdio.h> void main() { ::::::::: f1(); :::::::: :::::::: } void f2() { ::::::::: ::::::::: } 从一个函数调用另一个函数称为函数的嵌套调用
函数递归调用 /* 此函数用于计算 a 的阶乘 */ int factorial(int a) { if (a == 1) return 1; else { a = a * factorial(a-1); return a; } } 在一个函数体内调用自身称为函数的递归调用
函数类型 函数名(参数表); int max( int a, int b); void prt ( int n); 函数必须先定义后调用,将主调函数放在被调函数的后面,就像变量先定义后使用一样。 如果自定义函数在主调函数的后面,就需要在函数调用前,加上函数原型声明。 函数声明:说明函数的类型和参数的情况,以保证程序编译时能判断对该函数的调用是否正确。 2.函数原型声明 只写函数定义中的第1行(函数首部),并以分号结束。
3.函数结果返回 • 函数返回的两种情况 • 完成确定的运算,有一个运算结果返回给主调函数。 • 完成指定工作,没有确定的运算结果需返回给主调函数(函数类型void)。 • 函数结果返回的形式: • return 表达式; • return (表达式);
7.1.4 按值传递机制 • 函数定义时的参数被称为形式参数(简称形参) int max(int a, int b); • 函数调用时的参数被称为实际参数(简称实参) z=max(a, b); • 实参形参 • 在参数传递过程中,实参把值复制给形参。 • 形参和实参一一对应:数量一致,类型一致,顺序一致 • 形参:变量,用于接受实参传递过来的值 • 实参:常量、变量或表达式 单向传递
7.2 使用函数编写程序 7.2.1 素数表 7.2.2 验证哥德巴赫猜想 7.2.3 组合数
7.2.1 素数表 解决问题:输出100以内的全部素数,每个素数占6列。要求定义和调用函数prime (m)判断m是否为素数,当m为素数时返回1,否则返回0。 思路分析:对2~100之间的每个数进行判断,若是素数,则输出该数。 for(m = 2; m <= 100; m++) if (m是素数) printf("%6d", m);
回顾我们在第四章所学的内容 for(m=2; m<=100; m++) { n=sqrt(m);flag=1; for(i=2; i<=n; i++) { if(m%i==0) { flag=0; break; } } if(flag!=0)printf("%6d", m); } 判断m是否是素数,若是则输出m
#include <stdio.h> #include <math.h> int main(void) { int m; int prime (int m); for(m = 2; m <= 100; m++) { if ( prime(m) != 0 ) printf("%6d", m ); } printf ("\n"); return 0; } 程序7-3 int prime (int m) { int i, n; if ( m == 1 ) return0; n = (int)sqrt (m); for( i = 2; i <= n; i++) { if (m % i == 0) return0; } return1; }
7.2.2 验证哥德巴赫猜想 解决问题:验证哥德巴赫猜想,任意输入一个不小于6的偶数,将其表示成两个素数的和,如: 6=3+3;8=3+5;18=7+13 思路分析: 该问题可以通过调用7-3中的prime函数来实现。因为每个偶数都可以分解为两个奇数之和,所以调用两次prime函数就能方便的判断出这两个奇数是否全部为素数,如果是,结论即成立,如果不是,则继续判断下一组奇数。
程序7-4 int main() { int even, i; scanf("%d", &even); for(i = 3;i <= even/2;i = i+2) if(prime(i) && prime(even-i)) printf("%d %d\n", i, even-i); return 0; } #include <stdio.h> #include <math.h> int prime (int m) { int i, n; if ( m == 1 ) return 0; n = (int)sqrt (m); for( i = 2; i <= n; i++) { if (m % i == 0) return 0; } return 1; }
7.2.3 组合数 “组合”是一个数学名词,是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序的因素,它源于人们对于客观世界中存在的数与形及其关系的发现和认识。我们的祖先在很早就开始研究组合问题了。例如,《易经》中用十个天干和十二个地支以六十为周期来记载月和年,以及杨辉三角。我们在小时候经常玩的七巧板,用7块小小的积木可以拼出259种图形,其实也是一个组合问题。
7.2.3 组合数 解决问题:编写计算组合数的程序 求阶乘: unsigned long fact(unsigned int n) { unsigned int i; unsigned long result = 1; for(i = 2; i <= n; i++) result *= i; return result; }
程序7-5 #include “stdio.h” int main(void) { int m, k; double p; scanf(“%d%d”, &m, &k); p=(double)fact(m) / (fact(k) * fact(m - k)); printf(“p=%.0f\n”, p); return 0; }
7.3 数组作为函数参数 7.3.1 最大进制数 7.3.2 数组做函数参数 7.3.3 形参数组的实质 7.3.4 多个二进制数排序 7.3.5 集合的合并——利用有序关系简化问题
7.3.1 最大进制数 解决问题:输入n个非负整数,每个数的进制由其后面的数字k指定,k>=2且k<=10,输出最大的数对应的十进制数。 思路分析: (1)k进制数要用字符串来存储,比如一个9位的十进制数对应的二进制数长度是30位左右,如果用%d来获取就会产生类型溢出。 (2)调用函数kToD()将K进制数转化为十进制整数。 (3)利用第四章所学的方法求出其中的最大值。
程序7-6 int main() { int i, n, k, b[50], max; char a[50]; scanf("%d", &n); for(i = 0; i < n; i++) { scanf("%s%d", a, &k); b[i] = kToD(a, k); } max = b[0]; for(i = 0; i < n; i++) { if(b[i] > max) max = b[i]; } printf("%d\n", max); return 0; } #include<stdio.h> #include<string.h> int kToD(char str[], int k) { int i, sum; sum = str[0] - '0'; for(i = 0; i<strlen(str)-1; i++) sum = sum*k+(str[i+1]-'0'); return sum; }
7.3.2 数组做函数参数 • 因为数组名代表该数组的首地址,用数组名作实参时,是以传址方式调用函数,也就是说是把实参数组的首地址赋给形参; • 实际上形参数组和实参数组为同一数组,共同拥有一段内存空间。
b b[0] array b[4] 求数组元素的和 int sum (int array[ ], int n) { int i, s = 0; for(i=0; i<n; i++) s += array[i]; return(s); } int main( ) { int i; int b[5] = {1, 4, 5, 7, 9}; printf("%d\n", sum(b, 5)); return 0; } 形参数组和实参数组为同一数组,共同拥有一段内存空间 array[0] …… array[4]
7.3.3 形参数组的实质 一般变量作参数:传递的是数值,形参的值的改变不影响实参。 数组做参数:传递的是数组的首地址,形参数组和实参数组实质上是同一块内存区域,形参数组发生改变,实参数组也随之发生改变。
b y a x 3 3 5 5 5 3 以下程序的运行结果? #include<stdio.h> void swap(int x, int y) { int t; t = x; x = y; y = t; } int main() { int a = 3, b = 5; swap(a, b); printf("%d %d\n", a, b); return 0; } 运行结果: 3 5
解决问题:通过函数实现n个学生成绩的排序 程序7-7: #include <stdio.h> #define N 100 void sort( double grade[ ], int n); int main() { double grade[N]; int i,n; scanf("%d", &n); for(i=0;i<n;i++) scanf("%lf",&grade[i]); sort(grade, n); printf("\n 排序后的成绩为:\n"); for(i=0;i<n;i++) printf("%5.2f ",grade[i]); printf("\n"); } void sort( double a[ ], int n) { int i,j; double temp; for(i=1;i<N;i++) { for(j=0;j<N-i; j++) { if(a[j] > a[j+1]) { temp = a[j+1]; a[j+1] = a[j]; a[j] = temp; } } } } 传递数组时形参要声明为数组(指针),并且数组长度要和实参相等,或缺省长度 传递数组时实参用数组名 对形参数组排序,就是对实参数组排序
7.3.4 多个二进制数排序 解决问题:输入三个二进制数,要求将这三个二进制数对应的十进制整数按从小到大的顺序输出。 思路分析: 可以借鉴程序7-4的kToD函数,该问题中全部为二进制数,所以可以通过对kToD函数的改写设计bToD函数。 读入三个字符串s1,s2,s3分别调用三次bToD函数得到对应的三个十进制数,对这三个十进制数排序输出。
程序7-8 #include <stdio.h> #include <string.h> #define N 32 /* 把一个二进制数转换成对应的十进制数 */ int bToD(char str[]) { int i, d; d = 0; for(i = 0; str[i] != '\0' ; i++) d = d * 2 + (str[i] – '0'); return d; }
程序7-8 int main(void) { char str1[N], str2[N], str3[N]; int a, b, c, temp; scanf("%s%s%s", str1, str2, str3); a = bToD(str1); b = bToD(str2); c = bToD(str3); if(a > b) /* 三个数两两比较排序 */ { temp = a; a = b; b = temp; } if(a > c) { temp = a; a = c; c = temp; } if(b > c) { temp = b; b = c; c = temp; } printf("%d %d %d\n", a, b, c); return 0; }
7.3.5 集合的合并——利用有序关系简化问题 解决问题:将两个整数集合A和B合并为集合C。并将集合C中元素按升序输出。你应该知道,集合中不允许有重复元素。 思路分析: 两个集合合并并且要排序,可以调用程序7-7中的sort()函数先对集合a、b分别排序,然后利用有序序列合并的思想把两者中的数据合并到集合C中,因为集合中的数据具有唯一性,所以如果a、b中有相同的数据,只保存一个。
程序7-9 #include<stdio.h> #define N 100 void sort( int a[ ], int n) { int i, j; int temp; for(i = 1; i < n; i++) /*冒泡排序*/ for(j = 0; j < n-i; j++) if(a[j] > a[j+1]) { temp = a[j+1]; a[j+1] = a[j]; a[j] = temp; } }
程序7-9 int main(void) { int i, j, k, m, n; int a[N/2], b[N/2], c[N]; scanf("%d%d", &m, &n); for(i = 0; i < m; i++) scanf("%d", &a[i]); for(i = 0; i<n; i++) scanf("%d", &b[i]); /*调用两次排序函数对两个数组进行排序,得到升序序列 */ sort(a, m); sort(b, n);
程序7-9 /* a、b数组中的元素从小到大两两比较,选择合适的数据插入到c中 */ i = 0, j = 0, k = 0; while(i < m && j < n) { if(a[i] < b[j]) /*如果a[i]<b[j],则存入a[i] */ c[k++] = a[i++]; else if(a[i] > b[j]) /*如果a[i]>b[j],则存入b[j] */ c[k++] = b[j++]; else /* 如果a[i]=b[j],则存入a[i],舍去b[j] */ { c[k++] = a[i]; i++; j++; } }
程序7-9 while(i < m) c[k++]=a[i++]; while(j < n) c[k++]=b[j++]; for(i = 0; i < k; i++) /* 输出c中的信息 */ printf("%d ", c[i]); printf("\n"); return 0; }