第 8 讲 函数的设计方法

102
程程程程程-2005程程 《》 1 第8第 第第第第第第第 程程程 2005 程 11 程 2 程 /9 程

Upload: sani

Post on 21-Jan-2016

79 views

Category:

Documents


0 download

DESCRIPTION

第 8 讲 函数的设计方法. 周水庚 2005 年 11 月 2 日 /9 日. 提要. 函数形参 (Formal parameters of function) 函数说明 (Function declaration) 函数指针 (Function pointer) 及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例. 提要. 函数形参 指针类型、数组类型、字符指针 函数说明 函数指针及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例. 函数形参. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 1

第 8 讲 函数的设计方法

周水庚

2005 年 11 月 2 日 /9 日

Page 2: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 2

提要 函数形参 (Formal parameters of function) 函数说明 (Function declaration) 函数指针 (Function pointer) 及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 3: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 3

提要 函数形参

指针类型、数组类型、字符指针 函数说明 函数指针及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 4: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 4

函数形参 函数设置形参的目的是让函数被调用时,能从调

用处获得数据或指针信息 C 语言关于函数形参遵守以下规定

函数调用时,形参从实参获得初值 函数形参可看作函数内部的局部变量,函数体内对形

参的修改不会影响实参本身 函数形参的作用由形参的类型确定

基本类型、指针类型、数组类型 当函数的形参是某种指针类型或数组类型时,形参从

实参处得到某环境变量的指针,函数体就能通过指针形参间接引用环境变量,能改变环境变量的值

Page 5: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 5

指针类型形参 指针形参从实参处得指针值。它使函数得到了调用

环境中某变量的指针,函数就可用这个指针间接访问函数之外的变量,或引用其值,或修改其值。因此,指针类型形参为函数改变调用环境中的数据对象提供了手段

以交换两个整型变量值的函数 swap() 为例说明指针形参的作用和用法 函数 swap() 的功能是交换两个整型变量的值,函数 sw

ap() 设置了两个能指向整型变量的指针形参。在函数 swap() 的体中,用指针形参间接访问它们所指向的变量。调用函数 swap() 时,提供的两个实参必须是要交换值的两个变量的指针,而不再是变量的值

Page 6: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 6

指针类型形参示意程序 -1 对于简单类型形参,实参向形参传值,函数不能改

变实参变量值的示意程序 #include <stdio.h> void paws(int u, int v) { int t = u; u = v; v = t; printf(“In paws: u = %d, v = %d\n”, u, v); } void main() { int x = 1, y = 2; paws(x, y); printf(“In main: x = %d, y = %d\n”, x, y); }

Page 7: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 7

指针类型形参示意程序 -2

void swap-1(int u, int v) { int t; t = u; u = v; v = t; }

void swap-2(int *pu, int *pv) { int t; t = *pu; *pu = *pv; *pv = t; }

#include <stdio.h>void swap-1(int u, int v); void swap-2(int *pu, int *pv);void main(){ int a = 1, b = 2; printf(”Befor swap. a = %d\tb = %d\n”, a, b); swap-1(a, b); /* 以变量的值为实参 */ printf(“After swap: a = %d\tb = %d\n”, a, b); swap-2(&a, &b); /* 以变量的指针为实参 */ printf(“After swap: a = %d\tb = %d\n”, a, b);}

Page 8: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 8

指针类型形参 ( 续 ) 如希望函数能按需要任意引用指定的变量,

需要在三个方面协调一致 函数应设置指针类型的形参 函数体必须通过指针形参间接访问变量,或引用

其值或修改其值 调用函数时,要以欲改变值的变量的指针为实参

调用函数

Page 9: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 9

#include<stdio.h>void f1(int x, int y){int t=x;x=y;y=t;}void f2(int *x, int *y){int t=*x; *x=*y; *y=t;}void f3(int **x, int **y){int *t=*x; *x=*y;*y=t;}Void main(){ int x=1, y=2; int *xpt=&x,*ypt=&y;

printf(First: x=%d y=%d\n\n,x,y);

int x=1, y=2;f1(x,y);printf(After call f1(): x=%d y=%d\n\n,x,y);

int x=1, y=2;f2(&x,&y);printf(After call f2(): x=%d y=%d\n\n,x,y);

int x=1, y=2;printf(Before call f3(): x=%d y=%d\n\n,x,y);printf(Before call f3(): *xpt=%d *ypt=%d\n\n,*xpt,*ypt);

f3(&xpt,&ypt);printf(After call f3(): x=%d y=%d\n\n,x,y);printf(After call f3(): *xpt=%d *ypt=%d\n\n,*xpt,*ypt);

}

1, 2

2, 1

1, 2

1, 21, 2

2, 11, 2

Page 10: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 10

数组类型形参 使函数能处理不同的成组变量,应使用数组类型形参 对应数组形参的实参是数组某元素的地址,最通常的情况是

数组首元素的地址。由于数组名能代表数组首元素的地址,所以常用数组名实参对应数组形参

例如,下面定义的函数 sum() 用于求数组的前 n 个数之和 int sum(int a[], int n) { int i, s; for(s = 0, i = 0; i < n; i++) s += a[i]; return s; }

函数 sum 有两个形参,一个形参是数组类型的,用于对应可变的实在数组;另一个形参是整型的,用于指定求和数组的元素个数

Page 11: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 11

数组类型形参 ( 续 ) 前面定义的函数 sum() 用于求数组的前 n 个数之和

利用函数 sum() ,如有以下变量定义 int x[] = {1, 2, 3, 4, 5}; int i, j; 则语句 i = sum(x, 5); j = sum(&x[2], 3);

printf("i = %d\nj = %d\n", i, j); 将输出 i = 15 j = 12 函数调用 sum(x,5) 将数组 x 的地址 (&x[0]) 传送给形参 a ;

函数调用 sum(&x[2], 3) 将数组 x 中的 x[2] 的地址 (&x[2]) 传送给形参 a ,而 x[2] 的地址就是数组元素段 x[2] 、 x[3] 、 x[4] 的开始地址

Page 12: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 12

数组类型形参 ( 续 ) 与数组类型形参对应的实在数组有多少个元素是不

确定的,可能会对应一个大数组,也可能会对应一个小数组,甚至会对应数组中的某一段。所以在数组形参说明中,形参数组不必指定数组元素的个数。为了正确指明某次函数调用实际参与计算的元素个数,应另引入一个整型形参来指定

因传递给数组形参的实参是数组段的开始地址,函数内对数组形参的访问就是对实参所指数组的访问。函数也可以改变实参所指数组元素的值

Page 13: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 13

数组类型形参示例 函数 initArray() 是给数组元素赋指定值的 void initArray(int x[], int n, int val) { int i; for(i = 0; i < n; i++) x[i] = val; } 如另有数组定义 int a[10], b[100];

语句 initArray(a, 10, 1); initArray(b, 50, 2); initArray(&b[50], 50, 4);

分别给数组 a 的所有元素置值 1 ,为数组 b 的前 50个元素置值 2 ,后 50 个元素置值 4

Page 14: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 14

数组类型形参 ( 续 ) 数组形参也可以是多维数组。当数组形参是多维时,除数组

形参的第一维大小不必指定外,其它维的大小必须明确指定 如下面的函数 sumAToB() ,用于将一个 n 行 10 列的二维

数组各行的 10 个元素之和存于另一个数组中 void sumAToB(int a[][10], int b[], int n) { int i, j; for(i = 0; i < n; i++) for(b[i] = 0, j = 0; j < 10; j++) b[i] += a[i][j]; }

在函数 sumAToB() 的定义中,对形参 a 的说明如写成 int a[][] ;是错误的

Page 15: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 15

数组类型形参 ( 续 ) 因二维数组的元素只是按行存放,并不自动说明数

组的列数(即每行元素个数)。如在数组形参中不说明它的列数,就无法确定数组元素 a[i][j] 的实际地址

在实际使用数组形参编写函数时,注意程序中实参所指数组大小不能小于函数实际要用的数组的大小。如实参数组太小,因系统不检查超界情况,可能会使用实际数组后面别的变量的空间,这会发生意想不到的错误

Page 16: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 16

数组类型形参函数示例 -1 为数组输入值的函数 void readArray(int a[], int n) { int i; for(i = 0; i < n; i++) { printf("Enter a[%d] ", i); scanf("%d", &a[i]); } }

Page 17: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 17

数组类型形参函数示例 -2 求数组中最大元素值的函数 int max(int a[], int n) { int i, m; for(m = 0, i = 1; i < n ; i++) if (a[m] < a[i]) m = i; return a[m]; }

Page 18: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 18

数组类型形参即指针形参 数组形参对应的实参也可以是数组某元素的地址,即数组

某元素的指针,所以数组形参也是一种指针形参 所以任何数组形参说明 类型 形参名 [] 都可改写成 类型 * 形参名 如前面的函数 sum() 的定义可改写成如下形式

int sum(int *a, int n) { int i, s; for(s = i = 0; i < n; i++) s += a[i]; return s; }

Page 19: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 19

数组类型形参 - 指针形参示例 函数形参也是函数的局部变量,指针形参

就是函数的指针变量,函数 sum() 的定义又可改写成如下形式

int sum(int *a, int n) { int s = 0; for(; n--; ) s += *a++; return s; }

Page 20: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 20

数组类型形参 - 指针形参示例( 续 )

为使函数对表进行操作更具一般性,可为函数设置表的首元素指针和元素个数等形参,而函数体用指向表元素的指针对表作处理 如函数 sum() 也可改写成以下形式

int sum(int *a, int n) { int *ap, s; for(s = 0, ap = a; ap < a+n; ap++) s += *ap; return s; }

Page 21: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 21

数组类型形参程序示例 设某个班有若干位学生,共学 5门功课。学生成绩单的学号

和各门功课成绩存放在二维数组 s[][] 中, s 的每一行存储一位学生的有关信息,其中每行的第一列存放学生的学号

程序设计分析 设计函数 search(int (*p)[6], int m, int no) 用于寻找指

定学号的学生,并输出该生的各门功课成绩 其中,形参 p 是指向数组的指针,它所指数组有 6 个 int

型元素;形参 m 是学生数;形参 no 是待寻找学生的学号

主函数按以下格式调用 search(s, m, n); 其中 s 为成绩单首行数组的指针; m 为成绩单行数,即

学生人数; n 为某学生的学号

Page 22: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 22

数组类型形参程序示例 ( 续 ) 学生成绩单程

序 ( 续 )-P1#include <stdio.h>#define MAXN 3int s[MAXN][6] = {{ 5, 70, 80, 96, 70, 90}, { 7, 40, 80, 50, 60, 80}, { 8, 50, 70, 40, 50, 75}};void search(int (*)[6], int, int) ;void main(){ search(s, MAXN, 7); search(s, MAXN, 5); search(s, MAXN, 8); search(s, MAXN, 2);}

Page 23: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 23

数组类型形参程序示例 ( 续 ) 学生成绩单程

序 ( 续 )-P2

void search(int (*p)[6], int m, int no){ int (*ap)[6], *pp; for(ap = p; ap < p+m; ap++) if (**ap == no) { printf(“%d 号学生是 :\n", no); for(pp = *ap+1; pp <= *ap+5; pp++) printf("%4d\t", *pp); printf("\n"); return; } printf(“ 没有 %d 号的学生 .\n", no);}

Page 24: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 24

数组类型形参程序示例 ( 续 ) 学生成绩单程序 ( 续 )

内循环的 ap 是找到的那位学生的信息数组指针, *ap 是该数组的第一个元素的指针, *ap+1 是第二个元素的指针

利用二维数组元素按行连续存放的规则,如有必要,二维数组也可看作一维数组。如下面的代码段能累计全班学生的全部成绩之和,并存于变量 total 中

{ int *pp = *s, i; for(total = 0, i = 0; i < MAXN*6; i++; pp++) if (i%6) /* 跳过学号 */ total += *pp; }

工作变量 pp 是一个指向 int 型的指针,每次循环增加 1 个单位,遍历整个二维数组 s

Page 25: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 25

字符指针形参 字符指针形参,是说它指向字符串的首字符,对应

的实参或是字符数组某个元素的指针,或是字符串的首字符指针

字符串就是元素为字符的一维数组。所以字符指针形参与指向数组元素指针形参有相同的使用方法。但因字符串的特殊性,在编写字符串处理函数时还会有许多技巧

Page 26: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 26

字符指针形参示例 -1 字符串拷贝函数 strcpy()

实现该函数功能的代码前面讨论过,这里把它改写成函数,是将一个已知字符串内容复制到另一字符数组中

拷贝函数设有两个形参 from , to 。 from 为已知字符串的首字符指针, to 为存储复制的字符串首字符指针

void strcpy(char *to, char *from) { while ((*to++ = *from++) != ’\0’); }

由于字符串结束符 ’ \0’ 的 ASCII 码值为 0 ,因此上述测试当前拷贝字符不是字符串结束符的代码“ != ’\0’” 可以省略。函数可简写为

void strcpy (char *to, char *from) { while (*to++ = *from++); }

Page 27: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 27

字符指针形参示例 -2 两字符串比较函数 strcmp()

比较两字符串大小的函数 strcmp() 有两个形参 s 和 t ;分别为两个待比较字符串的首字符指针。如 s 所指的字符串小于 t 所指的字符串,函数返回值小于零;如 s 所指字符串大于 t 所指字符串,函数返回值大于零;如两个字符串相同,则函数返回零

int strcmp(char *s , char *t) { while (*s == *t) { /* 对应字符相等循环 */ if (*s == '\0') return 0; s++; t++; } return *s - *t; /* 返回比较结果 */ }

Page 28: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 28

字符指针形参示例 -3 输入字符行函数 getnch()

函数 getnch() 输入字符行,输入的字符存于由指针形参指定的字符数组中。为避免输入过多字符,函数 getnch() 另设一个 int 型形参,它给出最多能存储的字符数

int getnch(char *s, int lim) { int c; char *ps = s; while (ps < s+lim-1&&(c = getchar()) != EOF) { *ps++ = c; if (c == ’\n’) break; /* 输入换行符结束 */ } *ps = ’\0’; return ps - s;/* 返回实际输入存储的字符个数 */ }

Page 29: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 29

提要 函数形参 函数说明 函数指针及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 30: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 30

函数说明 函数说明的理由

一个函数要调用另一个函数,应知道被调用函数的有关如何正确调用的一些重要信息

编译程序根据这些信息检查调用的正确性。对不正确的调用给出错误信息,对正确的调用编译出实现的机器代码

Page 31: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 31

调用函数与被调用函数 调用函数与被调用函数之间在程序正文中可能会存在以

下几种情况 调用同一程序文件中前面已定义的函数 调用处于同一程序文件后面定义的函数 调用别的程序文件中定义的函数

对于第一种情况,在函数调用处,被调用函数的详细信息已被编译程序所接受

对于后两种情况,这时因被调用函数的信息还未被编译程序所接受,不能检查函数调用的正确性,所以在调用之前需对被调用函数有关调用的一些信息作出说明

Page 32: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 32

函数说明形式 函数与调用有关的信息包括

函数的返回值类型、函数名和函数有关形参的个数及其类型等

只给出函数的调用信息称作函数说明 函数说明的一般形式为

类型 标识符 ( 形参类型表 ); 其中,形参类型表顺序给出各形参的类型。形参类型表可以为空。

为了强调函数没有形参,空形参类型表可以写作 void 函数说明对要定义的函数名及其返回值类型和各形参的类型作说明,

以便让编译程序预先知道该标识符是函数名、它的返回值的类型和它的形参个数及各形参的类型

Page 33: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 33

函数说明示例#include <stdio.h>void main(){ double power(double, int) , x; int n; printf("Enter x and n.\n"); scanf("%lf %d",&x,&n); printf("power(%f, %d) = %f\n", x, n, power(x, n));}

double power (double x, int n){/*xn=x*xn-1, 当 n 为奇数 ; 或 xn = (x2)n/2 ,当 n 为偶数 */ double z, y; z = 1.0; y = x; while (n>0) { while (n%2 == 0) { n /= 2; y *= y; } n--; z *= y; } return z;}

Page 34: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 34

程序有函数说明的两种情况 -1 被调用函数是库函数。程序需使用预处理命令 #in

clude将包含有库函数的模型、它们所使用的常量、宏定义、数据类型定义等信息的文件包含进来 习惯称文件名以“ . h” 为后缀的文件为头文件 (header

file) 在头文件中通常包含宏定义、数据类型说明和定义、公共变量的外部说明等

在大型 C 程序设计中,用户程序也会有自己的头文件,如有头文件名为 d_type.h 使用包含预处理命令

#include "d_type.h"

Page 35: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 35

程序有函数说明的两种情况 -2 调用同一程序文件中后面定义的函数,或是调用别

的程序文件中定义的函数,应在调用函数之前对被调用函数作函数说明 在 C 语言中,如被调用函数的返回值是整型或字符型,

也可以不对它作说明。因编译系统发现程序要调用一个还未被定义或说明的函数时,就假定被调用函数的返回值是整型的,而字符型又是与整型相通的。但习惯仍给这样的函数作说明

在 C++ 语言中,调用后面定义或别的文件中定义的函数时,都必须在调用之前对被调用函数作说明

Page 36: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 36

提要 函数形参 函数说明 函数指针及其应用

函数指针和函数指针变量、利用函数指针调用函数 函数指针形参、函数指针数组

返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 37: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 37

函数指针和函数指针变量 在 C 语言中,函数不可设置函数形参,但函数可以设置函

数指针形参,函数要调用的函数也可以由实参指定 为了实现函数要调用的其它函数可以由实参指定, C 语言

引入函数指针类型、函数指针和函数指针变量等概念 函数指针如同一般数据一样能复制、存贮,函数指针变量

能作为复杂数据结构的成分,也能作为函数的形参等 函数的目标码有入口地址,这个入口地址就认作该函数的

指针,并用函数名来表记函数的指针 函数指针变量是一种特殊的指针变量,它能存储函数的指

针值,使它指向某个函数。如同数据指针变量可间接访问它所指变量一样,程序也可利用函数指针变量间接调用它所指的函数

Page 38: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 38

函数指针和函数指针变量 ( 续 ) 为区别能指向不同特性函数的函数指针变量,用函数的返回

值类型和函数的参数类型等来区分不同的函数指针,并以函数指针类型来标识

定义指向函数的指针变量的一般形式为 函数返回值类型 (* 指针变量名 )( 形参类型表 ); 例如, int (*fp)(int); 定义 fp 是一个能指向函数的指针变量,它能指向的函数的返回值必须

是 int 型的,并有一个 int 类型的形参 注意, *fp 两侧的括弧是必需的,表示 fp 先与 * 结合,说明它是一

个指针变量。然后与后随的 () 结合,表示指针变量 fp 是指向函数的。最前面的 int 表示所指向的函数应是返回 int 型值的函数

而代码“ int *f(int)” ,因 () 优先级高于 * ,就变成是说明一个函数 f() ,该函数的返回值是指向 int 型量的指针。

Page 39: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 39

函数指针和函数指针变量 ( 续 ) 在 C 语言中,定义函数指针变量时,形参类型表也

可省略不写 指向函数的指针变量表示它能存放函数的入口地址,

它能指的函数是函数指针变量定义时所规定的。 在程序执行时,某个符合要求的函数入口地址都能

赋给函数指针变量,使函数指针变量指向该函数,可以用该指针变量间接调用它所指函数

可根据需要向函数指针变量赋不同的函数入口地址,使它指向的函数可以按需要改变

Page 40: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 40

利用函数指针调用函数 定义了指向函数的指针变量,就可向它赋某函数的入口地址 C 语言约定,单独的函数名本身就是函数的入口地址 ( 不可

以在函数名之后加上一对园括号,否则变成函数调用 ) 如语句“ fp = min ;” 使函数指针变量 fp 指向函数 mi

n() 当一个指向函数的指针变量确实指向某个函数时,就可用它

调用它所指向的函数。一般形式的函数调用是 函数名 ( 实参表 )

改用指向函数的指针变量调用该函数,就要改写成 (* 函数指针变量名 ) ( 实参表 )

Page 41: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 41

利用函数指针调用函数示例 求两个整数中小的值的函数 min() ,它返回 int 型

值 如有 int (*fp)(int,int) , x=5, y=8, z, min(int,int); 则语句 fp = min; 使指针变量 fp 指向函数 min() 用 fp 间接调用函数 min() ,写成 z = (*fp)(x, y);

Page 42: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 42

利用函数指针调用函数示例 用函数指针变量调用函数的示意程序用函数指针变量调用函数的示意程序

#include <stdio.h>void main(){ int (*fp)(int, int), x, y, z; int min(int, int), max(int, int); printf("Enter x, y:");scanf("%d%d", &x, &y); fp = min; /* 让 fp 指向函数 min()*/ z = (*fp)(x, y); /* 调用 fp 所指函数 */ printf("MIN(%d, %d) = %d\n", x, y, z); fp = max; /* 现在更改 fp ,使它指向函数 max()*/ z = (*fp)(x, y); /* 调用 fp 所指函数 */ printf("MAX(%d, %d) = %d\n", x, y, z);}

int min(int a, int b) { return a < b ? a : b; } int max(int a, int b) { return a > b ? a : b; }

Page 43: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 43

函数指针形参 为什么要为函数设函数指针形参?

设有一个函数 fun() ,它有一个函数指针形参 fp 和若干其他形参

函数 fun() 利用函数指针形参 fp 和其他形参,根据给定的算法计算结果

调用函数 fun() 时,除提供其他实参外,还得为它提供与形参 fp 相对应的实在函数指针

这样,用不同的实在函数指针调用函数 fun() ,就能获得不同的结果

Page 44: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 44

函数指针形参示例 对给定实数数表,求它的最大值、最小值和平均值 程序设计分析

程序有三个函数 max() 、 min() 和 ave() ,另设一个函数afun()

主函数调用函数 afun() ,并提供数组、数组元素个数和求值函数指针作为实参

由函数 afun() 根据主函数提供的函数指针实参调用实际函数

Page 45: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 45

函数指针形参示例 ( 续 ) 给定实数数表,求最大值、最小值和平均值 ( 续 )

#include <stdio.h>#define N sizeof a / sizeof a[0]double max(double a[], int n){ int i; double r; for(r = a[0], i = 1; i < n; i++) if (r < a[i]) r = a[i]; return r;}double min(double a[], int n){ int i; double r; for(r = a[0], i = 1; i < n; i++) if (r > a[i]) r = a[i]; return r;}

double ave (double a[], int n){ int i; double r; for (r = 0.0, i = 0; i < n; i++) r += a[i]; return r/n;}double afun(double a[], int n, double (*f)(double *, int)){ return (*f)(a, n);}

Page 46: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 46

函数指针形参示例 ( 续 ) 给定实数数表,求最大值、最小值和平均值 ( 续 )

void main(){ double a[]={1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0, 9.0}; double result; printf("\n\nThe results are : "); result = afun(a, N, max); printf("\t%s = %4.2f", "MAX", result); result = afun(a, N, min); printf("\t%s = %4.2f", "MIN", result); result = afun(a, N, ave); printf("\t%s = %4.2f", "AVERAGE", result); printf("\n\n\n");}

Page 47: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 47

函数指针数组 利用函数指针能被存储的性质 , 可将若干函数指针

存于一数组中 如以下代码double (*fpt[])(double*, int)={ max, min , ave}; /* 函数指针数组 */ 定义了函数指针数组 fpt[] ,并用函数名对它初始化 , 将

函数 max() 、 min() 和 ave() 的函数指针填写在函数指针数组 fpt[] 中,使数组 fpt[] 成为函数的入口表

例子:设计一个通用的菜单处理函数,除其他有关菜单位置、大小、颜色等信息外,另设两个数组形参。一个数组的元素为指向菜单项字符串的指针,另一个数组的元素为指向对应处理函数的指针

Page 48: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 48

函数指针数组示例 通用的菜单处理函数 ( 续 )

#include <stdio.h>#define N sizeof a / sizeof a[0]void main (){ double a[] ={1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0, 9.0}; double result; double(*fpt[])(double *,int)={ max, min , ave}; char *title[]={" 最大值 ", " 最小值 ", " 平均值 "}; char *menuName[] ={” 求最大值” , ” 求最小值” , ” 求平均值” ,””}; int ans, k;

Page 49: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 49

函数指针数组示例 ( 续 ) 通用的菜单处理函数 ( 续 )

while (1) { printf(” 请选择以下菜单命令。 \n”); for(k = 0; menuName[k][0] != ’\0’ ; k++) printf(”\t%d: %s\n”, k+1, menuName[k]); printf(“\t 其它选择结束程序运行。 \n”); scanf(“%d”, &ans); if (ans < 1 || ans > k) break; printf("\n\n 结果: "); result = (*fpt[ans-1])(a, N); printf("\t%s=%4.2f\n\n",title[ans-1], result); }}

Page 50: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 50

提要 函数形参 函数说明 函数指针及其应用 返回指针值的函数

返回数据对象指针的函数 返回函数指针的函数

递归函数基础 命令行参数 函数程序设计实例

Page 51: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 51

返回数据对象指针的函数 函数可以返回整型值、字符值、实型值等,也可以返

回指向某种数据对象的指针值。返回指针值的函数与以前介绍的函数在概念上是完全一致的

定义 ( 或说明 ) 返回指针值函数的一般形式为 类型说明符 * 函数名 ( 形参表 ); 例如,函数说明: int *f(int , int); 函数 f() 返回指向 int 型数据的指针,有两个整型形参 注意:在函数名两侧分别为 * 运算符和 ( ) 运算符,而 ( ) 的优先级高于 * ,函数名先与 () 结合。函数名 () 是函数的说明形式。在函数名之前的 * ,表示函数返回指针类型的值

Page 52: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 52

返回数据对象指针的函数示例 -1 编制在给定的字符串中找特定字符的第一次出现。若找到,

返回指向字符串中该字符的指针;否则,返回 NULL值 程序设计分析

设函数为 sear_ch() ,该函数有两个形参,指向字符串首字符的指针和待寻找的字符

函数 sear_ch() 的定义 char *sear_ch(char *s, char c) { while (*s && *s != c) s++; return *s ? s : NULL; }

Page 53: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 53

返回数据对象指针的函数示例 -2 改写例 5.5 (学生成绩单)的函数 search() ,使新的 searc

h() 函数具有单一的寻找功能,不再包含输出等功能。新的函数 search() 的功能是从给定的成绩单中找指定学号的成绩表。新的函数 search() 的形参与例 5.5 中的函数 search()的形参相同,但新的函数 search() 返回找到的那位学生的成绩表 ( 不包括学号 ) 的指针 函数 search() 定义如下

int *search (int (*p)[6], int m, int no) { int (*ap)[6]; for(ap = p; ap < p+m; ap++) if(**ap == no) return *ap+1; return NULL; }

Page 54: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 54

返回函数指针的函数 返回函数指针值函数定义或说明的一般形式为

类型说明符 (* 函数名 ( 形参表 ))( 形参类型表 ); 例 5.11 的菜单函数接受用户选择,返回相应处理函数的指

针: double (*menu(char **))(double*, int) 在 menu() 函数说明中,

先是 menu(char **) , menu 同园括号结合,说明 menu 是函数; 随后是 (*menu(char **)) ,同 * 结合,函数返回指针; 接着是 (*menu(char **))(double*, int) 同一对园括号结合,表示是指向函数的指针,所指函数有 double

* 和 int 类型两个形参; 最后是 double (*menu(char **))(double*,int) ,说明指向的函数

的返回值是 double 类型

Page 55: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 55

返回函数指针的函数示例程序#include <stdio.h>#define N sizeof a / sizeof a[0]double max(double *, int), min(double *, int), ave(double *, int);double (*fpt[])(double *, int) = {max, min, ave, NULL}; /* 函数指针数组 */char *title[] = {“ 最大值” , “ 最小值” , “ 平均值” ,“”};char *menuName[] ={" 求最大值 "," 求最小值 "," 求平均值 ",""};double a[] ={1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};double (*menu(char **titptr))( double *, int) /* 函数返回函数的指针 */{ int ans, k; printf(" 请选择以下菜单命令。 \n"); for(k=0; menuName[k][0] !='\0';k++) printf("\t%d:%s\n", k+1, menuName[k]); printf("\t 其它选择结束程序运行。 \n"); scanf("%d", &ans); if (ans < 1 || ans > 3) return NULL; *titptr = title[ans-1]; return fpt[ans-1]; /* 返回函数的指针 */}

Page 56: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 56

返回函数指针的函数示例程序( 续 )

void main (){ double (*fp)( double *, int); char *titstr; double result; while (1) { if ((fp = menu(&titstr)) == NULL) break; result = (*fp)(a, N); printf("\n 结果: %s = %4.2f\n\n", titstr, result); } }double max(double a[], int n){ int i; double r; for(r = a[0], i = 1; i < n; i++) if (r < a[i]) r = a[i]; return r;}

Page 57: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 57

返回函数指针的函数示例程序( 续 )

double min(double a[], int n){ int i; double r; for(r = a[0], i = 1; i < n; i++) if (r > a[i]) r = a[i]; return r;}double ave(double a[], int n){ int i; double r; for (r = 0.0, i = 0; i < n; i++) r += a[i]; return r/n;}

Page 58: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 58

提要 函数形参 函数说明 函数指针及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 59: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 59

递归函数基础 一个函数为完成它的复杂工作,可以调用其他函数

例如,从主函数出发,主函数调用函数 A() ,函数 A() 又调用函数 B() ,函数 B() 又调用函数 C() ,等等。这样从主函数出发,形成一个长长的调用链,就是通常所说的函数嵌套调用 (nested call)

函数嵌套调用时,有一个重要的特征,先被调用的函数后返回。如这里所举例子,待函数 C() 完成计算返回后,B() 函数继续计算 ( 可能还要调用其他函数 ) ,待计算完成,返回到函数 A() ,函数 A() 计算完成后,才返回到主函数

当函数调用链上的某两个函数为同一个函数时,称这种函数调用方式为递归调用 (recursive call)

Page 60: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 60

递归函数基础 (2)

Page 61: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 61

递归的概念 递归的定义

若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的

若一个函数直接地或间接地调用自己,则称这个函数是递归函数 (recursive function)

在以下三种情况下,常常用到递归方法 定义是递归的 数据结构是递归的 问题的解法是递归的

Page 62: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 62

递归函数示例 -1 用循环实现阶乘计算的函数 long fac(int n) { long s; int i; for(s = 1L, i = 1; i <= n; i++) s *= i; return s; } 用递归实现阶乘计算函数 求解阶乘函数的递归算法 long fac (int n ) { if ( n == 0 ) return 1L; else return (long)n*fac (n-1); }

阶乘函数 :

时当时当

1 ,)!1(

0 ,1!

n

n

nnn

Page 63: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 63

求解阶乘 n! 的过程

Page 64: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 64

递归函数示例 -2 用递归函数实现数组元素的求和计算 int rsum(int *a, int n) { if (n == 0) return 0; /* 若数组没有元素,则返回 0 */ return *a+rsum(a+1, n-1); /* 当前元素与其余元素的和 */ }

Page 65: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 65

递归函数示例 -3 求解Hanoi塔问题。问题是这样的,有三根针 ( 设分别为 A 、 B、 C) ,在 A 针上有 n张大小均不相同的金片,大的在下,小的在上。要求按以下规则,把这 n张金片从 A 针搬到 C 针上 在搬动过程中可使用 B针; 每次只允许搬动一张金片; 在搬动过程中,必须保证大金片在下,小金片在上。

Page 66: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 66

递归函数示例 -3( 续 ) Hanoi塔问题 ( 续 ) 程序设计分析 move(int n, char A, char C, char B) { /* 将 n 张金片从 A 针搬到 C 针,搬动过程可使用 B 针 */ if (n==1) /* 对于只搬动一张金片情况 */ (1) 将金片 n 从 A 针搬到 C 针 ; else { /* 搬 n(>1) 张金片时,按同样算法先搬前 n-1 张金片 */ (2) 将 A 针上的 n-1 张金片搬到 B 针上,中间可使用 C 针 ; (3) 将金片 n 从 A 针搬到 C 针 ; /* 搬动第 n 张金片 */ /* 搬 B 针上前 n-1 张金片 */ (4) 将 B 针上的 n-1 张金片搬到 C 针,中间可使用 A 针 ; } }

Page 67: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 67

Hanoi 问题的解法是递归的,是直接递归

递归函数示例 -3( 续 )

Page 68: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 68

递归函数示例 -3( 续 ) Hanoi塔问题 ( 续 ) #include <stdio.h> void hanoi (int n,char A,char C,char B) { // 解决汉诺塔问题的算法 if ( n == 1 ) printf( " move %c to %c\n ",A, C); else { hanoi ( n-1, A, B, C ); printf( " move %c to %c\n ",A, C); hanoi ( n-1, B, C, A ); } }

Page 69: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 69

递归函数 ( 续 ) 对于算法间接地由其自身定义情况,称为间接递归。

在间接递归情况,函数调用链中,在同一个函数再次出现之前,有一个或多个其他别的函数

递归程序特点 通常,以递归形式定义算法与非递归算法比较,算法结构会更紧凑、清晰

但是,递归函数在递归调用过程中,会占用更多的内存空间和需更多的运行时间

另外,在编写递归函数时,必须防止遗漏递归终止条件和防止振荡式的相互递归调用,否则会产生无限递归调用现象

Page 70: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 70

直接递归 vs. 间接递归

直接递归 间接递归

Page 71: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 71

线性递归 在算法的递归定义形式中,有一种称之为线性递归

的情况,线性递归的算法有以下所示的一般形式 p(int n) { S(n); if (E(n)) { p(n-1); /* 递归调用 */ T(n); } else U(n); }

其中, S(n) 、 E(n) 、 T(n) 和 U(n) 是可能与 n 有关的非递归计算的代码段,也可能为空

Page 72: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 72

线性递归 ( 续 ) 线性递归能机械地被转换成一个功能等价的非递归描述 p(int n) { int k = n+1; do { k--; S(k); } while E(k); U(k); while (k<n) { k++; T(k); } } 除能将线性递归机械地变换成非递归的循环实现外,有很

多的直接递归问题可改造成递推或者说循环( iterative )的方式实现

Page 73: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 73

用递推实现递归的示意程序 勒让得多项式的递归定义为 ┌ 1 , n=0; p(n,x)= ┤ x , n=1; └ ((2n-1)xp(n-1,x)-(n-1)p(n-2,x))/n , n>1 程序设计分析

用递归函数实现,能很容易地写出它的函数定义如下 double p(int n, double x) { if (n==0) return 1.0; if (n==1) return x; return ((2.0*n-1.0)*x*p(n-1,x)-(n-1.0)*p(n-2,x))/n; }

Page 74: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 74

用递推实现递归的示意程序 ( 续 ) 勒让得多项式用递推方法实现 程序设计分析

计算 p(n,x) 的困难是 n>1 情况 然而,若知道了 p(n-2,x) 和 p(n-1,x) 的话,就能立即

按公式计算出 p(n , x) 由于 p(0,x) 与 p(1,x) 是能立即得到的 这样就能用递推方法,从 p(0,x) 与 p(1,x) 出发,依次计算出 p(2,x) , p(3,x) ,…

即从幂次低的勒让得多项式到幂次高的勒让得多项式的递推解法

Page 75: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 75

用递推实现递归的示意程序 ( 续 ) 勒让得多项式用递推方法实现,函数定义如下

double p(int n, double x){ double first, second, third; int count; if (n == 0) return 1.0; if (n == 1) return x; first = 1.0; second = x; for(count = 2; count <= n; count++) { third = ((2.0*count-1.0)*x*second - (count-1.0)*first)/count; first = second; second = third; } return third;}

Page 76: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 76

提要 函数形参 函数说明 函数指针及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 77: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 77

命令行参数 执行 C 程序,可以看作是对该程序的 main() 函数的调用。

main() 函数执行结束,返回环境。为能从环境向 C 程序传递信息,启动 C 程序的命令行可带有任选的参数

命令行的一般形式为 程序名 参数 1 参数 2 … 参数 n 其中程序名和各参数之间用空白符分隔 为能让 main() 函数读取命令行中的参数,环境将多个参数以两个参

数形式传递给 main() 函数。其中第一个参数 ( 习惯记作 argc) 表示命令行中参数的个数 ( 包括程序名 ) ;第二个参数 ( 习惯记作 argv)是一个字符指针数组。其中 argv[0] 指向程序名字符串, argv[1] 指向参数 1 字符串, ... , argv[argc-1] 指向最后一个参数字符串。

如果 argc 等于 1 ,则表示程序名后面没有参数

Page 78: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 78

命令行参数示例 说明 main() 函数对参数 argc 与 argv引用方法的程序

打启动程序时的命令行各参数 #include <stdio.h> void main(int argc, char *argv[] /* 或 char **argv; */ ) { int k; for(k = 1; k < argc; k++) printf("%s %c", argv[k], k < argc-1 ? ' ' : '\n'); printf("\n\n"); }

如上述程序的执行程序名为 aecho.exe , 执行该程序的命令行为: aecho Hello world!

则程序将输出 Hello world! 根据约定, main() 函数的参数 argc 的值为 3 ; argv[0] , argv

[1] , argv[2] 分别指向字符串 "echo" 、 "Hello" 、 "world!" 的第一个字符

Page 79: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 79

命令行参数 ( 续 ) 在程序的 printf() 函数调用中,字符转换格式%c

输出一个字符,若是已输出了命令行最后一个参数,该格式将输出一个换行符,若是输出其他参数,则输出一个空白符

因函数的数组参数是指向数组首元素的指针变量,所以在主函数 main() 中可对 argv施行增量运算 例如,在 argv[0] 指针指向程序名字符串的第一个字符情况下,对 argv 施增量运算 ++argv 后, argv[0]( 或 *argv) 就指向参数 1 的第一个字符

Page 80: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 80

命令行参数 ( 续 ) 利用在主函数 main() 中可对 argv施行增量运算

的性质,可改写前述示例程序为以下形式 #include <stdio.h> void main(int argc, char **argv) { while (--argc > 0) printf("%s %c", *++argv,argc > 1 ? ' ' : '\n'); }

这里, ++argv 使指针 argv 先加 1 ,让它一开始就指向参数 1 ;逐次增 1 ,使它遍历指向各参数

又利用函数 printf() 的第一个格式参数是字符串表达式,上述程序对 printf() 的调用可改写成

printf((argc > 1)? "%s " : "%s\n",*++argv);

Page 81: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 81

命令行参数示例 -1 假定启动程序时给出的命令行参数是一串整数,程

序将全部整数求和后输出 #include <stdio.h> #include <math.h> void main(int argc, char **argv) { int k, s; for(s = 0, k = 1; k < argc; k++) s += atoi(*++argv); /* 从数字字符串译出整数 */ printf(“\t%d\n”, s); }

Page 82: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 82

命令行参数 ( 续 ) 利用指针数组作 main() 函数的形参,向程序传递命令行参

数字符串,其中字符串的长度可以不同,参数的个数也可以任意,这是指针数组作函数参数最为方便灵活的应用

命令行可带任选的参数,程序 (主要 main() 函数)应能正确识别某参数的出现,及该参数的意义。通常, C 程序将命令行参数分成两部分

一部分是一定要出现的参数,它们通常被安排在命令行参数表的最后部分,并对这些参数出现顺序也有明确的约定

另一部分是可任选的参数,因它们中的某些或全部可以不出现,为使程序能正确识别出现的参数及其意义, C 程序习惯用以下形式表示一个可任选的参数

- 字符 参数

Page 83: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 83

命令行参数 ( 续 ) C 程序习惯用以下形式表示一个可任选的参数

- 字符 参数 其中负号表示一个任选参数,紧接的字符是任选参数的标志符。

其后的参数可以没有。如有,就作为命令行的下一个参数。

特别是对于标志符之后的参数没有的情况,多个参数标志符紧接在一起,合用一个任选参数开始符 ( 负号字符 )

Page 84: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 84

命令行参数程序示例 输入正文,从中寻找包含特定字符串的行。其中特定字符串由命令行的参数指定。程序另设两个供任选的参数 参数 1 -x 印出所有那些不包含特定字符串的行;无 -x,则印出所有那些包含特定字符串的行

参数 2 -n 在输出字符行之前,冠以该行在正文中的行号;否则,输出时不带行号

另约定,特定字符串参数是命令行的最后一个参数;任选参数的出现顺序可以任意。如两个任选参数都出现时,可合在一起同时指定,如 -xn 或 -nx。一个任选参数最多只能出现一次。

运行本程序的命令行的一般形式为find -x -n pattern filename

Page 85: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 85

命令行参数程序示例 ( 续 )#define LINES 500#define MAXLINE 300#include <stdio.h>#include <string.h>#include <malloc.h>int getline(FILE *fp, char *s, int lim);int index(char *s, char *t);int main(int argc, char **argv){ char *lineptr[LINES]; int lineno[LINES]; int len, m; int nlines = 0; int n = 0; int err = 0; int inverse = 0; int num = 0; FILE *fp; char line[MAXLINE], *s;

Page 86: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 86

命令行参数程序示例 ( 续 ) while (err == 0 && --argc > 0 && (*++argv)[0] == ’-’) /* *++argv 是指向任选参数串的首字符指针, (*++argv)[0] 是任选参数字符串的首字符 */ for(s = argv[0]+1; *s != ’\0’; s++) switch (*s) { case 'x' : if (inverse) err = 1; /* 限制 x 最多只能出现 1 次 */ else inverse = 1; break; case 'n' : if (num) err = 1; /* 限制 n 最多只能出现 1 次 */ else num = 1; break; default : err = 1; } /* 正确情况下,至此 argc 应为 2*/ if (err || argc != 2) { /* 如有使用不当 */ printf("Usage:find -x -n pattern filename\n"); return 1; }

Page 87: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 87

命令行参数程序示例 ( 续 ) if ((fp = fopen(*(argv+1), "r")) == NULL) { fprintf(stderr, "Can't open %s. \n", *(argv+1)); return 2; } while ((len = getline(fp, line, MAXLINE)) > 0) { nlines++; if ((index(line, *argv) >= 0) != inverse) if ((s = (char *)malloc(len+1)) == NULL) break; else { lineno[n] = nlines; strcpy(s, line); lineptr[n++] = s; } } fclose(fp); for(m = 0; m < n; m++) { if (num) printf("%4d: ", lineno[m]); printf("%s\n", lineptr[m]); } return 0;}

Page 88: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 88

命令行参数程序示例 ( 续 )int getline(FILE *fp, char *s, int lim){ int c; char *ps = s; while (ps < s+lim-1 && (c = fgetc(fp)) != EOF) { *ps++ = c; if (c == '\n') break; } *ps = '\0'; return ps-s;}int index(char *s, char *t){ int i, len1 = strlen(s), len2 = strlen(t); char *ps, *pt; for(i = 0; i <= len1-len2; i++) { for(ps = s+i, pt = t; *pt != '\0' && *ps == *pt; ps++, pt++); if (*pt == '\0') return i; } return -1;}

Page 89: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 89

提要 函数形参 函数说明 函数指针及其应用 返回指针值的函数 递归函数基础 命令行参数 函数程序设计实例

Page 90: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 90

函数程序设计实例 -1 编制求整数是几位十进位数的函数

设函数模型为 int digit(int n) ,函数的功能是求形参 n 是几位十进位数。引入变量 c 计数 n 的位数,则反复将 n 除以 10 ,直至 n 等于 0 的重复次数,就能推算出 n 的十进位的位数

函数开始时预置计数器 c 为 0 ,循环的工作部分是让 c 增 1和 n 除以 10 ,循环直至 n 除以 10 后为 0 结束

如下面的函数 digits() 的定义 int digits(int n) { int c = 0; do { c++; n /= 10; } while (n); return c; }

问题:如何把一个实数变成十进位数串?

Page 91: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 91

函数程序设计实例 -2 编制从两个已知非空字符串中找出最长公共子串的的长度和

最长公共子串的个数的函数 设函数模型为 int commStr(char *str1, char *str2, int *lenpt) 函数的形参 str1 和 str2 是已知字符串首字符的指针,形参 lenpt 用

于存储找到的最长子串的长度,函数返回找到的最长公共子串的个数 函数算法的基本想法是对字符串 s2 的各种可能子串,在字符串 s1 中找是否有同样的子串

记两个字符串 str1 和 str2 的长度分别为 len1 和 len2 。假定 len1 >= len2 。则它们最长的公共子串长度不会超过 len2 。设要寻找的子串长为 ln , ln 的初值为 len2 ,即以 str2 字符串的最长子串开始寻找,当在 str1 中没有长为 ln 的相同子串时,就让 ln 减 1 ,即用 str2 更短的子串重新寻找。若找到,则该子串就可作为函数要找的 str1 和 str2的一个最长子串。若直至找遍了 str2 的所有可能的子串,还未在 str1中找到相同的子串,则说明 str1 和 str2 没有公共子串

Page 92: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 92

函数程序设计实例 -2( 续 ) 编制从两个已知非空字符串中找出最长公共子串的的长度和最长公共子串的个数的函数 ( 续 )

#include <stdio.h>int commStr(char *str1, char *str2, int *lenpt)/* 函数返回最长公共子串的个数 , 通过指针参数传回最长公共子串的长度 */{ int len1, len2, ln, count, i, k, p; char *st; if((len1=strlen(str1))<(len2=strlen(str2))){ st = str1; str1 = str2; str2 = st; ln = len1; len1 = len2; len2 = ln; } count = 0;

Page 93: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 93

函数程序设计实例 -2( 续 ) for(ln=len2;ln>0;ln--) { /* 找长 ln 的公共子串 */ for(k = 0; k + ln <= len2; k++) { /* 自 str2[k] 开始,长为 ln 子串与 str1 的子串比较 */ for(p = 0; p + ln <= len1; p++) { /* str1 中的子串自 str1[p] 开始,两子串对应字符逐一比较 */ for(i = 0; i < ln; i++) if (str2[k+i] != str1[p+i]) break; if (i == ln) count++; /* 找到一个最长公共子串 */ } } if (count) break; /* 如找到过,退出寻找循环 */ } *lenpt = ln; return count;}void main(){ int c, len; c=commStr("Abc1AbcsAbcd123", "123bAbc", &len); printf(" 有 %d 个长为 %d 的公共子串 \n", c, len);}

Page 94: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 94

函数程序设计实例 -3 编写函数 int trans(unsigned n, int d, char s[]) ,将无符号

整数 n 翻译成 d(2 <= d <= 16) 进制的字符串 s 。函数的返回值是该 d 进制的字符串长度。当 d>10 时, d 进制的数字位值可能要大于 9 ,约定大于 9 的数字位值分别用字符 A 、 B 、C 、 D 、 E 、 F 标记数字位值 10 、 11 、 12 、 13 、 14 、15 求 n 整除 d 的余数,就能得到 n 的 d 进制数的最低位数

字,然后 n 除以 d 重复上述步骤,直至 n 为 0 ,能依次得到 n 的 d 进制数

的最低位数字至最高位数字 再由各位数字取出相应字符,就得到 n 的 d 进制数的字

符串表示

Page 95: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 95

函数程序设计实例 -3( 续 ) 编写函数 int trans(unsigned n, int d, char s[])( 续 ) #include <stdio.h> #define M sizeof(unsigned int)*8 int trans(unsigned n, int d, char s[]) { char digits[] = ”0123456789ABCDEF”; char buf[M+1]; int j, k = M; if (d < 2 || d > 16) { s[0] = ’\0’; return 0; } buf[k] = ’\0’; do { buf[--k] = digits[n%d]; n /= d; } while (n); for(j = 0; s[j++] = buf[k++]; ); return j-1; }

Page 96: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 96

函数程序设计实例 -4 编制判定正整数 n 的 d进制表示形式是否是回文数的函数。回文数就是自左向右读和自右向左读是相同的数。例如, n = 232, 其十进制表示是回文数; n = 27,其二进制表示 11011 是回文数 设函数 circle(int n, int d) 是要判 n 的 d进制表示是否是回文数。实现这个判定有两种方法:

一是顺序译出 n 的 d 进制表示的各位数字,然后将其首末对应位数字两两比较,若对应位数字都相同,则 n 的 d 进制表示是回文数。

二是在顺序从低位到高位译出 n 的 d 进制各数字位的同时,把译出的各位数字看作是另一个 d 进制数字的高位至低位,并将其重新转换成整数。若转换所得整数与 n 相等,则 n 的 d 进制表示是回文数

Page 97: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 97

函数程序设计实例 -4( 续 ) 判 n 的 d进制表示是否回文数函数 circle(int n, int d)( 续 )

下面是参照第二种方法编写的函数

int circle(int n, int d) { int s = 0, m = n; while (m) { s = s*d + m%d; m /= d; } return s == n; }

Page 98: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 98

函数程序设计实例 -5 编制函数 int part(int a[], int low, int high) 对数组元素 a[low] -- a[high] 以 a[low] 为基准,对它们作

划分,使 a[low] 移至 a[k] 后,新的 a[low] -- a[k-1] 中的元素均小于 a[k] ; a[k+1] -- a[high] 中的元素均大于等于 a[k] 。函数返回 k 值, a[k] 是数组 a 中的第 k+1 大元素 令函数要调整存储位置的元素是 a[k] 至 a[j] ,变量 k 和 j

就是未确定存储位置元素区间的下界和上界,它们的初值分别为 low 和 high

首先将 a[low] 暂存于变量 temp ,然后是一个循环,循环内是交替地执行从后向前考察元素的循环和从前向后考察元素的循环

Page 99: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 99

函数程序设计实例 -5( 续 ) 编制函数 int part(int a[], int low, int high)( 续 )

在从后向前考察循环中,当考察元素 a[j] 不比 temp 小时,让 j 减 1 。当发现元素 a[j] 比 temp 小时,结束从后向前的循环,并将 a[j] 移至 a[k] ,和让 k 增 1

在从前向后考察循环中,当考察元素 a[k] 比 temp 小时,让 k 增 1 。当发现 a[k] 不比 temp 小时,结束从前向后的考察循环,并将 a[k] 移至 a[j] ,和让 j 减 1

上述循环直至调整区间不再有元素时结束。最后将暂存于temp 中的值回填到 a[k] 中,返回 k 值

Page 100: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 100

函数程序设计实例 -5( 续 ) 编制函数 int part(int a[], int low, int high)( 续 )

int part(int a[], int low, int high){ int k = low, j = high, temp = a[low]; do { while ( j > k && a[j] >= temp ) j--; if (k < j) a[ k++ ] = a[j]; while ( k < j && a[k] < temp ) k++; if (k < j) a[ j-- ] = a[k]; } while ( k < j); a[k] = temp; return k; }

Page 101: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 101

函数程序设计实例 -5( 续 ) 编制函数 int part(int a[], int low, int high)( 续 )

以下是利用函数 part() 实现求数组 a 按自小到大顺序,第 k 个元素 ( 自 0开始计数)的函数 int kth() 和示意主函数

int kth(int a[], int k, int n) { int m = 0, h = n-1, t; do { t = part(a, m, h); if ( t > k ) h = t - 1 ; else if ( t < k ) m = t + 1 ; } while ( t != k ); return a[k]; }

int a[] = {0, 1, 9, 7, 6, 4, 5, 2, 3, 8};void main(){ int i; printf("Enter i "); scanf("%d", &i); printf("%d %d\n", i, kth(a, i, 10)); }

Page 102: 第 8 讲  函数的设计方法

-2005《程序设计》 年秋 102

第 5 章 小结 函数形参 ( 指针类型、数组类型、字符指针 ) 函数说明 函数指针及其应用

函数指针和函数指针变量、利用函数指针调用函数 函数指针形参、函数指针数组

返回指针值的函数 ( 返回数据对象指针、返回函数指针 ) 递归函数基础 (直接递归、间接递归、线性递归、递推 ) 命令行参数 函数程序设计实例