Upload
celine
View
260
Download
0
Embed Size (px)
DESCRIPTION
第六章 函数 6.1 概述 6.2 函数定义的一般形式 6.3 函数参数和函数返回值 6.4 函数的调用 6.5 函数的嵌套调用 6.6 函数的递归调用 6.7 数组作为函数的参数 6.8 局部变量和全局变量 6.9 变量的存储类别 6.10 内部函数与外部函数. 6.1 概述 C 程序是模块结构的,每个模块也称为函数。 一个 C 程序可由一个 main 函 数和若干个用户 自定义函数构成,这些函数是通过调用来建立联系的。 在这些函数中还可以调用系统提供的各类标 准库函数。 - PowerPoint PPT Presentation
Citation preview
电信学院
1
第六章 函数6.1 概述6.2 函数定义的一般形式6.3 函数参数和函数返回值6.4 函数的调用6.5 函数的嵌套调用6.6 函数的递归调用6.7 数组作为函数的参数6.8 局部变量和全局变量6.9 变量的存储类别6.10 内部函数与外部函数
电信学院
2
6.1 概述
C 程序是模块结构的,每个模块也称为函数。
一个 C 程序可由一个 main 函数和若干个用户自定义函数构成,这些函数是通过调用来建立联系的。
在这些函数中还可以调用系统提供的各类标准库函数。
本章主要介绍用户自定义函数的定义、调用以及函数间信息的传递。
电信学院
3
1. 采用自定义函数的意义
1) 符合结构化设计思想,便于设计、调试和扩充,是逐步细化开发方式的具体体现 ;
2) 每个自定义函数可单独编译,便于模块调试;
3) 避免在程序中设计重复的代码,函数定义一次 便可多次调用,有效地利用了已有的代码资源。
电信学院
4
2. C 程序各函数之间的关系
1) 不管主函数的书写位置如何, C 程序总是从主函 数开始运行、并在主函数结束;
2) 主函数可调用所有自定义函数,但不能被任何函 数调用;
3) 一个自定义函数除了可被主函数调用外,还可被 其它自定义函数调用;
4) 自定义函数可调用除主函数之外的其它自定义函 数,甚至可直接或间接地自己调用自己。
电信学院
5
main
自定义 f1
自定义 f2
自定义 fn
┇
函数调用关系图示:
电信学院
6
3. 函数调用的执行流程嵌套调用: main a
b
a
c
开始
结束
f2f1
调用返回
调用返回
间接递归调用: 直接递归调用:
调用
返回
f
电信学院
7
4. C 函数的分类
从用户使用的角度分类:库函数——由系统提供,包括:常用数学库、标准 I/O 库、 DOS 专用库、字符屏幕控制库、 图形库等;用户自定义函数——根据特定的需要自己编制;
从函数的形式分类:无参函数——调用时不需要任何参数;有参函数——调用时必须给出实参,函数依据实参 进行相应处理。
电信学院
8
6.2 函数定义的一般形式
1. 有参函数定义的一般形式
[ 类型标识符 ] 函数名 ( 形式参数表 ) 函数头 { 局部声明 函数体 执行语句 }
特点:调用时必须给出实参,函数将依据实参实现 特定操作。其中——类型标识符:说明函数返回值的类型,省略为 int 型;
函数名:遵循标识符命名规则,名称尽量反映功能;
电信学院
9
形式参数:从形式上说明该函数调用时所需的参数个数及类型,多个形参间逗号分隔;
局部说明部分:对自定义函数局部使用的变量、数组等进行类型定义;
执行语句:实现函数功能;其中的返回语句 return将
控制程序流程返回主调函数;
电信学院
10
例:用自定义函数找出两数中的较大数
main( ){ int a, b; scanf("%d,%d", &a, &b); printf("MAX=%d\n", max(a, b)); }int max(int x, int y) /*int 可省略 , 定义形参变量 */{ int z; /* 局部变量定义*/ z=x>y? x:y; return(z); /* 将 z 的值作为返回值 */ }
电信学院
11
2. 无参函数定义的一般形式
[ 类型标识符 ] 函数名 ( ) { 局部声明 执行语句 }
特点:不依据任何参数来实现特定操作;注意:尽管没有参数但圆括号不能省去。
例:定义实现延时功能的无参函数void delay( ) /* 无参、无返回值
*/{ long i; for( i=0; i<=32000000; i++); }
电信学院
12
3. 空函数定义的一般形式
[ 类型标识符 ] 函数名 ( ) { }
作用:用于调试程序时,在逻辑上表示这个函数已 经定义,而不至于影响程序其它部分的编译; 待下一阶段再编制该函数具体内容。
电信学院
13
6.3 函数参数和函数返回值
形式参数——定义函数时的参数
实际参数——调用函数时的参数
函数返回值——返回主调函数时带回的结果
电信学院
14
例:利用自定义函数,找出三个数中的最大数。
main( ) { int a, b, c, d; scanf("%d,%d,%d", &a, &b, &c); d=max(a, b); /*a 、 b 的值作为实
参 */ d=max(d, c); /*d 、 c 的值作为实
参 */ printf("Max is %d\n", d); } max(int x, int y) { int z; z=x>y? x:y; return(z); /*z 值作为返回值 */ }
电信学院
15
8 5
1. 形式参数和实际参数说明1) 编译时并不为形参分配存储单元,在程序运行中 发生函数调用时,才动态地为形参分配存储单元 , 并接受实参传递的值;函数调用结束,形参占用 的存储单元将被释放;
如例中第一次调用: 实参: a b d=max(a, b); 形参: x y
第二次调用 : 实参: d c d=max(d, c); 形参: x y
8 5
8 12
8 12
电信学院
16
5
8 5
080
2) 由于实参和形参各有各的存储单元,因此在被调函数中给形参变量赋值,不会对实参造成任何影响。
如例中主调函数: d=max(a, b); 实参: a b
被调函数: 形参: x y z=x>y? x: y; x=y=0; return(z);
电信学院
17
3) 实参、形参的形式
实参形式 传递内容 形参形式 常量变量、下标变量 表达式 函数调用
数组名 同类型数组
例: d=max( i%100, 10); /* 实参:表达式、常量*/
d=max(n[1], n[2]); /* 实参:下标变量 */ d=max((int)sqrt(25), c);
实参的值单向传递
数组指针单向传递
同类型变量
电信学院
18
4) 由于实参和形参各有各的存储单元,因而实参和 形参可以同名,且互不干扰;
5) 实参与形参在个数、类型、顺序上要对应一致; 否则因编译系统不做匹配性检查,将造成数据传
递错误。
如: d=max(7.8, 10.2); /* 实、形参类型不一致*/
d=max(a, b, c); /* 实、形参个数不一致*/
6) 若主调函数采用了函数原型声明,在实、形参类 型不一致时将按赋值规则自动转换为同一类型。
电信学院
19
上例:观察实参、形参的存储地址main( ){ int a, b, c, d; scanf("%d,%d,%d", &a, &b, &c); printf("&a=%x,&b=%x,&c=%x\n", &a, &b, &c); d=max(a, b); d=max(d, c); printf("a=%d,b=%d,c=%d,d=%d\n", a, b, c, d); }max(int x, int y){ int z; z=x>y? x:y; x=y=0; printf("&x:%x,&y:%dx:%d,y:%d\n", &x, &y, x, y); return(z); }
电信学院
20
2. 函数的返回值
若仅返回一个值:可用 return 返回语句实现;
若需返回多个值:则需要使用其它手段实现; 如:指针、全局变量
返回语句的一般形式: return( 表达式 );
功能:在自定义函数中使用该语句,可使程序的执 行流程返回到主调函数的调用位置,并将表 达式的值作为返回值。
表达式形式:任何合法的 C 表达式;
电信学院
21
使用说明:1) 一个自定义函数中可以有一个以上的 return 语句; 这常用于分支结构的不同出口,但只可能有一个被
执行。
如上例: if (x>y) return(x); else return(y);
如:函数功能——判断形参 m 、 n 的大小关系; 若 m>n 返回 1 、 m<n 返回 -1 、 m==n 返回
0 。 int compare(int m, int n) { return((m-n)>0? 1:-1); /*只执行该条 */ return(!((m-n)==0)); /*永远不可能执行
*/ }
电信学院
22
2) 若函数不需要用 return 语句返回值,其类型应采
用 void空类型标识符;此时返回语句的形式为: return; 或者干脆省去返回语句 ; 函数体结束
标 志‘ }’ 也可控制流程返回。
如: void printstar( ) { printf("********************\n"); return; /* 或省略 */ }
该函数仅仅完成输出一行星号字符的特定操作,不需要参数,也不需要返回值。
电信学院
23
3) return后表达式值的类型一般应与定义函数的类
型一致;若不一致,则以定义函数时的类型为准 自动转换。
如: int average(…… ) { ┇ return(sum/4.0); } /* 值为实型,自动转为 int 型返回
*/
电信学院
24
6.4 函数的调用
1. 函数调用的一般形式
函数名 ( 实参表 )
实参形式:可是常量、变量、下标变量、表达式、 函数调用等;多个实参逗号分隔。
注意:实参在个数、类型、顺序上应与形参对应。
电信学院
25
调用过程:
首先求解各实参,然后将实参值对应传递到为形参分配的存储单元中,且程序流程转至被调函数,当被调函数碰到 return 语句或函数体结束标志‘ }’
时,流程返回主调函数,继续后面的操作。
图示:被调主调 实参值
返回值
电信学院
26
例:定义一函数判断两个量是否相等,相等返回 0 , 数 1> 数 2 返回 1 ,数 1< 数 2 返回 -1 。main( ){ int a, b,flag; scanf("%d,%d", &a, &b); flag=compaer(a, b); if(flag==0) printf("%d==%d", a, b); else printf("%d%c%d", a, flag>0? '>':'<',
b); } int compare(int a, int b) /* 实、形参名相同
*/{ if(a==b) return(0); else if(a>b) return(1); else return(-1); }
电信学院
27
2. 函数调用的位置
1) 对有返回值函数的调用位置,可是表达式的运算 元素、函数的参数;如: c=2*max(a,b)+10;如: m=max(a, max(b,c) );如: printf("MAX=%d", max(a, b+10) );
2) 对 void空类型函数的调用位置,只能是独立的函
数调用语句;如: printstar( );如: delay( );
电信学院
28
3. 对被调函数的声明 要调用一个自定义函数,除了该函数已被定义,还需要对其做一些必要的声明。
1) 声明方法 ( 有 2 种 )
简单声明法—— 类型标识符 函数名 ( );
特点:此方法在编译时不对参数类型、个数做匹配 性检查,因此调用时实参和形参的类型、个 数必须一致,否则会产生奇怪的结果。
电信学院
29
原型声明法——
类型标识符 函数名 ( 参数类型标识符表 );
其中:在参数类型标识符表中,按顺序指明所调函 数的各个参数类型。
特点:此方法在编译时将依据声明,校验函数调用 的参数类型、个数是否匹配。个数不匹配则 编译出错;类型不匹配则以形参类型为准自 动转换 (赋值兼容 ) 。
建议:采用原型声明法,可充分利用系统检查能力; 为了便于阅读,参数类型后可包含参数名。
电信学院
30
例:计算三个数整数部分的和。main( ){ int t; int sum( ); /*简单声明不做参数匹配检查*/ t=sum(12.1, 6.9, 3); /*t=sum(12.1, 6.9);*/ printf("SUM=%d\n",t); }int sum(int a, int b, int c){ return(a+b+c); }
-26215
原形声明: int sum(int, int, int );或带参数名声明: int sum(int a, int b, int c);
电信学院
31
2) 声明的位置 主调函数内或源文件开头;
3) 选择声明位置的原则
若函数 f1仅被少数 ( 一两个 ) 函数调用,则选择在主调函数内声明 f1 ,此时对 f1 的调用只能在这些带有声明的函数内调用 ;
若 f1 被若干函数调用,则选择在源文件开头对f1 声明,此时所有函数都可调用 f1且不必再行声明。
电信学院
32
4) 下列情况函数声明可省略
函数返回值类型为 int 型;
被调函数书写在主调函数之前;
函数声明集中书写在源文件开头;
电信学院
33
例: void printstar( ); /* 声明 printstar*/ float fun1(…) { …; printstar( ); /*printstar 在开头说明 */ …; } main( ) { … ; /* 省略所有被调函数声明*/ x=fun1(…); /*fun1 书写在前 */ y=fun2(…); /*fun2 类型为 int*/ printstar( ); …; } /*printstar 在开头说明 */ void printstar( ) { printf("****************"); } int fun2(…) { …… }
电信学院
34
函数调用举例例:已知四边形边长 a 、 b 、 c 、 d 及一条对角线
长 e, 求四边形面积。 a b
cd
e
分析:可分解为求任意三角形面积的问题。将求三 角形面积的功能独立出来,用自定义函数实 现。
电信学院
35
#include "math.h"float ss(float a, float b, float c) /* 函数定义 */{ float t, s; t=(a+b+c)/2.0; s=sqrt(t*(t-a)*(t-b)*(t-c)); return(s); }main( ) { ┇ }
电信学院
36
main( ) { float a, b, c, d, e, area; int i, j; float ss(float, float, float); /* 函数声明*/ printf("\nInput a,b,c,d,e:"); scanf("%f,%f,%f,%f,%f", &a,&b,&c,&d,&e); i=a+b>e&&a+e>b&&e+b>a; j=c+d>e&&c+e>d&&e+d>c; if(i&&j) { area=ss(a,b,e)+ss(c,d,e); /* 函数调用 */ printf("\tarea=%7.2f\n", area); } else printf("\tDATA ERROR!\n"); }
YS
电信学院
37
例:用字符‘ *’打印一个梯形。
main( ){ void pstar(int); int i, n; printf("Enter line number:"); scanf("%d",&n); for(i=1; i<=n; i++) pstar(i*2+3); }void pstar(int sn) /* 有参无返回值*/{ int j; for(j=1; j<=sn; j++) printf("*"); printf("\n"); return; }
***** 5 个******* 7 个 ********* 9 个*********** 11 个************* 13 个
YS
电信学院
38
例:已知双曲正弦 ,输入 x ,
利用 ex 幂级数的前 21项求出 sinh(x) 的值。 2
)sinh(xx ee
x
)...(!
...!3!2!1
132
xn
xxxxe
nx
设计分析:
1) 定义函数计算 ex ,调用实参值分别取 x 、 -x;2) 利用循环和迭代公式: t=t*x/i 计算幂级数和;
电信学院
39
程序: #include "math.h" main( ) { float myexp(float), x, y; printf("Enter x:"); scanf("%f",&x); y=(myexp(x)-myexp(-x))/2.0; printf("sinh(%4.2f)=%f\n", x, y); } float myexp(float x) { int i; float sum=1.0, t=1.0; for(i=1; i<=20; i++) { t=t*x/i; sum=sum+t; } return(sum); }
YS
电信学院
40
6.5 函数的嵌套调用 在一个函数被调用的过程中,又发生了对另一个函数的调用,这种现象称为函数嵌套调用;嵌套的层次可是多层的。
例:用函数求m 个元素中取 n 个的组合。
公式:
定义函数: main: 实现 I/O 、调用 comp 函数 ; comp: 按公式计算、调用阶乘函数 fact; fact: 阶乘函数 ;
)!(!
!
nmn
mC nm
电信学院
41
调用关系: main comp fact
main( ){ int n, m; long c; long comp(int, int); /*原形声明*/ printf("Enter m,n:"); scanf("%d,%d", &m, &n); c=comp(m, n); /* 调用函数*/ printf("C(%d,%d)=%ld\n", m, n, c); } ┇
电信学院
42
main( ) { ┅ }long comp(int m, int n){ long s; long fact(int); /* 声明函数*/ s=fact(m)/(fact(n)*fact(m-n)); /*嵌套调用 */ return(s); }long fact(int x){ int i; long f=1; for(i=1; i<=x; i++) f=f*i; return(f); }
YS
电信学院
43
6.6 函数的递归调用 一个函数被调用的过程中,又发生了直接或间接地自己调用自己,这种现象称为函数递归调用;
递归是一种非常有效的数学方法,也是程序设计的重要算法。对某些问题的处理,采用递归的方法比非递归方法更为有效,或者能够更自然、更明显地反映出解决问题的过程。
但递归调用需要占用大量时间和额外的内存,应综合考虑后决定是否选用递归方法。
电信学院
44
例:用递归法求阶乘
数学中定义阶乘,可用迭代法和递归法定义:
迭代法: n!= n×(n-1)×(n-2)×…×1 递归法: n!=n*(n-1)! (n>1)
递归过程:
5!=5*4! 24 4!=4*3! 6 反 递 3!=3*2! 2 推 归 2!=2*1! 1 1!=1
电信学院
45
main( ){ long fact(int), y ; int num; printf("Enter n:"); scanf("%d", &num); if(num<0) printf("DATA ERROR!"); else { y=fact(num); printf("%d!=%ld\n", num, y); } }long fact(int n){ long f; if(n==0||n==1) f=1; /* 递归控制 */ else f=n*fact(n-1); /* 递归调用 */ return(f); } YS
电信学院
46
递归说明:1) 函数 fact 的执行代码只有一组,递归过程就是 循环执行这组代码段;
2) 每次递归调用,都动态地为形参 n 和局部变量f
分配存储单元, n 接受本次递归传递的实参值;
3) 由分支条件控制递归的继续或终止; 注意:要保证递归是有限的
4) 递归调用过程始终未出现 f 的赋值操作,始终未
执行 return 语句;
5) 递归调用结束的反推过程,将不断执行 return和
f 的赋值。
电信学院
47
递归过程动态分配图示: ┇
┇
调用 3: f=1;
调用 2: f=2*fact(2-1);
调用 1: f=3*fact(3-1);
452
456
460
464
468
472
476
480
484
1
2
6f:
返回指针1
n: 3
f:
返回指针2
n: 2
f:
返回指针3
n: 1
主调 : fact(3)
分配
释放
栈底
电信学院
48
递归和反推过程图示:
y=fact(3);输出 y
f=3*fact(2);return(f);
f=1;return(f);
f=2*fact(1);return(f);
3 12
26 1
main fact 被调 fact 自调1
fact 自调2
电信学院
49
例:输入 1 ~ 32767 之间的正整数,用递归方式分离 各数符并反序输出。如: 2466 4 2void fun(int n){ int d; d=n%10; /* 取 n当前的个位*/ printf("%d", d); n=n/10; if(n!=0) fun(n); } /*条件成立递归调用*/main( ){ int n; do { printf("\nEnter n(1-32767):");
scanf("%d", &n);} while(!(n>=1&&n<=32767)); fun(n); }
YS
电信学院
50
6.7 数组作为函数的参数
实参形式 传递内容 形参形式 特点
常量 变量下标变量 同类型变量名 表达式函数调用
数组名 同类型数组名
实参的值单向传递
数组指针单向传递
调用时动态分配、释放存储单元。
与实参数组共用存储单元。
电信学院
51
1. 形参是变量名、实参是数组元素 ( 下标变量 )例:计算一整型数组各元素的阶乘 main( ) { int a[3]={5, 2, 3}, i; long fact(int); for(i=0; i<3; i++)
printf("%d!=%ld\n", a[i], fact(a[i]) ); } long fact(int n) /* 形参:变量 */ { long f; int i; f=1; for(i=1; i<=n; i++) f=f*i; return(f); }
电信学院
52
2. 实参和形参均为数组名例:定义函数,求一维数组的平均值。#define N 5float average(int a[N]) /* 形参数组*/{ int i; float sum=0; for(i=0; i<N; i++) sum=sum+a[i]; return(sum/N); }main( ){ int i, s[N]; float aver, average(int *); for(i=0; i<N; i++) scanf("%d", &s[i]); aver=average(s); /* 数组名作实参*/ printf("aver=%-7.2f\n", aver); }
电信学院
53
数组作参数的说明:
1) 当不同函数需要对同一个数组进行操作时,可采 用数组的指针作为函数的参数;
2) 数组名代表数组的指针;在发生函数调用时,实 参数组的起始地址将传递给形参数组,使实、形 数组各元素按存储结构一一对应共用存储单元。 函数调用返回时,这种地址对应关系同时消失。图示: 实参数组 s: s[0] s[1] s[2] s[3] s[4] 形参数组 a: a[0] a[1] a[2] a[3] a[4]
起始地址
电信学院
54
3) 既然是共用存储单元,在被调函数中对形参数组 元素赋值,必将影响到实参数组。函数调用返回 后,这种影响依旧存在。实参数组 s: s[0] s[1] s[2] s[3] s[4]
形参数组 a: a[0] a[1] a[2] a[3] a[4]
被调函数中: a[0]=a[0]*10;
1 2 3 4 5 480 482 484 486 488指
针 :480 10
电信学院
55
4) 实参数组和形参数组的类型必须一致,但长度 可相同或不同;若 : 被调函数要利用实参数组所有元素,则长度应一致;若只利用前一部分,则形参数组长度可少些;
如:主调 被调:{ int a[20]; fun(int x[5]) ┇ { int i, t=0; sum=fun(a); for(i=0;i<5;i++) ┇ t=t+x[i]; } return(t); }
电信学院
56
5) 为了使被调函数具有更好的通用性,形参数组 的长度可不指定;此时可设一个整型参数传递 数组长度,控制越界访问。
如 : 主调 被调: /*求前 n 个元素和 */ { int g[20]; sum(int x[ ], int n) ┇ { int i, s=0; y=sum(g, 20); for(i=0; i<n; i++) x=sum(g, 10); s=s+x[i]; ┇ return(s); } }
电信学院
57
例:自定义函数,实现两字串的连接。 ( 不用strcat)
实参 str1:
形参 s1: s1[0] s1[1] …… s1[6]
连接位置 i : 6, 7, 8,…
实参 str2:
形参 s2: s2[0] s2[1] … s2[4] s2[5]
连接范围 j: 0,1,… 4
H e l l o ! \0 …
W a n g . \0
连接起点 i: 6
电信学院
58
#include "stdio.h" main( ) { void mystrcat(char *, char *); /* 函数声明*/ static char str1[20], str2[10]; gets(str1); gets(str2); mystrcat(str1, str2); /* 数组名作实参*/ puts(str1); /*连接结果带回主调函数*/ } void mystrcat(char s1[20], char s2[10]) { int i=0, j=0; while(s1[i]!=0) i++; /*测试空字符的位置*/ for(j=0; s2[j]!=0; j++, i++) s1[i]=s2[j]; s1[i]=0; } /* 最后追加一个空字符*/
YS
电信学院
59
例:定义一个函数,在一有序数组中插入一个数, 使其仍然有序。 插入 n: 13
实参 a:指针形参 a: a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
7 9 10 12 15 17 24 32 45
插入方法:1) 首先确定插入位置,可从头或尾顺序判断查找;2) 将插入点后的各元素依次后移一个位置;3) 二分法 (折半法 )查找插入位置效率较高。
查找 : n>a[i]? (i: 0,1,2…)移动 : a[j+1]=a[j]; (j: 8,7…i)
32241715 4513
电信学院
60
main( ){ void insert(int *, int); /* 函数声明 */ int a[10]={7,9,10,12,15,17,24,32,45}, i, num; printf("Insert number:"); scanf("%d", &num); /*输入要插入的数*/ insert(a, num); /* 调用函数*/ for(i=0; i<10; i++) printf("%6d", a[i]); printf("\n"); }void insert(int a[10], int num) /* 实现插入 */{ ┇ ┇ }
电信学院
61
main( ){ ┅; insert(a, num); ┅; }
void insert(int a[10], int num){ int i, j; for(i=0; i<=8; i++) /*判断插入位置*/ if(num>a[i]); else break; /*退出时 i即为插入位置*/ for(j=8; j>=i; j--) /* 从后面依次后移 */ a[j+1]=a[j]; a[i]=num; /*插入 num*/ return;}
YS
电信学院
62
7 9 10 12 15 17 24 32 45
从尾: n>a[i]? i: 8,7,6… 假: a[i+1]=a[i];
32241715 4513
插入方法 2 : 插入 n: 13
实参 a: 指针形参 a: a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
电信学院
63
main( ){ ┅; insert(a, num); ┅; }
void insert(int a[10], int num){ int i; for(i=8; i>=0; i--) /* 从尾部开始判断*/ if(num>a[i]) break; else a[i+1]=a[i]; /*边判断边后移*/ a[i+1]=num; /*插入 num*/ return; } YS
电信学院
64
例:用选择法编制一个通用的排序函数。#define N 10main( ){ void sort(int *), /* 函数声明 */ void myprint(int *, int); int a[N], i, s; printf("Enter 10 data:"); for(i=0; i<N; i++) scanf("%d,", &a[i]); myprint(a, N); /* 调用输出函数*/ sort(a); /* 调用排序函数 */ myprint(a, N); } ┇
电信学院
65
void myprint(int a[], int n) /*输出一维数组函数 */{ int i, j; for(i=0; i<=n-1; i++) printf("%5d", a[i]); printf("\n"); return; }
电信学院
66
main( ){ …; sort(a); …; }
void sort(int a[ ]){ int i, j, p, t; for(i=0; i<=N-2; i++) { p=i; for(j=i+1; j<=N-1; j++) if(a[j]>a[p]) p=j; /*刷新 p*/ if(p!=i) { t=a[i]; a[i]=a[p]; a[p]=t;}/*交换 */ } return; }
YS
电信学院
67
例:在有序数组中插入若干数,并使其仍然有序。main( ){ int a[10]={5, 9}, n, i; void insert(int a[ ], int, int); for(i=1; i<=8; i++) { scanf("%d", &n); insert(a, n, i); } for(i=0; i<=9; i++) printf("%5d", a[i]); }void insert(int a[ ], int n, int s){ ┇
}
电信学院
68
┇ for(i=1; i<=8; i++) { scanf("%d", &n); insert(a, n, i); } ┇ void insert(int a[10], int n, int s){ int i; for(i=s; i>=0; i--) /* 从尾部开始判断*/ if(n>a[i]) break; else a[i+1]=a[i]; /*边判断边后移*/ a[i+1]=num; /*插入 num*/ return; }
YS
电信学院
69
例:定义函数将一数组中的值按逆序重新存放。#define N 8void restore(int a[N]){ int i, j, t; for(i=0, j=N-1; i<j; i++, j--) { t=a[i]; a[i]=a[j]; a[j]=t; } /*a[i]a[j]*/ return; }main( ){ int a[N]={12,8,6,5,4, 1,18,7}, i, j, m, t; restore(a); printf("\n"); for(i=0; i<N; i++) printf("%6d", a[i]);}
YS
电信学院
70
方法 2 :for(i=0; i<N/2; i++) { t=a[i]; a[i]=a[N-1-i]; a[N-1-i]=t; }
3. 多维数组作为函数参数
实参 形参
二维数组名 二维数组 数组的指针单向指针值的传递
电信学院
71
例: 实参 a 形参 x
main( ) { int a[3][4]; void f1(int a[3][4]); …; f1(a); …; } void f1(int x[3][4]) { …… }
a[0][0]
a[0][1]
a[0][2]
a[0][3]
a[1][0]
a[1][1]
a[1][2]
a[1][3]
a[2][0]
a[2][1]
a[2][2]
a[2][3]
x[0][0]
x[0][1]
x[0][2]
x[0][3]
x[1][0]
x[1][1]
x[1][2]
x[1][3]
x[2][0]
x[2][1]
x[2][2]
x[2][3]
指针
电信学院
72
说明:1) 实参数组、形参数组的各元素将按行一一对应共 用存储单元;
2) 对形参数组各元素所做的赋值操作,必定影响到 实参数组;
3) 在形参数组的定义和函数声明中,可省略第一维 的行长说明,且只能省略行长说明。 如上例定义: void f1(int x[ ][4]) { ┅ } 声明 : void f1(int a[ ][4]);
4) 函数声明时也可不带参数名; 如: void f1(int [ ][4]);
电信学院
73
例:用函数 make 在二维数组中生成 10 行的杨辉三角 ;
用函数 parray输出杨辉三角。 1
1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 7 21 35 35 21 7 1 1 8 28 56 70 56 28 8 1 1 9 36 84 126 126 84 36 9 1
分析 : 1) 0列及对角线元素均为 1 ;右上三角全为0 ;
2) 其余元素为: a[i][j]=a[i-1][j-1]+a[i-1][j]
电信学院
74
#define N 10main( ){ static int a[N][N], i, j; void make(int a[][N]), parray(int [][N]); make(a); /* 函数调用 */ parray(a); }void make(int a[N][N]){ int i, j; for(i=0;i<N;i++) a[i][0]=a[i][i]=1;/* 生成 1元素 */ for(i=2; i<N; i++) for(j=1; j<i; j++) a[i][j]=a[i-1][j-1]+a[i-1][j]; /* 生成其余元素*/ return; } ┇
电信学院
75
main( ){ ┅; parray(a); ┅ }
void parray(int a[][N]){ int i, j; for(i=0; i<N; i++) /*按行输出*/ { for(j=0; j<=i; j++) /*输出直到对角线元素*/ printf("%5d", a[i][j]); printf("\n"); /* 控制换行*/ } return;}
YS
电信学院
76
例:编制程序,生成并打印 n 阶魔方阵。魔方阵的元素为 1 ~ n2 之间的自然数,其中 n 为奇数;方阵的每一行、每一列及对角线元素之和都相等,其和为: n×(n2+1)/2 。
魔方阵排列规律:1) 自然数 1 总是在方阵第一行当中一列上;
2) 后续的自然数在当前数的右上方,即行数减 1 、 列数加 1 的位置。 若当前数在第一行但不在最 后列,则后续数在最后一行的下一列上;若当 前数在最后列,则后续数在上一行的第一列;
3) 若按照规律 2 得出的位置已被占用,则下一个 自然数放在当前数的下一行同列上。
电信学院
77
图示: 5 阶魔方阵
17 24 1 8 15
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9
和数: n×(n2+1)/2=5×(25+1)/2=65
行和: 17+24+1+8+15=65列和: 17+23+4+10+11=65对角和: 17+5+13+21+9=65
电信学院
78
源程序:#define N 5main( ){ void marray(int a[N][N]); int a[N][N]={0}, i, j; marray(a); for(i=0; i<N; i++) /*输出方阵*/ { for(j=0; j<N; j++) printf("%-5d", a[i][j]); printf("\n"); }}void marray(int a[N][N]) { ┅┅ }
电信学院
79
void marray(int a[N][N]){ int i, j, k, t, x, y; i=0; j=N/2; t=N-1; /* 自然数 1 下标的确定 */ for(k=1; k<=N*N; k++) { a[i][j]=k; x=i; y=j; /*产生 i 、 j副本 */ if(i==0) i=t; /* 行为 0 则取最后一行 */ else i=i-1; /*否则取上一行*/ if(j!=t) j=j+1; /*非最后列取下一列*/ else j=0; /*否则取首列 */ if(a[i][j]!=0) { i=x+1; j=y; } } /* 已被占用取下一行的同列*/ return; }
电信学院
80
①
②
③
例:编写程序使方阵转置,并输出转置前后的数据。
要求: 1) 尽可能少地使用存储空间 ; 2) 由函数实现转置、输出。
函数调用关系: main dataout change
电信学院
81
#define N 4 main( ){ void change(int a[N][N]), dataout(int a[N][N], char p[ ]); int a[N][N], i, j; printf("Input %d data:", N*N); for(i=0; i<N; i++) for(j=0; j<N; j++) scanf("%d", &a[i][j]); dataout(a,"Before change:"); /* 调用①*/ change(a); /* 调用② */ dataout(a,"After change:"); /* 调用③*/} ┅
电信学院
82
main( ){ ┇ dataout(a, "Before change:"); ┇ }void dataout(int a[N][N], char p[ ]) /*输出 */{ int i, j; printf("%s\n", p); for(i=0; i<N; i++) { for(j=0; j<N; j++)
printf("%4d", a[i][j]); printf("\n"); } return; }
电信学院
83
main( ){ ┇ change(a); ┇ }void change(int a[N][N]) /*转置*/{ int i, j, t; for(i=0; i<N; i++) for(j=0; j<i; j++) { t=a[i][j]; a[i][j]=a[j][i]; a[j][i]=t; } return; } YS
电信学院
84
函数定义的一般格式: 函数值类型 函数名(参数说明表) { 函数体 }
其中: • 函数值类型:函数返回值的类型。如: int
float double char void 等 ,缺省按整型处理。 无返回值的函数,函数值类型可选用 void ,
如: void putpixel(int x,int y,int color)
电信学院
85
• 函数说明的一般形式: 函数值类型 函数名(参数说明表); 如函数值是整型或字符型,可不必说明; 如被调函数的定义在主调函数之前,可不必说明; 如不说明函数参数,系统不检查参数传递正确性; 参数说明表中可只说明形参类型,而无形参本身。
函数的调用一般形式: 函数名(实参表)
电信学院
86
函数的返回值是通过函数中的 return 语句实现的:
return 表达式; 或 return (表达式);
电信学院
87
函数参数的传递 函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。1. 形参和实参• 在函数定义时,函数名后面圆括号内的参数称为
形式参数 ,简称形参• 在函数调用时,函数名后面圆括号内的参数称为
实际参数,简称实参 • 形参变量一般只在发生函数调用时才被分配内存 单元,调用结束后,所占内存单元即刻被释放。
电信学院
88
• 形参变量只有在函数内部有效 。 • 在函数调用时,先将实参的值赋给形参,然后执
行被调函数的函数体。
实参与形参在数量上,类型上,顺序上应严格一 致。实参可以是常量、变量、表达式或函数等, 要求有确定的值。 实参对形参的数据传递是“值传递”,即单向传
递。 因此,函数执行中形参值的改变不会影响实参的
值。
电信学院
89
调用函数时的传值有一般的数值和地址值两种,
当形参是数组或指针变量,调用时给出的实参
是地址值(数组名或指针),被调函数用该地
址(数组名或指针)通过间接访问可以存取(改变)主调函数中所指向的变量的值。
电信学院
90
例:判断正整数是否为素数的函数。是则返回 1 ; 不是则返回 0 。#include "math.h"
int isprime (int n)
{ int i;
if (n==1) return 0;
if (n==2) return 1;
for(i=2; i< = (int)sqrt(n); i++)
{ if (n%i==0) return 0;}
return 1;}
main(){ int a; printf("Enter a integer :\n"); scanf("%d", &a); if(isprime(a)) printf("%d is prime", a);}
电信学院
91
3. 地址值传递例调用交换两整型量的函数。⑴ 用数值传递void swap ( int a, int b){ int t; t = a; a = b; b = t;}main ( ){ int x = 5, y = 10; swap (x, y); printf("x =%d\ty =%d\n", x, y);
} 运行结果: x =5 y =10 (实际未交换)
电信学院
92
⑵ 改用指针作为函数的参数。void swap ( int *a, int *b){ int t; t = *a; *a = *b; *b = t;}main ( ){ int x = 5, y = 10; swap (&x, &y); printf("x =%d\ty =%d\n", x, y);}运行结果: x =10 y =5注:如果需要函数返回多值,可以通过指针参数或 全局变量来实现
电信学院
93
4. 数组作为函数参数 • 实参用数组名,形参用数组例 4 :求字符串长度。int my_strlen (char s[ ]){ int i; for (i=0; s[i]!='\0'; i++); return i;}main ( ){ char a[50]; gets(a); printf("strlen=%d\n", my_strlen(a)); }
电信学院
94
例 5 :以下程序的运行结果是什么 ?
#include <stdio.h>f(int a[]){ int i=0;
while(a[i]<=10) { printf("%d,", a[i]);
i++; }
}main(){ int a[]={ 1, 5, 10, 9, 11, 7 }; f(a+1);}
运行结果: 5,10,9
电信学院
95
例 6 :请读程序:#include <stdio.h> 结果是 : ______ 。f (int b[ ], int n) A) 720{ int i, r=1; B) 120 for (i=0; i<=n; i++) C) 24 r=r*b[i]; D) 6 return r;}main ( ){ int x, a[ ]={ 2, 3, 4, 5, 6, 7, 8, 9 }; x=f(a, 3); printf ("%d", x); }
B
电信学院
96
例 7 :写出下列程序的运行结果fun (int a[][3]){ int i, j, sum=0; for(i=0; i<3; i++)
for(j=0; j<3; j++) { a[i][j]=i+j;
if(i==j) sum=sum+a[i][j];
} return(sum);}另一例题: cp63.c
main()
{ int a[3][3]={1, 3, 5, 7, 9, 11, 13, 15, 17}; int sum; sum=fun (a); printf("\nsum=%d\n",sum);}
运行结果: sum=6
电信学院
97
函数的嵌套调用是指程序在调用函数过程中又调用其它函数的过程。
电信学院
98
例:给出年、月、日,问是星期几(星期日~六 用 0 ~ 6 表示)。
int isleap( int y ) /* 是闰年返回 1 ,否则为 0 */
{ return ((y%4==0 && y%100!=0) || y%400==0); }
int days_pm(int y, int m) /* 返回 y 年 m月的天数 */
{ int dpm[13]={ 0, 31, 28, 31, 30, 31, 30, 31,
31, 30, 31, 30, 31};
return dpm[m]+( m==2 ? isleap(y) : 0 );
}
电信学院
99
int lastyear ( int y ) /* 返回 y-1 年 12 月 31日是星期几 */
{ int i, n=y-1; /* 每年 365天为 52 个星期多 1天 */
for(i=1; i<y; i++) n+=isleap(i); /* 加上闰年天数 */
return n%7;}main( ){ int y, m, d, n, i; printf("\nInput date (yyyy-mm-dd):"); scanf ("%d-%d-%d",&y, &m, &d); for(n= d+lastyear(y), i=1; i<m; i++) n+=days_pm(y, i); printf("%d\n", n%7);}
电信学院
100
函数的递归 一个函数直接或间接地调用该函数自身称为
递归。这样的函数称为递归函数。
f 函数
调用 f 函数
f 1 函数 f2 函数
调用 f 2 函数
调用 f 1 函数
电信学院
101
例:编函数,求 n 的阶乘。 阶乘的计算式: 1. n!=1*2*3*...*n
1 n = 0, 1 2. n!= n*(n-1)! n >1
按计算式 1 :int factor ( int n ) { int i, f =1; for ( i=1; i< = n; i++) f =f * i; return f; }
电信学院
102
按计算式 2 ,采用递归函数:long factor (int n)
{ if (n>0)
return n*factor(n-1);
return 1;
}
• 要有递归公式 • 要有递归终止条件 • 处理问题不断简化 • 时空上无任何节约, 描述问题可能简单
1*factor(0)
2*factor(1)
3*factor(2)
4*factor(3)
factor(4)
1×1
2×1
3×2
4×6
n=4 时,递归过程:
电信学院
103
例:采用递归函数给一个不多于 5 位的正整数,按逆序打印出各个位上的数字,并求出它是几位数int i=0;rev_print(long n) { printf("%d", n%10); if(n>9) rev_print(n/10); else printf("\n"); return i=i+1}
main(){ long a; printf("Enter number:\n"); scanf("%ld", &a);
printf("%d\n",rev_print(a));
}
电信学院
104
例:写出下列程序的执行结果void digui(int n)
{ int i=0;
if(n>=10) digui(n/10);
while(i++<n%10)
printf("*");
printf("%d#\n", n%10);
}
main()
{ digui(32164);}
运行结果:
***3#
**2#
*1#
******6#
****4#
电信学院
105例:写出下列程序的执行结果void prtlist( int n ){ if ( n == 0 ) printf( "%d", n ); else { printf( "%d", n ); prtlist( n-1 ); printf( "%d", n ); prtlist( n-1 );
}}main(){ prtlist( 2 ); }
运行结果: 2 1 0 1 0 2 1 0 1 0
电信学院
106
电信学院
107
6.8 局部变量和全局变量变量的存在和可引用范围称为变量的作用域。变量按作用域范围可分为两种:局部变量和全局变量。
1.局部变量 • 局部变量是在函数(包括主函数)内定义,在该 函数内部有效,即其作用域仅限于函数内 。也称
内部变量。 • 函数内的形参和定义的其他变量都是局部变量。 • 函数调用结束,为局部变量分配的内存便释放, 但静态局部变量除外。
电信学院
108
局部变量有效范围图示: f1(int a) { int b, c; a, b, c 有效 ┇ ( 形参也为局部变量 ) } f2(float x, float y) { int i, j; x, y, i, j 有效 ┇ } main( ) { int i, j, x, m; i, j, x, m 有效 ┇ (main 中变量也为局部变量 ) }
电信学院
109
2. 全局变量• 在函数外部定义的变量统称全局变量,也称外部
变量。 • 全局变量作用域自定义处起直至程序结束。在其 后的各个函数中都有效。 • 全局变量和某函数的局部变量同名,则在局部变 量的作用范围内(即该函数内)由局部变量起作 用,外部变量被“屏蔽”,即不起作用。
电信学院
110
全局变量有效范围图示: int p, q; f1(int a) p, q 有效 { int b, c; …; } f1,f2,main 可使
用; char c1, c2; f2(float x, float y) c1, c2 { int i, j; 有效; …; } main( ) f2, main { int i, j, x, m; 可使用; ┇ }
电信学院
111
例:全局变量和局部变量同名int a=3,b=5; /*a, b 为全局变量 */max(int a, int b) /* 形参 a, b 为局部变量 */{ int c; c=a>b?a:b; 形参 a, b 作用范围
return (c);}main(){ int a=8; /*a 为局部变量 */ 局部变量 a 的作用范围
printf("%d",max(a,b)); 全局变量 b 的作用范围}
电信学院
112
例:定义函数可求任意矩阵最大元素值及行列位置; 要求:在 main 中实现输入、输出。
分析:1) main 和自定义函数需对同一数组操作,应选数 组名做参数;
2) 自定函数需返回三个值,可用 return 返回其一, 而由全局变量返回其余两个;
3) 为了能处理任意矩阵,用符号常量说明数组的 行长、列长。
电信学院
113
#define N 3#define M 4int line, col; /* 定义全局变量 */main( ){ int a[N][M], i, j, max, fun1(int a[][M]); for(i=0; i<N; i++) /* 数组输入 */ for(j=0; j<M; j++) scanf("%d", &a[i][j]); for(i=0; i<N; i++) /* 数组输出 */ { printf("\n"); for(j=0; j<M; j++) printf("%4d", a[i][j]); } max=fun1(a); printf("\nMAX: a[%d][%d]=%d\n", line, col, max);} /*输出全局变量及局部量 max*/ ┇
电信学院
114
int fun1(int x[N][M]){ int i, j, max; max=x[0][0]; line=col=0; for(i=0; i<N; i++) for(j=0; j<M; j++) if(x[i][j]>max) { max=x[i][j]; /* 在局部变量中记录最大值*/ line=i; /* 在全局变量中记录行列下标*/ col=j; } return(max); }
YS
电信学院
115
全局变量使用规则:
1) 全局变量在程序的整个运行期间有固定的存储单 元,某个函数对全局变量的赋值操作,是针对整 个有效范围的;
2) 全局变量是静态变量,未赋初值时初值自动为 0 ;
例: int a, b=5; main( ) { printf("%d,%d", a, b); } 0,5
电信学院
116
3) 若全局变量与局部变量同名,则在全局变量有效 区间,全局变量暂时失效;例: int a=3, b=5; max(int a, int b) { int c; 全局 a,b失
效 c=a>b? a:b; return c; } main( ) { int a=8; 全局 a失效 printf("%d", max(a, b); } 8
电信学院
117
4) 若全局变量 p 定义点之前的函数 f1 想引用 p ,一 是将定义点提前,或在函数 f1 内做外部变量说 明,使 p 的有效范围扩展到 f1 中;
例: f1(int a) { extern int p; /* 外部变量说明*/ …; if(p>a) ~ ; …; } p 有效 int p, q; ┇ p, q ┇
5) 结构化程序设计思想强调函数间的偶合性越小 越好,实际编程中应尽量少用或不用全局变量。
电信学院
118
6.9 变量的存储类别程序运行时的内存分配情况:
系统区程序代码区静态数据区动态数据区自由空间
变量对象 存储方式 生存期静态局部变量、全局变量 静态 程序运行全过程自动局部变量、形参变量 动态 函数被调用期间
变量的存储方式及生存期:
固定位置
临时分配
静态局部变量全局变量
自动局部变量形参变量
电信学院
119
1. 变量的存储类别
共有四种: auto—— 自动 static——静态 register——寄存器 extern—— 外部
2. 局部变量的存储类别 可采用: auto 、 static 、 register
register :用 CPU 的通用寄存器保存变量;特点是
访问速度快,适合于使用频率高的变量;通常设两个左右为宜。
电信学院
120
auto :对局部变量无特殊要求时采用;特点是函数被调用时动态分配、释放,可节省内存资源; auto 型变量一定是局部变量。 auto 型变量在函数内部定义,作用于该函数内。 auto 型变量存储在堆和栈区中,随函数调用而 建立,随函数调用结束而释放。 通常, auto 存储类别的说明都省略不写,如: auto int i ; 写成 int i ;
电信学院
121
static :static 型变量存储在静态数据区, static 说明符可用于局部变
量,也可用于全局变量。 static 型变量与 auto 型变量有两点不同: auto 型变量所占内存在程序退出函数时释放; static 型变量所占内存要到程序结束才会释放。 static 型变量的初始化只在第一次调用函数时进行,以后
若反复调用函数是保留上次调用函数后的值。因为: 静态局部变量在程序的整个运行期间始终在静态存储区占据着固定的存储单元,但只有发生函数调用时才可对其引用或赋值,函数调用结束它仍然保存其值和存储单元,
函数再次被调用时又可继续引用。
电信学院
122
例:
int fun(int a){ auto int b, c=3; /* 调用时分配单元并赋初值*/ static int x, y=1; /* 编译时分配单元并赋初值*/ ┇ }
说明: 静态局部变量 x 、 y 存储单元的分配和 y 赋初值是一次性的。 x, y a, b, c
静态存储区动态存储区
电信学院
123
外部 ( extern ) 存储类别 如对一个全局变量的存储类别未作任何说明,则 认为它是 extern 型全局变量,即隐含的 extern 型全
局变量。 系统为隐含的 extern 型全局变量分配内存。 系统不再为显式地冠以 extern 存储类别说明的变 量分配内存,而是在本文件或其他相关文件中去 寻找缺省存储类别说明的(即隐含的 extern 型) 同名的全局变量。 若要规定本文件内定义的全局变量不准其它文件 运用,则可在定义时说明为静态存储类别,如: static int a;
电信学院
124
extern 型变量使用说明:
int data;main(){
func(){ extern int data;
extern int data;
static int data;
……
…
……
……
file1.c file2.c
file3.c file4.c
说明为全局变量在其他文件定义的全局变量,本文件有效
在其他文件定义的全局变量,函数内有效
全局变量,存储在数据区,只限本文件有效
电信学院
125例:读程序 (静态变量和全局变量 ) ,写结果。int d=1;func(int p){ static int d=4; d+=p++; printf("p=%d d=%d",p,d);}main(){ int i, a=5; for(i=0;i<2;i++) { func(a); d+=a++;
printf(" a=%d d=%d\n",a,d); }}
运行结果:p= 6 d= 9 a= 6 d= 6p= 7 d=15 a= 7 d=12
电信学院
126
6.7 使用库函数 【例 6.26】利用库函数中的产生随机数的函数,编 制 100 以内加法运算练习程序,连续答对 5 题程序 结束。 • 随机数产生函数 原型: int random( int num ) ;
功能:返回一个 [0, num-1]区间的整型随机数 • 初始化随机数发生器函数: 原型: int randomize( void ) ;
功能:用一随机值(时间)初始化随机数发生器
电信学院
127
• random() 和 randomize() 函数原型包含在头文件 stdlib.h 中,由于 randomize() 函数要调用 time()
函数,所以使用 randomize() 函数时,还需包含
头文件 time.h 。#include <stdlib.h>
#include <time.h>
main ( )
{ int a, b, c, i=0;
randomize(); /*初始化随机数发生器 */
电信学院
128
while (i<5) { a = random (100 ); b = random (100); if (a+b>=100) continue; /* 超出范围,重出题 */
printf ( " %d+%d=? " , a, b ); scanf ( " %d " , &c ); if ( c == a + b) { printf ( " The answer is right. " ); i ++; } else { printf ( " The answer is wrong! " ); i=0; } } printf ( " \n Program is over.\n " );}
电信学院
129
例:求 1+2+3+…+nlong fun1(int k) k: 动态存储区 { static long s=0; s=s+k; s: 静态存储区 return s; } /* 返回阶段累加和 */main( ){ int n, i; long sum, fun1(int); printf("Enter a number:"); scanf("%d", &n); /*输入 5n*/ for(i=1; i<=n; i++) sum=fun1(i); printf("1+2+…+%d=%d\n", n, sum);}
01361015
12345
电信学院
130
例:定义高效的求阶乘函数,求多个整数的阶乘。思路:1) 多个整数存入数组中 : int a[5]={2, 5, 5, 3,
8};
2) 阶乘函数的调用形式: 循环体中: f=fact(a[i]); i取值: 0 、 1 、…
4
3) fact 函数需要保留的数: 上一个数 静态局部变量 n0 上一个数的阶乘值 静态局部变量 f
4) fact 函数分支处理情况: 当前数 > 上一个数、当前数 < 上一个数、 当前数 == 上一个数
电信学院
131
源程序:long fact(int);main( ){ int a[5]={2, 5, 5, 3, 8}, i; long f; for(i=0; i<5; i++) { f=fact(a[i]); printf("\t%d!=%ld\n", a[i], f); } }long fact(int n){ …… }
电信学院
132
main( ){ int a[5]={2, 5, 5, 3, 8}, i; for(i=0; i<5; i++) { f=fact(a[i]); …; } }long fact(int n){ int i; static long f=1; f: static int n0=0; n0: if(n==n0) return(f); else if(n>n0) i=n0; else { f=1; i=0;} for(i=i+1; i<=n; i++) f=f*i; n0=n; /*保存本次形参值供下次比较用*/ return(f); }
┇
1
0┇
静态存储区
2
2
120516
3
40320
8
YS
电信学院
133
3. 全局变量的对外类别 可采用: static 标识或不标识;
标识 static ,只允许本源文件中的函数引用;
不标识,允许本源文件中的函数及其它源文件中的函数引用;但要求在其它源文件作外部变量声明。
如: file1.c 内容 file2.c 内容 int p, q; /* 定义 */ extern int p, q; /* 声明 */ main( ) float f1(…) { …… } { …… } int fun(…) void f2(…) { …… } { …… }
电信学院
134
TC 中多个源文件的 C 程序调试:
两种方法:1 、利用项目文件实现2 、利用包含命令实现
电信学院
135
利用项目文件实现:1) 首先分别编辑、编译 file1.c 和 file2.c ;
2) 在 TC 下编辑一个项目文件,扩展名为 .PRJ ;如 : 文件内容—— c:\1109zyc\cprg\file1.c c:\1109zyc\cprg\file2.c 文件内容指出了要参加联调的源程序文件名; 并存入 c:\1109zyc\cprg\MYPRG.PRJ
3) 选择菜单: project | project name 会话输入: c:\1109zyc\cprg\MYPRG.PRJ
4) 编辑状态按 F9, 将按照MYPRG.PRJ文件的指示 编译、链接,生成一个 MYPRG.EXE文件。
电信学院
136
利用包含命令实现:
1) 首先分别编辑、编译 file1.c 和 file2.c ;
2) 在 file1.c 中使用文件包含预编译命令: #include " c:\1109zyc\cprg\file2.c"
3) 启动编译操作,在正式编译之前的预编译阶段, file2.c 的内容将被包含到 file1.c 中;正式编译阶 段,被编译的内容是 file1.c 、 file2.c 合并内容。
4) 启动链接操作,生成一个可执行文件file1.exe 。
电信学院
137
例:求 3×4矩阵最大元素及位置,程序的两个函数分别编制在 file1.c 和 file2.c 两个源文件中。FILE1.C——int line, col; /*全局变量定义 */extern int fun1(int x[][4]); /* 外部函数声明*/main( ){ int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12},i,j,max; for(i=0; i<3; i++) /*按行输出矩阵 */ { printf("\n"); for(j=0;j<4;j++) printf("%4d", a[i][j]); } max=fun1(a); /* 函数调用 */ printf("\nMAX: a[%d][%d]=%d\n",line,col,max);}
电信学院
138
FILE2.C——
extern int line, col; /* 外部变量声明*/int fun1(int x[][4]){ int i, j, max; max=x[0][0]; line=col=0; for(i=0; i<3; i++) for(j=0; j<4; j++) if(x[i][j]>max) { max=x[i][j]; line=i; col=j; } return(max); } 项目文件MYPRG.PRJ
YS
电信学院
139
例:设计某源文件中的全局变量只允许本源文件中 的函数引用。
PRG1.C——static int max, min; /* 不对外 */char c1, c2; /* 对外 */int f1(…){ …… }float f2(…){ …… }
注:带有 static 标识的全局变量仅限于本源文件中 的函数引用,即便其它源文件作了外部变量 声明也是如此。
电信学院
140
6.10 内部函数与外部函数
1. 内部函数——用 static 标识的函数,只能被本源程
序文件中的函数调用;例: static float f1(int x, int y) /*限定 f1 不对
外 */ { …… }
2. 外部函数——不作标识的函数,允许被其它源程 序文件中的函数调用;但必须在调 用的源文件中作外部函数声明;例: file1.c file2.c int fun1(int x) extern int
fun1(int x); { …… } ┇
电信学院
141
函数举例:
【例】定义一个函数,可将一个四位的正整数转换为一个字符串,并以空格分隔输出各字符。 如:输入整数 1990 ,输出字符序列 1 9 9 0
分析:1) 在函数中从高位开始不断分离各位数符,并转 换为对应数字字符; 转换方法:数符 +’ 0 ’ 或:数符 +48 如: 4+’0’ 52 ’4’
2) 定义一个字符数组,按高位在前的顺序存放各 个字符。
电信学院
142
源程序清单:main( ){ void inttostr(int, char *); int i, n; static char str[5]; printf("\nEnter n(1000--9999):"); scanf("%d", &n); inttostr(n, str); for(i=0; str[i]!=0; i++) printf("%c ", str[i]); /*空格分隔输出各元素*/ }void inttostr(int n, char s[5]){ …… }
电信学院
143
main( ){ …; inttostr(n, str); …; }void inttostr(int n, char s[5]){ int i, t, m=1000; for(i=0; i<4; i++) { t=n/m; /* 分离出高位数符 */ s[i]=t+'0'; /* 数值转为字符 */ n=n-(t*m); /*祛除数值高位 */ m=m/10; /* 使 m缩小 10倍*/ } s[i]='\0'; /*插入串结束符 */ return;}
YS
电信学院
144
【例】用递归法将一个整数转换成字符串输出。#include "stdio.h"char ch[12]={0}; /* 定义全局数组 */main( ){ void inttostr(long); long num; scanf("%ld", &num); inttostr(num); puts(ch); }void fun(long n){ static int i=0; if(n<0) { ch[0]='-'; i++; n=-n; } if(n/10!=0) fun(n/10); /* 递归调用 */ ch[i]=n%10+'0'; /* 数值转换为字符*/ i++; return; }
YS
电信学院
145
【例】有一篇三行文字的文章,每行最多 80 个字符,编写函数,由实参传递字符串,分别统计文章中大写字母、小写字母、数字、空格及其它字符的个数。
分析: 自定义函数需返回多个统计结果,可采用一个专门的数组存放统计结果,并将其作为函数参数,在被调函数中计数。
电信学院
146
#include "stdio.h"main( ){ char str[3][80]; static int c[5], i; /*c 各元素为0*/ void count(char [ ][80], int [ ]); for(i=0; i<=2; i++) /*输入三行字串 */ gets(str[i]); count(str, c); /* 实参:两个数组指针*/ for(i=0; i<=4; i++) /*输出统计结果 */ printf("%5d", c[i]);}void count(char s[3][80], int c[5]){ …… }
电信学院
147
main( ){ …; count(str, c); …; }void count(char s[3][80], int c[5]){ int i, j; char t; /* 用临时变量 t 提高效率 */ for(i=0; i<=2; i++) for(j=0; (t=s[i][j])!=0; j++) if(t>='A'&&t<='Z') c[0]++; else if(t>='a'&&t<='z') c[1]++; else if(t>='0'&&t<='9') c[2]++; else if(t==32) c[3]++; else c[4]++; return;}
YS
电信学院
148
【例】编写函数,用折半查找法在一个已排序的数组中,查找是否存在某数,若存在输出其位置,不存在输出无此数的信息。
步骤:1) 设三个变量 l, m, h, 其中 l,h 存放查找范围第一和 最后元素的下标, m 存放查找范围中间元素下标 即 m=(h+l)/2 ;或 m=(h- l)/2+l2) 判断要查找的数 x 与 a[m] 是否相等,相等已找到;3) 若 x>a[m], 则查找范围可缩小到后一半;查找前 更新 l=m+1, 计算新m; 再重复第二步;4) 若 x<a[m], 则查找范围可缩小到前一半;查找前 更新 h=m-1, 计算新m; 再重复第二步;
电信学院
149
2 3 5 7 12 15 23 46 50 100
0
图示:若查找 50 na[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
9(h+l)/2=4m:
n>a[m] 为真: m+1=5l:
l: h:
5 9h:
4
(h+l)/2=7m:
7
n>a[m] 为真: m+1=8l:
8
(h+l)/2=8m:
9 h
8
n==a[m] 为真:找到
电信学院
150
2 3 5 7 12 15 23 46 50 100
0
图示:若查找 3 na[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
9(h+l)/2=4m
n>a[m] 为假:
l: h:
0 3h:
4
(h+l)/2=1m1
n==a[m] 为真:找到
l: m-1=3h
m:
m:
电信学院
151
main( ){ int found(int *, int); int a[10]={2,3,5,6,12,15,23,46,50,100}, num, p; printf("Enter check number:"); scanf("%d", &num); /*输入要查找的数*/ p=found(a, num); if(p==-1) printf("%d Not be found!\n", num); else printf("%d found in array element %d\n", num, p+1);}void found(int a[10], int n){ …… }
YS
电信学院
152
main( ) { …; p=found(a, num); …; }int found(int a[10], int n){ int l, m, h, mark=-1; /*make 找到标志 */ l=0; h=9; while(l<=h) { m=(h+l)/2; /*求中间元素位置*/ if(n==a[m]) { mark=m; break;} else if(n>a[m]) l=m+1; else h=m-1; } return(mark);}
电信学院
153
【例】编写程序,将一个二进制数字字串转换为十进制整数;如字串” -10011” ,转换为整数 -19 。
要求:1) 数字字串可带有 + 或 - 号,也可不带;2) 字串不带小数部分;3) 串中出现无效字符则显示 Invalid string! 信息。
分析:1) 先对符号字符 + 或 - 处理,用整数 1 或 -1 表示符号;2) 再顺序地由第一个数符或最后一个数符开始,将 字符转换为数值,并不断按权相加; 3) 带入符号位;
电信学院
154
源程序:main( ){ char b[20]; void strtoint(char b[ ]); printf("Input binary string(1-15):"); scanf("%s", b); strtoint(b); /* 实参:数组名*/ getch( ); }void strtoint(char b[ ]) /* 形参:字符数组*/{ ┅ }
电信学院
155
void strtoint(char b[ ]){ int num, i=0, s=1, emark=0; if(b[0]=='-'||b[0]=='+') /* 对符号的处理*/ { s=b[0]=='-'?-1:1; i++; } for(num=0; b[i]!=0; i++) if(b[i]=='1'||b[i]=='0') num=num*2+b[i]-'0'; else { emark=1; break;} /* 出现非法字符*/ if(emark==0) { num=s*num; printf("string=\"%s\"\tnum=%-d\n", b, num); } else printf("Invalid string!"); return; }
YS
电信学院
156
【例】用自定义函数判断一个数是否素数;在主函 数 main 中输出 100 ~ 200 之间的全部素数。
#include "math.h"main( ){ int num, m, count=0, prime(int); for(num=101; num<=199; num=num+2) { m=prime(num); if(m) { printf("%5d", num);
count++; if(count%10==0)printf("\n"); }
} }
电信学院
157
main( ){ …; for(num=101; num<=199; num=num+2) { m= prime(num); if(m) { …; } } }int prime(int n){ int i, k, mark=1; k=sqrt(n); for(i=2; i<=k; i++) if(n%i); /*余数非零为真 */ else { mark=0; break;} /* 出现约数中断*/ return(mark);}
YS
电信学院
158
【例】用自定义函数将一方阵在原数组中转置。#define N 5main( ){ void change(int a[ ][ ]), parray(int a[ ][N]); int a[N][N], i, j; printf("\nEnter A array(%d):", N*N); for(i=0; i<N; i++) for(j=0; j<N; j++) scanf("%d", &a[i][j]); printf("\nOld A array:"); parray(a); change(a); /* 数组名作实参 */ printf("\nNew A array:"); parray(a);}
电信学院
159
void change(int a[N][N]){ int i, j, t; for(i=0; i<N; i++) /* 以对角线为对称轴交换*/ for(j=0; j<i; j++) { t=a[i][j]; a[i][j]=a[j][i]; a[j][i]=t; } return; }void parray(int a[ ][N]){ int i, j; for(i=0;i<N;i++) { printf("\n"); for(j=0;j<N;j++) printf("%5d", a[i][j]); }}