610 likes | 743 Views
第十七讲 函数. 北京大学信息学院. 本讲内容. 函数的定义 函数说明和函数体 函数的调用(递归) 参数传递:实参和形参 返回值 变量的作用域:全局变量和局部变量 函数示例 分治思想. C 语言与函数. C 语言是一种函数式语言,它的一个函数实际上就是一个功能模块 ——C 程序的基本组成是函数。 一个 C 程序是由一个固定名称为 main 的 主函数 和若干个其他函数(可没有)组成。 一个 C 程序必须有一个、也只能有一个主函数。 主函数在程序中的位置可以任意,但程序执行时总是从主函数开始,在主函数内结束。 C 语言程序是通过 函数调用 来完成复杂功能。.
E N D
第十七讲 函数 北京大学信息学院
本讲内容 • 函数的定义 • 函数说明和函数体 • 函数的调用(递归) • 参数传递:实参和形参 • 返回值 • 变量的作用域:全局变量和局部变量 • 函数示例 • 分治思想
C语言与函数 • C语言是一种函数式语言,它的一个函数实际上就是一个功能模块——C程序的基本组成是函数。 • 一个C程序是由一个固定名称为main的主函数和若干个其他函数(可没有)组成。 • 一个C程序必须有一个、也只能有一个主函数。 • 主函数在程序中的位置可以任意,但程序执行时总是从主函数开始,在主函数内结束。 • C语言程序是通过函数调用来完成复杂功能。
函数的定义 函数的定义语句如下: 返回值类型函数名 ([参数1类型参数名1, 参数2类型参数名2,…]) { 语句1;// 语句可能与参数有关 语句2;// 语句可能与参数有关 …… return 返回值表达式;//该表达式的值的类型必需和返回值类型一致 // 如果返回值类型为void,则不需return语句,或者返回值表达式为空 } 参数:函数在完成其功能时需要由函数调用者传递给函数的数据。 返回值:函数在完成其功能后得到的结果。
函数定义的例子 下面是一个函数定义的例子:两个数相加 void add2num() { int x, y, z; scanf(“%d%d”, &x, &y); z = x+y; printf(“%d”, z); return; //或者不要此语句 } int add(int x, int y) { int z; z = x+y; returnz; }
函数的调用 在C语言中,函数是语句的一种组织方式。把完成某一特定功能的语句按照一定的规则组合在一起,起一个名字,就构成了函数。在一段程序中,一个函数可以当作一条语句来运行,这称为函数的调用(函数调用语句)。 一个函数在实际被调用时,它所包含的语句才会执行,并且,它的参数的具体取值也是在调用时给定的。在函数执行后,它的执行结果(如果有的话)可以通过返回值返回给调用它的程序。
函数的调用 int add(int x, int y) { int z; z = x + y; return z; } int minus(int x, int y) { return x-y; }
函数的调用 void main() { int n1,n2; scanf(“%d %d”,&n1,&n2); n1 = add(n1,n2); //函数调用语句 n2 = minus(n1,n2); //函数调用语句 if(n1>0 && n2>0) { printf(“%d\n”, add(n1,n2)); //包含函数调用的语句 } else { printf(“%d\n”, minus(n1,n2)); //包含函数调用的语句 } }
函数调用的过程 int max(int x, int y) { if(x>=y) return x; else return y; } void main( ) { int x=0, y=0, z=0; x = 20; y = 45; z = max(x, y); …… }
函数的调用 • 一个函数在调用前必须是已经定义过的(“先定义,后使用”原则)。 • 函数体 • 函数说明
函数说明和函数体 int multiple(int x, int y); // 函数说明 declaration void main() { int a=0,b=0, c; scanf(“%d %d”, &a, &b); c = multiple(a, b); // 函数调用 printf(“%d\n”,c); } //main()的返回值类型为void,因此没有return语句 int multiple(int x, int y) //函数体 ,在函数调用之后给出 { int z; z = x*y; return z; }
函数说明和函数体 另一种形式:不需要函数说明 int multiple(int x, int y) //函数体 ,在函数调用之前给出 { int z; z = x*y; return z; } void main() { int a=0,b=0, c; scanf(“%d %d”, &a, &b); c = multiple(a, b); // 函数调用 printf(“%d\n”,c); }
函数的递归调用 一个关于递归的故事 一个没有去过北京的人问:天安门是什么样子?去过北京的人答道:天安门有个城楼,城楼上有个国徽,国徽里有个天安门,天安门有个城楼,城楼上有个国徽,国徽里有个天安门, ……
函数的递归调用 • 递归是常用的编程技术,其基本思想是“自己调用自己”。 • 数学上最常见、最简单的递归问题就是自然数的阶乘。 • n=1 n! = 1; • n>1 n! = n * (n-1)!; int factorial(int n) { if(n<=0) return –1; if(n==1) return 1; return n*factorial(n-1); //递归调用 }
函数的递归调用 适合用递归方法求解的问题 • 满足两个条件 • 出口条件:在某一条件下,递归不再继续 • 递推公式 – 必需向出口条件推进:后续的情况可由前面的状态推出 • 阶乘:n!=1,n=1;n! = n*(n-1)!,n>1 • Fibonacci数列:F1 = F2 = 1;Fn = Fn-1 + Fn-2 (n>=3) • 解决问题的思想 • 将问题逐步向更小规模的同类问题推进
函数的递归调用 • 其他递归问题 • 河内塔( Hanoi Tower)问题:这是一个流传很久的游戏。 • 1. 有三根杆子A,B,C。A杆上有n只碟子 • 2. 每次移动一块碟子,小的只能叠在大的上面 • 3. 把所有碟子从A杆经C杆全部移到B杆上. • 递归求解: • 1. 若只有一只碟子,直接将它从A杆移到B杆; • 2. 把n-1只碟子从A杆经B杆移动到C杆,将A杆上第n只碟子移到B杆;然后再将n-1只碟子从C杆经A杆移到B杆。
函数的递归调用 其他递归问题 8皇后问题:把8个皇后放在8 x 8的棋盘上,使得任何一个都不将其他7个的军。 骑士巡游问题:给出一个n x n的棋盘,一位骑士按国际象棋的规则移动——放在第(0,0)格里,找出一种可以走遍整个棋盘的方案(如果存在的话)。即做n2-1次移动,使得棋盘上每个格子都恰好只被访问一次。
函数参数:实参和形参 在调用函数时,向函数传递的参数—实参: • 可以是数值。 • 也可以是内存地址(指针)。 函数内部进行运算的参数—形参 实参形参:参数类型必须一一对应,采用值传递
指针形参 void swap(int *x, int *y) { int tmp = 0; tmp = *x; *x = *y; *y = tmp; }
指针实参 void main( ) { int a=0, b=0; a = 20; b = 45; if(a<b) swap(&a, &b); …… }
此时会有什么效果? void main( ) { int a=0, b=0; a = 20; b = 45; if(a<b) swap(a, b); …… } void swap(int x, int y) { int tmp = 0; tmp = x; x = y; y = tmp; }
20 45 45 20 0 20 参数 x y 变量tmp 代码段 swap tmp = x; x = y; y = tmp; if(a<b) swap(a,b);
再看scanf()和printf()函数 • scanf(char *control, ……) • 每一个希望从键盘中输入的变量,都需要使用指针传递参数。 • printf(char *control, ……) • 当输出变量是字符串时,也需要使用指针传递参数。
函数的返回值 • 函数执行完以后可以向调用它的程序返回一个值,表明函数运行的状况。很多函数的功能就是对参数进行某种运算,之后通过函数返回值给出运算结果。 • 函数的返回值可以有不同的类型,返回值类型在函数定义时说明。 • 函数的返回值是通过 return 语句来实现的,一旦一个函数执行了return语句,则表明函数任务已经完成了,函数到此结束了。 • 在main()函数中执行了return语句,表明什么?
函数的返回值 int min(int x, int y); //返回值类型为int,有两个整型参数,函数名为min double calculate( int a, double b); //返回值类型为double,有一个整型参数,一个双精度浮点型参数 //函数名为calculate char judge( ); //返回值类型为char,没有参数,函数名为judge void swap(int *x, int *y); //返回值类型为void,表示不返回任何值,有2个整型指针参数 //函数名为swap
函数的返回值 int add(int x, int y) { int z; z = x + y; return z; } int minus(int x, int y) { return x-y; } void swap(int *x, int *y) { int tmp = 0; tmp = *x; *x = *y; *y = tmp; }
函数的返回值 问题:当需要从函数中返回多个数据时,该如何处理? 使用指针形参!
变量的作用域:全局变量和局部变量 • 在程序中,每一个变量都有一个可以起作用的范围,这个范围就是变量的作用域。 • 根据变量作用域的不同, 变量 可分为 全局变量和局部变量。
全局变量:在函数之外定义的变量 #include <stdio.h> int total; //全局变量 int tryit(int a) { if(a>100 && a<200 && a%2==0 ) return a; total++; return 0; } void main() { int n, j; total = 0; for( j=0; j<1000; j++ ) { scanf(“%d”, &n); printf(“%d\n”, tryit(n)); } printf(“%d\n”, total); } • 全局变量的作用域: • 从变量定义开始,到源程序结束。
局部变量:在代码段之内定义的变量 #include <stdio.h> int calculate(int a) { int n = a; if(a<100 && a>=0) n = 2*a; if(a>=200) n = a*a; return n; } void main() { int n, j, c; c = 300; for( j=0; j<1000; j++ ) { int c; scanf(“%d”, &n); c = calculate(n); printf(“%d\n”, c); } } • 局部变量的作用域: • 从变量定义开始,到当前代码段结束。
变量的作用域 • 参数pp的作用域: • 整个函数 全局变量作用域: 从变量定义处开始,一直到整个源文件结束。 不同源文件之间:extern 局部变量作用域: 从变量定义处开始,到第离它最近的左大括号所匹配的右大括号为止。
#include <stdio.h> int image[10000][10000]; void main() { int i, j; for( i=0; i<10000; i++ ) { for( j=0; j<10000; j++ ) { scanf(“%d”, &image[i][j]); } } } 全局变量和局部变量 • 当需要静态分配较大的内存空间时,需要使用全局变量。 #include <stdio.h> void main() { int i, j, image[10000][10000]; for( i=0; i<10000; i++ ) { for( j=0; j<10000; j++ ) { scanf(“%d”, &image[i][j]); } } }
函数示例:假币问题 问题描述 赛利有12枚银币。其中有11枚真币和1枚假币。假币看起来和真币没有区别,但是重量不同。赛利不知道假币比真币轻还是重。于是他向朋友借了一架天平。赛利希望称三次就能找出假币并且确定假币是轻是重。例如:如果赛利用天平称两枚硬币,发现天平平衡,说明两枚都是真的。如果赛利用一枚真币与另一枚银币比较,发现它比真币轻或重,说明它是假币。经过精心安排每次的称量,赛利保证在称三次后一定能够确定假币。 任务 编写一个程序,根据三次称量的结果,确定哪一个是假币,并指出其轻重。
函数示例:假币问题 输入 输入有三行,每行表示一次称量的结果。赛利事先将银币标号为A-L。每次称量的结果用三个以空格隔开的字符串表示: 天平左边放置的硬币天平右边放置的硬币平衡状态 其中平衡状态用“up”, “down” 或 “even” 表示,分别为右端高、右端低和平衡。天平左右的硬币数总是相等的。
函数示例:假币问题 输出 输出哪一个标号的银币是假币,并说明它比真币轻还是重。