Download presentation
Presentation is loading. Please wait.
Published byMathieu Bourget Modified 5年之前
1
江南大学控制科学与工程研究中心 张正道(wxzzd@hotmail.com)
2
第8章 函数 函数参数和函数的值 概述 函数定义的一般形式 函数的调用(嵌套调用、递归调用) 数组作为函数参数
第8章 函数 概述 函数定义的一般形式 函数参数和函数的值 函数的调用(嵌套调用、递归调用) 数组作为函数参数 局部变量和全局变量 动态存储变量和静态存储变量 内部函数和外部函数
3
§8.1 概 述 一. 模块与函数 1. 结构化程序设计 2.“自顶向下”的模块化程序设计方法 ◆一种设计程序的技术。
§8.1 概 述 一. 模块与函数 1. 结构化程序设计 ◆一种设计程序的技术。 ◆通常采用自顶向下逐步求精的设计方法和单入 单出控制结构。 ◆ C语言是结构化的程序设计语言。 2.“自顶向下”的模块化程序设计方法 将一个大问题按层次分解成多个方便解决小问题的模块的设计思想.
4
3. 功能模块 求解较小问题的算法和程序称作“功能模块”, 各功能模块可以先单独设计,然后将求解所有子问题的模块组合成求解原问题的程序。
由功能模块组成程序的结构图 主控模块 模块1_1 模块1_n 模块2_1 模块2_n 模块n_1 模块n_n 模块1 模块2 模块n
5
4. 函数: 完成相对独立功能的程序 [例] 输入年月日,计算出该日为该年的第几天。 分析:
4. 函数: 完成相对独立功能的程序 [例] 输入年月日,计算出该日为该年的第几天。 分析: (1)判断年份是否为闰年。闰年的二月为29天,平年的二月为28天。因此,给定一个年份,首先应确定其是否是闰年。 (2)求月份的天数。1、3、5、7、8、10、12月每月31天,4、6、9、11月每月30天,2月根据所在年份是否为闰年来确定。 (3)求总天数。分为经历完整的月份天数与经历不完整的月份天数。 (4)输出数据。年月日及相应的天数。
6
(1)判断闰年。 程 序 int leap(int year) 实 现: {int lp;
主控模块 判断闰年 求某月的天数 输 出 输 入 求总天数 程序模块结构图 (1)判断闰年。 程 序 实 现: int leap(int year) {int lp; lp=(year%4==0&&year%100!=0 ||year%400==0)?1:0; return lp;}
7
(2)求某月的天数。 /*函数month_days调用函数leap判断是否为闰年*/ int month_days(int year,int month) { int ds,d; switch(month) {case 1: case 3: case 5: case 7: case 8: case 10: case 12:d=31;break; case 2:d=leap(year)?29:28;break; /*若为闰年,d赋值29,否则赋值28*/ default:d=30;} return d; }
8
(3)求天数和。 /*函数days调用month_days求各月份对应的天数*/ int days(int year,int month,int day) {int i,ds=0; for (i=1;i<month;i++) ds=ds+month_days(year,i); ds=ds+day; return ds; }
9
注意:在完整的程序中,前三个函数应放在main( )函数之前。
(4)主函数: void main() { int year,month,day,t_day; printf("Input year-month-day:\n“); scanf("%d-%d-%d“,&year,&month,&day); t_day=days(year,month,day); /*求天数和*/ printf(“%d-%d-%d is %dth day of the year!\n” ,year,month,day,t_day); } 注意:在完整的程序中,前三个函数应放在main( )函数之前。
10
二. 模块设计原则 1. 模块相对独立性 (1)模块实现相对独立的特定子功能。模块的功能单一、任务明确,函数可以相互调用但定义相互独立,一个函数不从属于另一个函数,。 (2)模块之间的关系简单。模块间只通过数据传递发生联系,且传递的数据个数越少越好。例如不能使用goto语句跳到另一个函数,以保证函数的独立性。 (3)模块内数据的局部化。模块内使用的数据具有独立性,一个模块不允许使用其它模块的数据,且一个模块的数据也不能影响其他模块中的数据。例如,C语言的局部变量就可以满足模块内数据局部化的要求。
11
2. 模块大小适中 3. 模块分解层次清楚 模块化程序设计要求对问题进行逐层分解、逐步细化,形成模块的层次结构。。 分解问题时要注意对问题进行抽象,将问题中的相似方面集中和概括起来,暂时忽略它们之间的差异,采取自上而下、逐步求精的方法实现.
12
例 a b c main d e f g h i 一个较大的程序是由若干个程序模块组成,用子程序实现模块
{printstar(); printmessage(); printstar(): } printstar() { printf("***************\n");} print_message() { printf (" How do you do?\n"); } *************** How do you do? a b c main d e f g h i
13
函数说明: (1)一个C程序由一个或多个源程序文件组成。 (2)一个源程序文件又由一个或多个函数组成.以源文件作为编译单位。
(3)C程序从main函数开始执行. 在main函数中结束函数的运行。主函数不能被其它函数调用,但其他函数可互相调用. (4)所有的函数都是平行的,不允许嵌套定义函数。 (5)用户使用的角度 ( 标准函数、自定义) (6)函数的形式 (无参函数、有参函数)
14
一. 标准库函数与头文件 从函数使用的角度来看,C语言的函数可以分为两类:标准库函数和用户自定义函数。
Turbo C系统提供了400多个标准库函数(参见附录Ⅲ),按功能可以分为: (1)类型转换函数 (2)字符判别与转换函数 (3)字符串处理函数 (4)标准I/O函数 (5)文件管理函数 (6)数学运算函数等。 它们的执行效率高,用户可在程序中直接进行调用。
15
2. 头文件 C语言库函数所用到的常量、外部变量、函数类型和参数说明,都在相应的头文件(扩展名为.h)中声明,这些文件通常存放在系统目录tc\include。如: (1) stdio.h文件:标准输入输出函数所用的常量、结构、宏定义、函数的类型、参数的个数与类型的描述。 (2) math.h文件:与数学函数有关的常量、结构及相应的函数类型和参数描述。 (3) string.h文件:与字符串操作函数有关的常量、结构以及相应的函数类型和参数描述。 (4) stdlib.h文件:与存储分配、转换、随机数产生等有关的常量、结构以及相应函数的类型和参数描述。 (5) ctype.h文件:字符函数有关的常量、宏定义以及相应函数的类型和参数描述。
16
二. 用户自定义函数 1. 函数分类 (从函数的形式看) ◆函数定义时无参数说明 无参函数 ◆调用无参函数一般用来执行指定的一组操作
◆主调函数不传送数据给被调函数 ●函数定义时定义了一个或一个以上的参数 有参函数 ●调用时将要处理的数据传送给被调函数 ◆定义时既无参数也无执行语句 空函数 ◆被调用时,不执行任何操作就立即返回
17
§8.2 函数定义的一般形式 1. 无参函数的定义形式: 类型标识符 函数名 ( ) {说明部分 语句}
1. 无参函数的定义形式: 类型标识符 函数名 ( ) {说明部分 语句} 2. 有参函数的定义形式: 类型标识符 函数名 ( 形式参数表列) { 说明部分 语句}
18
int max (int x , int y) {int z ; z=x>y? x : y ; return (z); } z并不是形式参数,而是一般变量,所以在说明部分定义. 如果在定义函数时不指定类型,系统隐含指定函数为 int 型。
19
3.空函数的形式: 类型说明符 函数名( ) { } 此语句的功能是使程序结构清楚,可读性好,以后扩充新功能方便。如: dummy() { }
20
4.传统与现代的形参的说明方式 传统形参说明方式: 函数值类型名 函数名(形式参数列表) 形式参数说明 {数据说明部分 语句 }
函数值类型名 函数名(形式参数列表) 形式参数说明 {数据说明部分 语句 } 如: int max(a,b) int a,b; 建议使用 现代形参说明方式: 函数值类型名 函数名(形式参数类型及参数说明) {数据说明部分 语句 } 如: int max(int a,int b) ANSI风格
21
5. 函数由函数说明与函数体两部分构成。 (1)函数说明 ◆函数说明包括函数值类型、函数名、参数类型及参数说明. ■函数说明又称为函数首部。
5. 函数由函数说明与函数体两部分构成。 (1)函数说明 ◆函数说明包括函数值类型、函数名、参数类型及参数说明. ■函数说明又称为函数首部。 ◆函数值类型指定所定义函数返回值的类型,可以是简单类型、void类型或构造类型等。 ■当函数值类型为void时,表示函数无返回值,相当于其它语言的过程。当函数值类型为int时,可省略其类型的说明,建议不使用缺省形式类型说明.
22
◆函数名是函数的标识符,遵循C语言标识符的命名规则,区分大小写。
■形式参数简称形参,处在函数名后的一对圆括号中。要特别注意的是,无论函数是否有形式参数,函数名后的圆括号不可省,并且圆括号之后不能接“;”。 ◆形式参数属于所在函数的局部变量,其存储类型只能是auto型或register型,缺省为auto型。
23
(2)函数体 ◆函数说明之后的花括号“{}”部分为函数体。 ■函数体内数据说明在前,执行语句在后。 ◆函数体中说明的变量是该函数调用时有效的局部变量,执行语句是实际生成命令代码的部分。 ■函数的功能由函数体内的各个语句来实现。 ◆函数体结束在“}”括号处。
24
例: 定义符号函数sign。 sign(x) int x; /*形式参数说明*/ { int y;/*函数体局部变量*/
/*建议给出函数类型说明*/ int x; /*形式参数说明*/ { int y;/*函数体局部变量*/ y=x>0?1:(x==0?0:-1); return y; /*返回函数值*/ }
25
6. 函数main ■一个C语言程序至少包含一个函数,并且必须有且只能有一个名为main的函数,称之为主函数。
◆在包含多个函数的程序中,不仅可以由主函数调用其它函数,还可以由被调函数调用其它函数,但任何函数都不能调用主函数。 ■通常主函数的类型定义为void(在TC2中可省略函数main的类型说明)。
26
◆在具有多个函数的C程序中,主函数出现的位置并不重要。为阅读方便,可将主函数main放在最前面。为了避免过多的函数声明语句,习惯上将主函数放在所有函数之后。不论主函数放在什么位置,一旦启动该程序,总是从主函数开始执行,并且最终在主函数结束整个程序的执行。
27
§8.3 函数参数和函数的值 8.3.1 形式参数和实际参数 形参:在定义函数时函数名后面括号中的变量名 实参:在调用函数时函数名后面括号中的表达式 例如: c=max( a , b ) (主调函数) int max (int x ,int y ) (被调函数) {int z ; z=x>y ? x : y ; return (z); } a , b 为实际参数 x , y 为形式参数
28
◆C语言中,采用函数之间的参数传递方式或用全局变量共享数据方式,使一个函数能对不同的数据进行相同功能的处理。
形式参数 (简称形参) 定义函数使用的参数 实际参数 (简称实参) 调用函数使用的参数 ◆C语言中,采用函数之间的参数传递方式或用全局变量共享数据方式,使一个函数能对不同的数据进行相同功能的处理。 ■函数的参数采用单向值传递方式(或称复制方式)。 ◆单向值传递方式是指在函数调用时,将实参之值传递给对应的形式参数,使形参具有与实参相同的值。 ■当实际参数是变量的地址值、指针常量或指针变量时,实际参数传递给形式参数的是地址值,也同样是单向值传递方式。
29
调用函数时的数据传递 main( ) { int a,b,c; scanf(“%d,%d”,&a,&b);
c=max(a,b); printf(“max is %d”,c); } max (int x, int y) { int z; z=x>y? x:y ; return(z); } 运行结果: 7,8 max is 8
30
有关实参与形参的几点说明: (1)在函数定义中指定的形参,未调用时,它们不占用存储单元。只有调用该函数时,形参才被分配空间,函数调用结束后,形参所占的存储单元被释放。 (2)实参为表达式。函数调用时,先计算表达式的值,然后将值传递给形参。常量、变量、函数值都可看成是表达式的特殊形式。 (3)定义函数时,形参的排列没有次序要求,但对形参列表中每个参数要进行说明。调用函数时,实参类型、个数及排列次序应与形参一一对应。若类型不一致,必须在参数前加上强制转换符,否则会发生“类型不匹配”的错误。 (4)实参与形参的数据传递为单向传递,只可由实参向形参传递,不能由形参传回实参。实参与形参处在不同的函数中,作用的区域不同,即使实参与形参同名,也是不同的变量。
31
2 3 10 15 a b x y 3 2 a x b y (5)实参同形参的数值传递是“值传递” ,
由实参传给形参,不能反向,即单向传递. 主调函数 2 3 10 15 a b x y 3 2 a x b y 被调函数 实参与形参数据传递 函数调用时,系统对参数的处理步骤为: (1)计算各实参的值,将实参值压入形参栈中,然后执行函数体。 (2)当函数执行完返回时,形参从栈顶弹出(取走)。
32
例:求方程ax2+bx+c=0(a0)的实数根。
分析: (1)一元二次方程的实根的条件是: 。 (2)定义函数dict来判断数方程是否有实根,有实根则返回函数值1,否则返回函数值0;然后在主函数中求方程的实根。 #include <stdio.h> #include <math.h> void main() { float a,b,c,x1,x2,d,dt; int dict(float,float,float); /*声明函数dict及形式参数类型*/ printf("Input a,b,c:"); scanf("%f,%f,%f",&a,&b,&c);/*输入方程*/ d=dict(a,b,c); /*调用函数dict,传递实参a,b,c*/ dt=b*b-4*a*c; /* ,可以用一个函数实现*/
33
if(d) {x1=(-b+sqrt(dt))/(2*a); x2=(-b-sqrt(dt))/(2*a); printf("实根x1=%f,x2=%f\n",x1,x2);} else printf("无实数根!\n"); } int dict(a,b,c) /*定义函数dict及形参说明*/ float a,b,c; {float d; d=b*b-4*a*c; /*可以用一个函数实现*/ if(d>=0) return(1); else return(0);
34
通过函数调用,将实参的值传递给形参. 函数main 函数dict a b c 实参与形参数据传递
35
8.3.2 函数的返回值 1)通过return 语句来实现的 实现形式: return (z) == return z return 后面可以是表达式,如: max(x, y) { return (x>y :x:y);} (2)函数值的类型: 在定义函数时指定函数值的类型.不加说明时,自动按整型处理. int max(x ,y) char letter(c1,c2) min(x ,y)
36
例如 (3) 函数值的类型同return语句中的表达式 不一致时,以函数类型为准.对数值型数据, 可以自动进行类型转换
{int a,b,c; a=printstar(); b=print_message(); c=printstar(); printf ("a=%d,b=%d,c=%d\n",a,b,c); } a,b,c表示输出字符的个数。
37
◆当函数类型为int型并且return语句的表达式缺省或return语句缺省时,返回不确定的值(在TC2中返回值为0)
■当函数类型为float型并且return语句缺省时,调用出错。
38
main() { int a; a=printstar(); printf (“%d\n”,a); } void printstar()
在使用此语句后,就不能调用 eg: a=printstar ( ) ; () 因为printstar ( ) 语句无返回值. main() { int a; a=printstar(); printf (“%d\n”,a); } void printstar() {printf(“********\n”); }
39
§8.4 函数的调用 {int i=2,p; {int c; if(a>b) c=1;
8.4.1 函数调用的一般形式 函数名 (实参表列) ; 实参与形参按顺序对应,一一传递数据.在C语言中按自右向左的顺序求值. 例 main() int f(int a,int b) {int i=2,p; {int c; if(a>b) c=1; p=f(i,++i); else if (a= =b c=0;) printf("%d",p);} else c=-1; return c;} 如: int i=1; printf(“%d,%d”,i,i++); 不等同于 int i=1; printf(“%d,%d”,i,++i); 结果: 为避免不确 定的情况: j=i; k=++i; p=f(j,k);
40
◆实际参数也称为实在参数,简称为实参,实际参数之间以“,”分隔。
■函数调用时,实参与形参应保持个数、次序及类型的一致性,以确保实参与形参之间数据的正确传递。 ◆实际参数一般为表达式,可以是常量、变量(调用时必须有确定的值或确定的地址)。 ■形式参数必须为变量。 注意 当实际参数的个数、次序、类型与对应形式参数的个数、次序、类型不一致时,系统并不提示错误,后果却难以预测。
41
8.4.2 函数调用的方式 在函数调用后加“;”,构成一个语句。 函数语句调用 调用函数的目的可能是执行一个动作或完成特定的功能。
大多数函数的调用形式。 被调用函数执行的结果为调用函数提供一个值,除非一个函数的类型说明为void型。 函数表达式调用 调用函数通过表达式接收值。 函数参数调用 被调函数作为某个函数的一个参数。
42
例: 函数语句调用与函数表达式调用。 #include <stdio.h> void main() {printf("\nk1=%d",printf("\n%k2"));} 程序运行结果为: k2 k1=3
43
/*主函数中采用函数参数调用形式调用函数max2*/ { int x,y,z,m; scanf("%d,%d,%d",&x,&y,&z);
int max2(int a,int b) /*求两个数中较大者*/ { int y; y=(a>b)?a:b; return y;} void main( ) /*主函数中采用函数参数调用形式调用函数max2*/ { int x,y,z,m; scanf("%d,%d,%d",&x,&y,&z); printf("max=%d\n",max2(max2(x,y),z)); / *内层函数max2的值作为外层函数max2的实参*/ / * 整个函数max2的值又作函数printf的的实参*/ }
44
8.4.3 对被调函数的声明和函数原型 float max( int a,int b) float max( int ,int )
(1)函数必须是已经存在的函数. (2)使用库函数时,用#include命令,包含相应的头文件. #include <stdio.h> #include <math.h> (3)使用用户自定义函数,应在调用之前加以说明. 类型名 函数名(类型1 形参1,类型2 形参2,…,类型n 形参n); float max( int a,int b) 类型名 函数名(类型1,类型2,…,类型n); float max( int ,int )
45
float add(float , float);
例8.5: main( ) {float add(float x, float y); /* 被调函数类型说明 */ float a ,b ,c ; scanf(“%f,%f”,&a,&b); c=add(a ,b); printf(“sum=%f”,c) ;} float add (float x,float y) { float z; z=x+y ; return (z); } 对函数的说明也可以: float add(float , float); 3.5,6.5 sum=
46
◆在文件的开头,函数的外部已说明函数类型时 ,函数声明可以省略。
◆函数声明是以语句形式出现的,因此其后有语句结束标记“;”。 ■若函数定义放在主调函数之前,遵循先定义后调用原则,函数声明可以省略。 ◆在文件的开头,函数的外部已说明函数类型时 ,函数声明可以省略。
47
例如: float add (float x, float y) {float z; z=x+y ; return (z); } main( ) { float a ,b ,c ; scanf(“%f,%f”,&a,&b); c=add(a ,b); printf(“sum=%f”,c) ; } 3.5,6.5 sum=
48
char letter(char,char );
float f(float,float); int i( float,float); main( ) { f(3.4,5.6); i(5.1,34.5); letter(‘a’,’b’); } float f(c1,c2) {….} int i(j,k) {….} char letter(c1,c2) {……}
49
§8.5 函数的嵌套调用 所谓函数的嵌套调用是指一个函数调用另一函数的过程中又出现对其它的函数调用。 C语句不能嵌套定义函数,但可以嵌套调用函数。 这种嵌套调用的层次原则上不限制。 函数 函数 函数3 {…… ① ② {…….③ ④ {…... ⑤ 调用函数 调用函数 } …… ⑨ ⑧ …….⑦ ⑥ } } 调用的例子:
50
§8.6函数的递归调用 递归调用:在调用一个函数的过程中又出现直接或间接的调用该函数本身的过程. Eg: 直接调用:
int f(int x) {int y,z; … z=f(y); return (2+z); } 间接调用: int f1(x) int f2(t) int x; int t; {int y,z; … {int a,c; …. z=f2(y); …. } c=f1(a); …. }
51
函数调用其它函数,而其它函数又调用该函数自身
直接递归 函数体内直接调用函数自身 间接递归 函数调用其它函数,而其它函数又调用该函数自身 (1)直接递归 (2)间接递归 void fun( ) void fun1( ) void fun2( ) { ... { ... { ... fun( ); fun2( ); fun2( ); ... ... ... } } }
52
求第5个人的年龄 age(5)=age(4)+2 age(4)=age(3)+2 age(3)=age(2)+2
age(n)=10 (n=1) age(n)= age(n-1)+2 (n>2)
53
main n=5 n=4 n=3 n=2 n=1 Age(5)=age(4)+2 Age(5)=18 Age(4)=age(3)+2
C=Age(4) +2 C=Age(3) +2 C=Age(2) +2 C=Age(1) +2 C=10
54
age (n) /* 求年龄的递归函数*/ int n; {int c; if(n= =1) c=10; /*使程序结束,退出循环*/ else c=age(n-1)+2 ; return (c); } main( ) { printf(“%d”,age (5));} 运行结果: 18
55
无论是直接还是间接递归,两者都是无终止的调用自身。要避免这种情况的发生,使用递归解决的问题应满足两个基本条件:
(1)问题的转化。有些问题不能直接求解或难以求解,但它可以转化为一个新问题,这个新问题相对较原问题简单或更接近解决方法。这个新问题的解决与原问题一样,可以转化为下一个新问题,…。 (2)转化的终止条件。原问题到新问题的转化是有条件的、次数是有限的,不能无限次数地转化下去。这个终止条件也称为边界条件,相当于递推关系中的初始条件。
56
例:编程求mn(m,n都为整数且n>0)。
分析: (1)mn可以通过累乘方法实现。 (2)mn又可写成递归公式: 即mn问题可以理解为:直接计算mn没有相应的运算符,但可以将mn转化为m×mn-1的形式,即将mn转化为一个乘法问题,但乘法中的mn-1与mn是同一类问题,需要继续进行转化,直到问题趋于边界条件m0=1。
57
#include <stdio.h>
void main( ) {int m,n; long power(int,int); /*函数声明*/ scanf("%d,%d",&m,&n); printf("power(%d,%d)=%ld\n",m,n,power(m,n)); } long power(int m,int n) /*求mn的递归函数的定义*/ {long p; if (n==0)p=1; else p= m*power(m,n-1); /*注意:不可写成power(m,n)=m*power(m,n-1)*/ return p;
58
23 2*22 2*21 2*20 power(2,3) 返回3 2* power(2,2)……① 返回2 2* power(2,1) ……② 返回1 2* power(2,0) ……③ 23的递归执行过程
59
例:编程求裴波那契(Fibonacci)数列(1,1,2,3,5,8,…,)第10项的值。
分析: (1)对于裴波那契数列而言,有两个初始条件,分别为: fib(1)=1,fib(2)=1; (2)其递推关系为从第三项开始,每项是前两项之和。因此,可以写出它的递归公式:
60
#include <stdio.h>
void main() {int n; long m; long fib(int); /*函数声明*/ scanf("%d",&n); /*求第n项*/ m=fib(n); printf("fib(%d)=%ld\n",n,m); } long fib(int n) /*定义函数fib及形参说明*/ { if (n==1) return(1); else if(n==2) return (1); /*递归终止条件*/ else return fib(n-1)+fib(n-2);
61
例 汉诺(Hanoi)塔游戏。 汉诺塔(Tower of Hanoi)游戏据说来源于布拉玛神庙。游戏的装置如图6-6所示(图上以3个金片例),底座上有三根金的针,第一根针上放着从大到小64个金片。游戏的目标是把所有金片从第一根针移到第三根针上,第二根针作为中间过渡。每次只能移动一个金片,并且大的金片不能压在小的金片上面。该游戏的结束就标志着“世界末日”的到来。 A B C 三个金片的Hanoi游戏的装置
62
◆游戏中金片移动是一个很繁琐的过程。通过计算,对于64个金片至少需要移动 264 – 1 = 1.8×1019 次 。
分析: ◆游戏中金片移动是一个很繁琐的过程。通过计算,对于64个金片至少需要移动 264 – 1 = 1.8×1019 次 。 ■不妨用A表示被移动金片所在的针(源),C表示目的针,B表示过渡针。对于把n(n>1)个金片从第一根针A上移到第三根针C的问题可以分解成如下步骤: A B C 三个金片的Hanoi游戏的装置 (1)将n-1个金片从A经过C移动到B。 (2)将第n个金片移动到C。 (3)再将n-1个金片从B经过A移动到C。 这样就把移动n个金片的问题转化为移动n-1个金片的问题,即移动n个金片的问题可用移动n-1个金片的问题递归描述,以此类推,可转化为移动一个金片的问题。显然,一个金片就可以直接移动。
63
i=0; /*i为移动的次数,说明为全局变量*/
void hanoi(int n, int A,int B,int C) {if(n==0) return; /* 0个金片不处理 */ if(n==1) step(A,C); /* n=1时, 直接将金片从A移动到C*/ else {hanoi(n-1,A,C,B); /* 先将n-1个金片从A经过C 移动到B */ step(A,C); /* 将第n个金片从A移动到C */ hanoi(n-1,B,A,C);} /* 再将n-1个金片从B经过A移动到C*/ } void step(int take,int put) {i++; /*第i次移动*/ printf("[%d] %d->%d\n",i,take,put);} #include <stdio.h> void main() {int n; printf("Input n:");scanf("%d",&n); hanoi(n,1,2,3);/*n个金片从第一根针经过第二根针移动到第三根针*/
64
程序运行结果: Input n:3 [1] 1->3 3 [2] 1->2 2 [3] 3->2 3 [4] 1->3 1 [5] 2->1 3 [6] 2->3 2 [7] 1->3 3 说明: 结果中第1列表示第i次移动,第2列表示从第m根针移动到第n根针,第3列表示第i个金片(i=1,2,3,最小的金片编号为3)。 ☆递归在算法上简单而自然,递归过程结构清晰,源程序代码紧凑,因而递归调用在完成诸如阶乘运算、级数运算以及对递归的数据结构进行处理等方面特别有效。
65
2. 递归与递推 主要采用循环技术; 逐步执行; 当前值的求得总建立在前面求解的基础上; 递推方法 程序描述繁杂,可读性差;
占用存储空间少,执行速度快。 描述与原始问题(递归公式)比较接近; 书写简洁、易读易写; 易于分析算法的复杂性和证明算法的正确性; 递归方法 在问题转化时,需要花时间和存储空间将有关的“现场信息”保存起来;当达到中止条件时,系统又需要花时间将有关的“现场信息”恢复以便处理未曾处理完的函数调用 。
66
§8.7 数组作为函数参数 1. 数组元素做函数实参 例如 2. 数组名作函数参数 它同变量作参数一样,是“值传递”方式.
数组名可以用作函数的形参和实参。 main() f(int arr[ ],int n) {int array[10]; { … … f(array,10); } … }
67
例如 例如 Score array array为实参数组名,arr为形参数组名。当用数组名作参数时,要求实参用数组名,形参用数组。
(1) 在实参和形参中分别定义数组。 (2) 实参和形参的类型必须一致。 (3)实参和形参的大小可以不一致。 只是将实参数组的首地址传给形参数组 (4)形参的大小可以不指定 例如 例如 Score array
68
5)形参和实参是地址传递,它们共享存储单元。
a[0] a [1] a[2] a[3] a[4] 起始地址1000 b[0] b[1] b[2] 例子
69
形参数组中各元素的值发生变化,实参数组元素的值随之变化。
数组元素作实参时的情况: 如定义一个函数,其原型为: void Swap(int x,int y); 今有以下的函数调用: Swap(a[1],a[2]); 用数组元素a[1],a[2]作实参的情况与用变量作实参时一样,是“值传递”方式,将a[1]和a[2]的值单向传递给x,y。当x和y的值改变时,a[1]和a[2]的值并不改变。
70
数组名作函数参数的情况: 实参数组名代表该数组首地址。形参接收从实参传递过来的数组首地址。因此,形参是一个指针变量(只有指针变量才能存放地址)。实际上,C编译将形参数组都作为指针变量来处理。 例如:f(int arr[ ], int n) 在编译时将arr按指针变量处理,相当于将函数f的首部写成: f(int *arr, int n) 在调用该函数时,系统会建立一个指针变量arr,用来存放从主调函数传递过来的实参数组首地址。
71
此时在f函数中用sizeof运算符求arr所占的字节数(即sizeof(arr)的值),结果为2。
当arr接收了实参数组的首地址后,arr就指向实参数组的开头,也就是指向array[0]。因此,*arr 就是array[0]的值,*(arr+i) 是array[i] 的值。 常用这种方法通过调用一个函数来改变实参数组的值。
72
我们把用变量名作为函数参数和用数组名作为函数参数作一比较。
实参类型 变量名 数组名 要求形参的类型 数组名或指针变量 传递的信息 变量的值 数组的起始地址 函数调用能否改变实参的值 不能 能 需要说明:函数调用时参数都是采用“值传递”方式传递,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组起始地址,因此传递的值是数组首地址,所以要求形参为指针变量。
73
在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢
在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?这是因为在C语言中用下标法和指针法都可以访问一个数组(如果有一个数组a,则a[i]和*(a+i)无条件等价),用下标法表示比较直观,便于理解。因此许多人愿意用数组名作形参,以便与实参数组对应。从应用的角度看,用户可以认为有一个形参数组,它从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。当然在主调函数中可以利用这些已改变的值。对C语言比较熟练的专业人员往往喜欢用指针变量作形参。
74
应该注意: 实参数组代表一个固定的地址(指针型常量),而形参数组作为指针变量,并不是一个固定的地址值(指针常量)。在函数调用开始时,它的值等于实参数组起始地址,但在函数执行期间,它可以再被赋值。如:
f(arr[ ], int n) {printf("%D\n", *arr);/*输出array[0]的值,*/ arr=arr+3; printf("%D\n", *arr);/*输出array[3]的值,*/ }
75
例:将数组a中n个整数按相反顺序存放。 算法:将a[0]与a[n-1]对换,再将a[1]与a[n-2]对换 ,……,直到将a[(n-1)/2]与a[n-int((n-1)/2)-1]对换。
76
void inv(int x[ ],int n)/*形参x是数组名*/
{ int temp,i,j,m=(n-1)/2; for(i=0;i<=m;i++) {j=n-1-i; temp=x[i];x[i]=x[j];x[j]=temp;} }
77
main() {int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("The oriGinal array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); inv(a,10); printf("The array haS Been inverteD:\n"); printf("%d,",a[i]); }
78
主函数中数组名为a,赋以各元素初值。函数inv中的形参数组名为x。在inv函数中不必具体定义数组元素的个数,元素个数由实参传给形参n(今实参值为10)。这样做可以增加函数的灵活性。即不必要求函数inv中的形参数组x和main函数中的实参数组a长度相同。如果在main函数中有函数调用语句:inv(a,10),表示要求对a数组的前10个元素实行题目要求的颠倒排列。如果改为:inv(a,5),则表示要求将a数组的前5个元素实行颠倒排列,此时,函数inv只处理5个数组元素。函数inv中的m是i值的上限,当i≤m时,循环继续执行;当i>m时,则结束循环过程。 例如,若n=10,则m=4,最后一次a[i]与a[j]的交换是a[4]与a[5]交换。
79
对这个程序可以作一些改动。将函数inv中的形参x改成指针变量。实参为数组名a,即数组a的首地址,将它传给形参指针变量x,这时x就指向a[0]。x+m是a[m]元素的地址。设i和j以及p都是指针变量,用它们指向有关元素。i的初值为x,j的初值为x+n-1。使*i与*j交换就是使a[i]与a[j]交换。
80
printf("The oriGinal array:\n");
for(i=0;i<10;i++) printf("%D,",a[i]); printf("\n"); inv(a,10); printf("The array haS Been inverteD:\n"); } void inv(int*x,int n) /*形参x为指针变量*/ { intp,temp,*i,*j,m=(n-1)/2; i=x;j=x+n-1;p=x+m; for(;i<=p;i++,j--) {temp=*i;*i=*j;*j=temp;} } main() {int i,a[10]={3,7,9,111, 0,6,7,5,4,2}; printf("The oriGinal array:\n");
81
3. 多维数组作函数参数 形参的定义:可以省略第一维的大小说明。 int array[3][10]; int array[][10]; 但int array[][]; int array[3][]; 非法 实参数组可以大于形参数组,这时只取实参数组的一部分 实参数组: int scroe[5][10] ; 形参数组 :int array[3][10] ; 例子
82
4.指针变量作为函数参数 数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址, 形参得到该地址后也指向同一数组。 这就好象同一件物品有两个彼此不同的名称一样。 同样,指针变量的值也是地址, 数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。
83
float aver(float *pa) { int i; float av,s=0; for(i=0;i<5;i++) s=s+*pa++; av=s/5; return av; } float aver(float *pa); main(){ float sco[5],av,*sp; int i; sp=sco; printf("\ninput 5 scores:\n"); for(i=0;i<5;i++) scanf("%f",&sco[i]); av=aver(sp); printf("average score is %5.2f",av); }
84
指针数组也可以用作函数参数。 在下例主函数中,定义了一个指针数组name,并对name 作了初始化赋值。其每个元素都指向一个字符串。然后又以name 作为实参调用指针型函数day name,在调用时把数组名 name 赋予形参变量name,输入的整数i作为第二个实参赋予形参n。在day name函数中定义了两个指针变量pp1和pp2,pp1被赋予name[0]的值(即*name),pp2被赋予name[n]的值即*(name+ n)。由条件表达式决定返回pp1或pp2指针给主函数中的指针变量ps。最后输出i和ps的值。
85
char *dayname(char *name[],int n); printf("input Day No:\n");
【例】main() { static char *name[]={ "Illegal day", "Monday","Tuesday","Wednesday", "Thursday", "Friday","Saturday","Sunday"}; char *ps; int i; char *dayname(char *name[],int n); printf("input Day No:\n"); scanf("%d",&i); if(i<0) exit(1); ps=day name(name,i); printf("Day No:%2d-->%s\n",i,ps); } char *day name(char *name[],int n) { char *pp1,*pp2; pp1=*name; pp2=*(name+n); return((n<1||n>7)? pp1:pp2);
86
【例】要求输入5个国名并按字母顺序排列后输出。
在前例中采用普通排序方法, 逐个比较之后交换字符串的位置。交换字符串的物理位置是通过字符串复制函数完成的。 反复的交换将使程序执行的速度很慢,同时由于各字符串(国名) 的长度不同,又增加了存储管理的负担。 用指针数组能很好地解决这些问题。把所有的字符串存放在一个数组中, 把这些字符数组的首地址放在一个指针数组中,当需要交换两个字符串时, 只须交换指针数组相应两元素的内容(地址)即可,而不必交换字符串本身。
87
#include"string.h" main(){ void sort(char *name[],int n); void print(char *name[],int n); static char *name[]={ "CHINA","AMERICA","AUSTRALIA", "FRANCE", "GERMAN"}; int n=5; sort(name,n); print(name,n);} void print(char *name[],int n) { int i; for (i=0;i<n;i++) printf("%s\n",name[i]);} void sort(char *name[],int n) {char *pt; int i,j,k; for(i=0;i<n-1;i++){ k=i; for(j=i+1;j<n;j++) if(strcmp(name[k], name[j])>0) k=j; if(k!=i){ pt=name[i]; name[i]=name[k]; name[k]=pt; } }}
88
程序中定义了两个函数,一个名为sort完成排序, 其形参为指针数组name,即为待排序的各字符串数组的指针。形参n为字符串的个数。另一个函数名为print,用于排序后字符串的输出,其形参与sort的形参相同。主函数main中,定义了指针数组name 并作了初始化赋值。然后分别调用sort函数和print函数完成排序和输出。值得说明的是在sort函数中,对两个字符串比较,采用了strcmp 函数,strcmp函数允许参与比较的串以指针方式出现。name[k]和name[ j]均为指针,因此是合法的。字符串比较后需要交换时, 只交换指针数组元素的值,而不交换具体的字符串, 这样将大大减少时间的开销,提高了运行效率。
89
归纳起来,如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的表示形式有以下4种情况:
(1) 形参和实参都用数组名,如: main() f(intx[ ],int n) {int a[10]; { … … f(a,10); } … } 可以认为有一形参数组,与实参数组共用一段内存单元,这种形式比较好理解。
91
(2) 实参用数组名,形参用指针变量。如: main() f(int *x,int n) {int a[10]; { … … f(a,10); } … } 实参a为数组名,形参x为指向整型变量的指针变量,函数开始执行时,x指向a[0],即x=&a[0] 。通过x值的改变,可以指向a数组的任一元素。
93
(3) 实参形参都用指针变量。例如: main() f(int *x,int n) {int a[10], *p; { p=a; … … } f(p,10); … } 实参p和形参x都是指针变量。先使实参指针变量p指向数组a,p的值是&a[0]。然后将p的值传给形参指针变量x,x的初始值也是&a[0] 。通过x值的改变可以使x指向数组a的任一元素。
95
(4) 实参为指针变量,形参为数组名。如: main() f(int x[ ],int n) {int a[10],*p; { p=a; … … } f(p,10); … } 实参p为指针变量,指向a[0],即p=a或p=&a[0]。形参为数组名x,实际上将x作为指针变量处理。今将a[0]的地址传给形参x,使指针变量x指向a[0](x取得数组a的首地址,x数组和a数组共用内存单元) 。x[i]值变化,就是a[i]值变化。主函数可以使用变化了的数组元素值.
97
例: void inv(int *x,int n) { intp,m,temp,*i,*j; m=(n-1)/2; i=x;j=x+n-1;p=x+m; for(;i<=p;i++,j--) {temp=*i;*i=*j;*j=temp;} return; }
98
main() {int i,arr[10],*p=arr; printf("The original array:\n"); for(i=0;i<10;i++,p++) scanf("%d",p); printf("\n"); p=arr; inv(p,10);/*实参为指针变量*/ printf("The array has been inverted:\n"); for(p=arr;p<arr+10;p++) printf("%d",*p); printf("\n"); }
99
注意,上面的main函数中的指针变量p是有确定值的。如果在main函数中不设数组,只设指针变量,就会出错,假如把程序修改如下:
{int i,arr; printf("The oriGinal array:\n"); for(i=0;i<10;i++) scanf("%d",arr+i); printf("\n"); inv(arr,10);/*实参为指针变量,但未被赋值*/ printf("The array haS Been inverteD:\n“ for(i=0;i<10;i++) printf("%d",*(arr+i)); printf("\n");}
100
编译时出错,原因是指针变量arr没有确定值,谈不上指向哪个变量。下面的使用是不正确的:
main()f(x[ ],int n) {int *p; { f(p,n); … } } 应注意,如果用指针变量作实参,必须先使指针变量有确定值,指向一个已定义的数组。
101
以上四种方法,实质上都是地址的传递。其中(1)(4)两种只是形式上不同,实际上都是使用指针变量。
102
例:用选择法对10个整数排序。 main() {intp,i,a[10]; p=a; for(i=0;i<10;i++) scanf("%D",p++); Sort(p,10); for(p=a,i=0;i<10;i++) {printf("%d",*p); p++;} }
103
sort(int x[ ],int n) {int i,j,k,t; for(i=0;i<n-1;i++) {k=i; for(j=i+1;j<n;j++) if(x[j]>x[k]) k=j; if(k!=i) {t=x[i];x[i]=x[k];x[k]=t;} } }
104
为了便于理解,函数Sort中用数组名作为形参,用下标法引用形参数组元素,这样的程序很容易看懂。当然也可以改用指针变量,这时Sort函数的首部可以改为
sort(int *x,int n) 其他不改,程序运行结果不变。可以看到,即使在函数Sort中将x定义为指针变量,在函数中仍可用x[i]、x[k]这样的形式表示数组元素,它就是x+i和x+k所指的数组元素。它等价于
105
if(*(x+j)>*(x+k)) k=j; if(k!=i) {t=*(x+i);*(x+i)=*(x+k);*(x+k)=t;} }
sort(int *x,int n) { int i,j,k,t; for(i=0;i<n-1;i++) {k=i; for(j=i+1;j<n;j++) if(*(x+j)>*(x+k)) k=j; if(k!=i) {t=*(x+i);*(x+i)=*(x+k);*(x+k)=t;} }
106
5 .main函数的参数 前面介绍的main函数都是不带参数的。因此main 后的括号都是空括号。实际上,main函数可以带参数,这个参数可以认为是 main函数的形式参数。C语言规定main函数的参数只能有两个, 习惯上这两个参数写为argc和argv。 因此,main函数的函数头可写为: main (argc,argv)
107
C语言还规定argc(第一个形参)必须是整型变量,argv( 第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头应写为:
main (argc,argv) int argv; char *argv[]; 或写成: main (int argc, char *argv[])
108
C:\>可执行文件名 参数 参数……;
由于main函数不能被其它函数调用, 因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢? 实际上,main函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。 DOS提示符下命令行的一般形式为: C:\>可执行文件名 参数 参数……;
109
但是应该特别注意的是,main 的两个形参和命令行中的参数在位置上不是一一对应的。因为,main的形参只有二个,而命令行中的参数个数原则上未加限制。
argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。
110
例如有命令行为: C:\>E BASIC dbase FORTRAN 由于文件名E6 24本身也算一个参数,所以共有4个参数,因此argc取得的值为4。argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址。 指针数组的长度即为参数个数。数组元素初值由系统自动赋予。
111
【例】main(int argc,char *argv) { while(argc-->1)
printf("%s\n",*++argv); } 本例是显示命令行中输入的参数 如果上例的可执行文件名为e24.exe,存放在A驱动器的盘内。因此输入的命令行为: C:\>a:e24 BASIC dBASE FORTRAN 则运行结果为: BASIC dBASE FORTRAN
112
该行共有4个参数,执行main时,argc的初值即为4。argv的4个元素分为4个字符串的首地址。执行while语句,每循环一次 argv值减1,当argv等于1时停止循环,共循环三次, 因此共可输出三个参数。在printf函数中,由于打印项*++argv是先加1再打印, 故第一次打印的是argv[1]所指的字符串BASIC。第二、 三次循环分别打印后二个字符串。而参数e24是文件名,不必输出。
113
下例的命令行中有两个参数,第二个参数20即为输入的n值。在程序中
下例的命令行中有两个参数,第二个参数20即为输入的n值。在程序中*++argv的值为字符串“20”,然后用函数"atoi"把它换为整型作为while语句中的循环控制变量,输出20个偶数。 【例】 #include"stdlib.h" main(int argc,char*argv[]){ int a=0,n; n=atoi(*++argv); while(n--) printf("%d ",a++*2); } 本程序是从0开始输出n个偶数。
114
10.5 函数的指针和指向函数的指针变量 10.5.1 用函数指针变量调用函数
10.5 函数的指针和指向函数的指针变量 用函数指针变量调用函数 可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。 如有函数:
115
max(int x,int y) {int z; if(x>y)z=x; else z=y; return(z); } 每一个函数都占用一段内存单元,它们有一个起始地址。因此,可以用一个指针变量指向一个函数,通过指针变量来访问它指向的函数。
116
main() {int max(int,int); int(*p)(); int a,b,C; p=max; scanf("%d,%d",&a,&B); C=(*p)(a,b); printf("max=%d",a,b,c); } 其中int(*p)()定义p是一个指向函数的指针变量,此函数带回整型的返回值。
117
*p两侧的括弧不可省略,表示p先与*结合,是指针变量,然后再与后面的()结合,表示此指针变量指向函数,这个函数值(即函数返回的值)是整型的。如果写成“int p()”,则由于()优先级高于*,它就成了声明一个函数了(这个函数的返回值是指向整型变量的指针)。 赋值语句“p=max;”的作用是将函数max的入口地址赋给指针变量p。函数名代表该函数的入口地址。这时,p就是指向函数max的指针变量,也就是p和max都指向函数的开头。调用*p就是调用函数max。请注意p是指向函数的指针变量,它只能指向函数的入口处而不可能指向函数中间的某一条指令处,因此不能用*(p+1)来表示函数的下一条指令。
119
在main函数中有一个赋值语句 c=(*p)(a,b); 它是用指针形式实现函数的调用,和“C=max(a,b);”等价。 说明: (1) 指向函数的指针变量的一般定义形式为数据类型 (*指针变量名)();这里的“数据类型”是指函数返回值的类型。 (2) 函数的调用可以通过函数名调用,也可以通过函数指针调用(即用指向函数的指针变量调用)。
120
(3) (*p)()表示定义一个指向函数的指针变量,它不固定指向一个函数的,而只是表示专门用来存放函数的入口地址的一个变量。把哪一个函数的地址赋给它,它就指向哪一个函数。在程序中,可以先后指向不同的函数。 (4) 在给函数指针变量赋值时,只需给出函数名而不必给出参数,如:p=max;因为是将函数入口地址赋给p,而不牵涉到实参与形参的结合问题。不能写成“p=max(a,B);”形式。 (5) 用函数指针变量调用函数时,只需将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括弧中根据需要写上实参。
121
(6) 对指向函数的指针变量,像p+n、p++、p--等运算是无意义的。
122
函数指针变量常用的用途之一是把指针作为参数传递到其他函数,也就是将函数名传给形参。
用指向函数的指针作函数参数 函数指针变量常用的用途之一是把指针作为参数传递到其他函数,也就是将函数名传给形参。 如:有一个函数(假设函数名为Sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数Sub时,实参用两个函数名f1和f2给形参传递函数地址。这样在函数SuB中就可以调用f1和f2函数了。
123
如:定义x1、x2为函数指针变量,x1指向的函数有一个整型形参,x2指向的函数有两个整型形参
实参函数名 f f2 ↓ ↓ Sub(int (*x1)(int ),int (*x2)(int,int )) {int a,B,i=1,j=2; a=(*x1)(i); /*调用f1函数*/ B=(*x2)(i,j); /*调用f2函数*/ … }
124
其中i和j是函数f1和f2所要求的参数。函数Sub的形参x1、x2(指针变量)在函数Sub未被调用时并不占内存单元,也不指向任何函数。在Sub被调用时,把实参函数f1和f2的入口地址传给形参指针变量x1和x2,使x1和x2指向函数f1和f2。这时,在函数Sub中,用*x1和*x2就可以调用函数f1和f2。 (*x1)(i)就相当于f1(i),(*x2)(i,j)就相当于f2(i,j)。
125
如果只是用到f1和f2,完全可以直接在Sub函数中直接调用f1和f2,而不必设指针变量x1、x2。但是,如果在每次调用Sub函数时,要调用的函数不是固定的,这次调用f1和f2,而下次要调用f3和f4,第三次要调用的是f5和f6。这时,用指针变量就比较方便了。只要在每次调用Sub函数时给出不同的函数名作为实参即可,Sub函数不必作任何修改。这种方法是符合结构化程序设计方法原则的,是程序设计中常使用的。 下面通过一个简单的例子来说明这种方法的应用。
126
例:设一个函数process,在调用它的时候,每次实现不同的功能。输入a和B两个数,第一次调用process时找出a和b中大者,第二次找出其中小者,第三次求a与b之和。
程序如下: main() {int max(int,int); /* 函数声明 */ int min(int,int); /* 函数声明 */ int add(int,int); /* 函数声明 */ int a,b; printf("enter a and b:");
127
else Z=y; scanf("%d,%d",&a,&b); printf("max="); process(a,b,max);
printf("min="); process(a,b,min); printf("sum="); process(a,b,add); } max(int x,int y) {int Z; if(x>y) Z=x; else Z=y; return(Z); } min(int x,int y) {int Z; if(x<y)Z=x; else Z=y; return(Z); add(int x,int y) Z=x+y;
128
process(int x,int y,int (*fun)(int,int))
{int reSult; result=(*fun)(x,y); printf("%D\n",result); }
129
从本例可以清楚地看到,不论调用max、min或add,函数process一点都没有改动,只是在调用process函数时将实参改变而已。这就增加了函数使用的灵活性。可以编一个通用的函数来实现各种专用的功能。需要注意的是,对作为实参的函数,应在主调函数中用函数原型作函数声明。例如,main函数中第2行到第4行的函数声明是不可少的。 过去曾说过:对本文件中的整型函数可以不加说明就可以调用。但函数调用时在函数名后面跟括弧和实参(如max(a,b)),编译时能根据此形式判断它为函数。而现在是只用函数名(如max)作实参,后面没有括弧和参数,编译系统无法判断它是变量名还是函数名。故应事先作声明,这样编译时将它们按函数名处理(把函数入口地址作实参值),不致出错。
130
10.6 返回指针值的函数 一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。只是带回的值的类型是指针类型而已。
10.6 返回指针值的函数 一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。只是带回的值的类型是指针类型而已。 一般定义形式: 类型名 *函数名(参数表); 例如: int *a(int x,int y); a是函数名,调用它以后能得到一个指向整型数据的指针(地址)。x、y是函数a的形参,为整型。注意在*a两侧没有括弧。
131
例:有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
程序如下: main() {float score[ ][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}}; floatsearch(float (*pointer)[4],int n); floatp; int i,m; printf("enter the number of Student:"); scanf("%d",&m);
132
printf("The Scores of No.%d are:\n",m);
p=Search(Score,m); for(i=0;i<4;i++) printf("%5.2f\t", (p+i)); } float *Search(float (*pointer)[4],int n) {float *pt; pt=*(pointer+n); return(pt);
133
注意:学生序号是从0号算起的。函数search被定义为指针型函数,它的形参pointer是指向包含4个元素的一维数组的指针变量。pointer+1指向score数组第1行。*(pointer+1)指向第1行第0列元素。也就是指针从行控制转化为列控制了。pt是指针变量,它指向float变量而不是指向一维数组)。main函数调用search函数,将score数组的首地址传给pointer(注意score也是指向行的指针,而不是指向列元素的指针)。m是要查找的学生序号。调用search函数后,得到一个地址(指向第m个学生第0门课程),赋给p。然后将此学生的4门课的成绩打印出来。*(p+i)表示此学生第i门课的成绩。
134
请注意,指针变量p、pt和pointer的区别。如果将search函数中的语句
pt=*(pointer+n); 改为 pt=(*pointer+n); 得到的不是第一个学生的成绩,而是二维数组中a[0][1]开始的4个元素的值。
135
§8.8 局部变量和全局变量 在讨论函数的形参时曾经提到, 形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的, 离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量, C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。 C语言中的变量,按作用域范围可分为两种, 即局部变量和全局变量。
136
变量应占用的内存空间大小 变量的数据类型 变量在存储空间分配时所限定 的边界条件 变量的数据类型 变量存储类型 共同决定 变量的性质 变量的作用域 变量的生存期
137
printf (”a=%d,b=%d ”, a, b);
例: void f1( ) { int t=2; a *= t; b /= t; } void main( ) { int a, b; printf(”Enter a,b:”); scanf(”%d,%d”, &a, &b); f1( ); /* 调用函数f1( ) */ printf (”a=%d,b=%d ”, a, b); 编译程序会提示出错: Undefined symbol ‘a’ 和 Undefined symbol ‘b’ 。 为什么?
138
例: #include <stdio.h> int a,b; /*a,b为全局变量*/ void f1( ) { int t1,t2; t1 = a * 2; t2 = b * 3; b = 100; printf (”t1=%d,t2=%d\n ”, t1, t2); } void main( ) { a=2; b=4; f1( ); printf (”a=%d,b=%d”, a, b); 程序输出结果为: t1=4,t2=12 a=2,b=100
139
例: #include <stdio.h> int a=2,b=4; /*a,b为全局变量*/ void f1( ) { int t1,t2; t1 = a * 2; t2 = b * 3; b = 100; printf (” t1=%d,t2=%d\n”, t1, t2); } void main() { int b=4; f1( ); printf (” a=%d,b=%d ”, a, b); 程序输出结果为: t1=4,t2=12 a=2, b=4 结论: 全局变量与局部变量同名时,局部变量的作用域屏蔽全局变量
140
8.8.1 局部变量 在一个函数内部定义的变量,只在本函数范围内有效的变量称为局部变量. float f1( int a) { int b,c; …..} char f2(int x, int y) {int i,j; } main() { int m,n ….} a ,b,c有效 x,y,i,j m,n
141
关于局部变量的作用域还要说明以下几点: 1. 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。 2. 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。
142
3. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。如形参和实参的变量名都为n,是完全允许的。
4. 在复合语句中也可定义变量,其作用域只在复合语句范围内。
143
例如: main() { int s,a; …… int b; s,a作用域 s=a+b; b作用域 }
144
【例】main() { int i=2,j=3,k; k=i+j; int k=8; if(i==3) printf("%d\n",k); } printf("%d\n%d\n",i,k);
145
本程序在main中定义了i,j,k三个变量,其中k未赋初值。 而在复合语句内又定义了一个变量k,并赋初值为8。应该注意这两个k不是同一个变量。在复合语句外由main定义的k起作用,而在复合语句内则由在复合语句内定义的k起作用。因此程序第4行的k为main所定义,其值应为5。第7行输出k值,该行在复合语句内,由复合语句内定义的k起作用,其初值为8,故输出值为8,第9行输出i,k值。i是在整个程序中有效的,第7行对i赋值为3,故以输出也为3。而第9行已在复合语句之外,输出的k应为main所定义的k,此k值由第4 行已获得为5,故输出也为5。
146
8.8.2 全局变量 全局变量也称为外部变量,它是在函数外部定义的变量。 它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。
147
在函数之外定义的 称为外部变量,也称为全局变量 int p=1,q=5;
float f1(a) int a; { int b,c; } char c1,c2; char f2 (int x,int y) {int i,j; } main() { int m,n;} p,q 适 用范围 c1,c2的 适用范围
148
全局变量说明 (1)全局变量的作用增加了函数间数据的渠道。可以得到多个被调函数中的值,全局变量的第一个字母一般用大写表示。
(2)不在必要时不要使用全局变量 全局变量在程序执行的过程中一直占用存储单元 使函数的通用性降低了 使用全局变量,降低程序的清晰性。难以判断程序瞬时值。 (3)如果在同一个源文件中,外部变量与内部变量同名,外部变量不起作用。 例子 例子
149
【例】输入正方体的长宽高l,w,h。求体积及三个面x*y,x*z,y*z的面积。
int s1,s2,s3; int vs( int a,int b,int c) { int v; v=a*b*c; s1=a*b; s2=b*c; s3=a*c; return v; } main() { int v,l,w,h; printf("\ninput length,width and height\n"); canf("%d%d%d",&l,&w,&h); v=vs(l,w,h); printf("v=%d,s1=%d,s2=%d,s3=%d\n", v,s1,s2,s3); }
150
本程序中定义了三个外部变量s1,s2,s3, 用来存放三个面积,其作用域为整个程序。函数vs用来求正方体体积和三个面积, 函数的返回值为体积v。由主函数完成长宽高的输入及结果输出。由于C语言规定函数返回值只有一个,当需要增加函数的返回数据时,用外部变量是一种很好的方式。本例中,如不使用外部变量,在主函数中就不可能取得v,s1,s2,s3四个值。而采用了外部变量,在函数vs中求得的s1,s2,s3值在main 中仍然有效。因此外部变量是实现函数之间数据通讯的有效手段。
151
【例】int vs(int l,int w) { extern int h; int v; v=l*w*h; return v; } main() { extern int w,h; int l=5; printf("v=%d",vs(l,w)); } int l=3,w=4,h=5; 本例中,外部变量在最后定义, 因此在函数中对要用的外部变量必须进行说明。外部变量l,w和vs函数的形参l,w同名。外部变量都作了初始赋值,mian函数中也对l作了初始化赋值。执行程序时,在printf语句中调用vs函数,实参l的值应为main中定义的l值,等于5,外部变量l在主函数内不起作用;实参w的值为外部变量w的值为4,进入vs后这两个值传送给形参l,vs函数中使用的h 为外部变量其值为5,因此v的计算结果为100。
152
4. 对于局部变量的定义和说明,可以不加区分。而对于外部变量则不然,外部变量的定义和外部变量的说明并不是一回事。外部变量定义必须在所有的函数之外,且只能定义一次。其一般形式为:[extern] 类型说明符 变量名,变量名… 其中方括号内的extern可以省去不写。 例如: int a,b; 等效于: extern int a,b;
153
而外部变量说明出现在要使用该外部变量的各个函数内, 在整个程序内,可能出现多次,外部变量说明的一般形式为:
extern 类型说明符 变量名,变量名,…; 外部变量在定义时就已分配了内存单元, 外部变量定义可作初始赋值,外部变量说明不能再赋初始值, 只是表明在函数内要使用某外部变量。
154
1. 变量按作用域:分为全局变量和局部变量 2. 区别: 全局变量(外部变量) 局部变量(内部变量) 定义位置 函数体外 函数体内 作用域 从定义处到文件结束 从定义处到本函数结束 举例 所有在函数体外定义的变量 (1) 所有在函数体内定义的变量 (2)形式参数 注意 与局部变量同名的处理 不同函数中同名局部变量互不干扰
155
8.9 变量的存储类别 8.9.1 动态存储方式与静态存储方式 全局变量: 在静态存储区 动态存储区 用户区 局部变量: 静态存储区 程序区
动态存储区: (1)形式参数; (2)未经static 定义的局部变量; (3) 函数调用保护现场和返回地址等。 用户区 程序区 静态存储区 动态存储区
156
变量的存在时间,即变量的存在性。 变量的生存期 变量的存储特性 变量存放的存储媒介,如存储器、外存储器和CPU的通用寄存器. 变量的存储类型
157
变量 (按存在时间) 数据类型 变量属性 存储特性 生存期为程序执行的整个过程,在该过程中占有固定的存储空间,也称永久存储。 静态存储变量
只生存在某一段时间内 函数的形参、函数体或分程序中定义的变量,只有当程序进入该函数或分程序时才分配存储空间,函数/分程序执行完后,变量的存储空间又被释放。 动态存储变量 数据类型 变量属性 存储特性
158
C语言变量的存储特性可分四类: 自动型(auto)、静态型(static)、外部型(extern)和寄存器型(register)。 完整的变量定义: [存储特性] [数据类型] 变量名; 1. 自动型变量 局部变量如不声明为static存储类别,都是动态分配存储空间的。 int f(a) { auto int b,c=3; } 实际上auto 可省略
159
◆[auto] 类型标识符 变量列表;常常可以缺省auto。
◆随函数的调用而存在,随函数的返回而消失,它们在一次调用结束到下一次调用开始之间不再占有存储空间。 ■作用域局限于所定义的函数,其生存期就是函数的生存期,在一个函数中引用另一个函数的自动型变量的值是错误的。
160
自动变量具有以下特点: 自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量只在该复合语句中有效。 2. 自动变量属于动态存储方式,只有在使用它时即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。
161
2. 静态型变量 ■静态型变量是指在编译时分配存储空间的变量。 ◆static 类型标识符 变量列表; 函数内部定义的静态变量
作用域与自动型变量相同 局部静态变量 当所在的函数执行结束后,静态变量所占内存单元并不释放,其值仍然保留 函数外部定义的静态变量 在程序中凡未指明存储类型的全局变量均为全局静态型变量 全局静态变量 作用域仅限所在的源程序文件
162
例8.17 考察静态局部变量的值 3 1 4 b c 7 8 9 f (int a) { auto int b=0;
例 考察静态局部变量的值 f (int a) { auto int b=0; static int c=3; b=b+1;c=c+1; return(a+b+c); } main() int a=2,i; for (i=0;i<3;i++) printf ("%d ",f(a)); 3 1 4 b c 第一次 调用开始 调用结束 第二次
163
静态变量的类型说明符是static。静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量。
例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。 对于自动变量,前面已经介绍它属于动态存储方式。 但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。 由此看来, 一个变量可由static进行再说明,并改变其原有的存储方式。
164
在局部变量的说明前再加上static说明符就构成静态局部变量。例如:
static int a,b; static float array[5]={1,2,3,4,5}; 静态局部变量属于静态存储方式,它具有以下特点: (1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。
165
(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。
(3对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。
166
根据静态局部变量的特点,可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。
167
【例】main() { int i; void f(); /*函数说明*/ for(i=1;i<=5;i++) f(); /*函数调用*/ } void f() /*函数定义*/ auto int j=0; ++j; printf("%d\n",j);
168
程序中定义了函数f,其中的变量j 说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋初值为0,故每次输出值均为1。现在把j改为静态局部变量,程序如下:
169
由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。
main() { int i; void f(); for (i=1;i<=5;i++) f(); } void f() static int j=0; ++j; printf("%d\n",j); 由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。
170
对静态局部变量的说明: (1)静态局部变量属于静态存储类别,在静态存储区分配存储单元。在程序整个运行期间都不释放。而自动变量属动态存储类别。
(2)静态局部变量是在编译期间赋值的,即只赋出值一次。自动变量是在调用时赋值的。 (3)静态局部变量如不赋值,则自动赋值为0,而动态变量如不赋值则为不确定的数。 (4)虽然静态局部变量在函数调用后仍然存在,但不能被其它函数引用。 1! =1 2! =2 3! =6 4! =24 5! =120
171
printf ("%d!=%d\n",i,fac(i)); } int fac(int n) {static int f=1; f=f*n;
静态局部变量的应用: 1!=1 2!=2 3!=6 4!=24 5!=120 (1)需要保留上一次的值时 (2) 如果初始化后,只被引用而 不改变其值 main() {int i; for (i=1;i<=5;i++) printf ("%d!=%d\n",i,fac(i)); } int fac(int n) {static int f=1; f=f*n; return (f);}
172
3. 寄存器型变量 内存变量 变量 (按使用的存储媒介) 寄存器变量 数据 内存 运算器 运算器 结果 控制器 数据
前面所介绍的变量都为内存变量 内存变量 由编译程序在内存中分配存储单元 变量的存取在内存储器中完成。 变量 (按使用的存储媒介) 使用CPU中的寄存器来存取变量的值 寄存器变量 寄存器处在CPU的内部 可避免CPU频繁访问存储器,从而提高程序的执行速度。 数据 内存 运算器 运算器 结果 控制器 数据 寄存器
173
不能定义为全局变量 只限于整型、字符型和指针型局部变量。 不可以定义为静态局部变量。 寄存器型变量 只能为动态变量 一般只允许同时定义两个寄存器变量。 寄存器型变量的定义形式: register 类型名 变量名; register int a; register char b;
174
对寄存器变量还要说明以下几点: 1. 只有局部自动变量和形式参数才可以定义为寄存器变量。因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为寄存器变量。 2. 在Turbo C,MS C等微机上使用的C语言中, 实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高。而在程序中允许使用寄存器变量只是为了与标准C保持一致。 3. 即使能真正使用寄存器变量的机器,由于CPU 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。
175
说明: (1) 只有局部自动变量和形式参数可以作为寄存器变量,其他不行。
(2) 不能定义任意多的寄存器变量。不同机器对寄存器变量的处理不同。如:有的机器按自动变量处理。有的系统只允许将int,char和指针型变量定义为寄存器变量。 当今的优化系统,能够识别使用频繁的变量,自动将这些变量放在寄存器中。 (3) 不能将静态变量定义为寄存器变量。如: register static int a,b,c
176
4. 外部型变量 ◆“外部型变量”,是相对于在函数“内部”说明的变量而言的,它们在函数外部定义。 ◆外部型变量的声明形式: extern 类型标识符 变量名; ■外部型变量生存期为整个程序的运行期。 ◆在引用外部型变量的文件中不会为其分配内存空间。
177
■外部型变量又称为全局变量,在编译时分配存储单元。
◆若变量的定义与引用处于同一源文件(即全局变量),在该源文件的不同函数中可以通过变量名对这种变量进行直接引用,作用域在整个源文件。 ■若变量的定义与引用分别处于不同的源文件中,即将变量的作用域延伸到其它的源文件,则需要在引用该变量的源文件中对它进行声明,源文件或源文件中的函数才能引用它。
178
int a=1;/在s.c文件定义了全局变量a*/ void fun() { … /*在函数fun中引用变量a不必声明*/ }
例6-16: int a=1;/在s.c文件定义了全局变量a*/ void fun() { … /*在函数fun中引用变量a不必声明*/ } /*源文件s.c*/ void main() { … /*在函数main中引用变量a不必声明*/ } extern int a; /*声明变量a为外部型变量*/ void fac(int n) … a=a*n; /*在函数fac外已声明a为外部变量,函数fac可以引用a*/ …} /*源文件d.c*/
179
外部变量的几个特点: 1. 外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变量是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。 2. 当一个源程序由若干个源文件组成时,在一个源文件中定义的外部变量在其它的源文件中也有效。
180
extern扩展作用域 1. 在一个文件内声明外部变量。 如果外部变量的定义不在文件的开头,其有效范围只是限于定义处到文件的终了。如果在定义点之前的函数想使用变量。 则在引用之前使用关键字声明。
181
如: int max (int x,int y) { int z; z=x>y ?x:y; return (z); } main ()
{extern [int] A, B; printf ("%d", max(A,B)); } int A =13, B=-8;
182
2.在多文件的程序中声明外部变量 如果两个文件中用同一个变量,不能在两个文件中同时定义,应该在一个文件中定义,而在另一个文件中用extern中说明。
183
例如有一个源程序由源文件F1.C和F2.C组成:
int a,b; /*外部变量定义*/ char c; /*外部变量定义*/ main() { …… } /*F2.C*/ extern int a,b; /*外部变量说明*/ extern char c; /*外部变量说明*/ func (int x,y) { …… }
184
在F1. C和F2. C两个文件中都要使用a,b,c三个变量。在F1. C文件中把a,b,c都定义为外部变量。在F2
在F1.C和F2.C两个文件中都要使用a,b,c三个变量。在F1.C文件中把a,b,c都定义为外部变量。在F2.C文件中用extern把三个变量说明为外部变量, 表示这些变量已在其它文件中定义,并把这些变量的类型和变量名,编译系统不再为它们分配内存空间。 对构造类型的外部变量, 如数组等可以在说明时作初始化赋值,若不赋初值,则系统自动定义它们的初值为0。
185
例8.21 给定b,输入A,m计算 axb, am 文件file2.c extern A; power(int n); {
int A; main() { int power (int); int b=3,c,d,m; scanf ("%d,%d",&A, &m); c=A*b; printf ("%d*%d=%d\n", A,b,c); d=power (m); printf ("%d*%d=%d\n",A,m,d); } 文件file2.c extern A; power(int n); { int i, y=1; for (i=1;i<=n;i++) y=y*A; return (y); }
186
即使在file1.c中 定义了A,但在 file2.c中仍然不能 使用 5. static 声明外部变量
static int A; main() { } File2.c extern int A; fun (int n) {…. A=A*m; …} 即使在file1.c中 定义了A,但在 file2.c中仍然不能 使用
187
全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
188
由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。
189
6. 关于变量的声明和定义 main() { extern A; /*声明*/ … } int A; /*定义*/ 定义只能有一次,声明可以有多次。只能在定义中初始化,而不能在声明中初始化 如 extern int A= 4; (错误)
190
存储类别小结 局部变量 全局变量 static int a; 静态内部整型变量或静态外部整型变量
auto char c; 自动变量,在函数内部定义 register int b; 寄存器变量,在函数内部定义 1从作用域分: 局部变量 全局变量 自动变量,即动态局部变量(离开函数,值就消失) 静态局部变量(离开函数,值就消失) 静寄存器变量(离开函数,值就消失) 形式参数可以定义为自动变量或寄存器变量 静态外部变量(只限本文件使用) 外部变量(非静态的外部变量,允许其他文件使用)
191
(2)从变量存在的时间区分: 动态存储 静态存储 自动变量(本函数内有效) 寄存器变量(本函数内有效) 形式参数 静态局部变量(函数内有效)
静态外部变量(本文件内有效) 外部变量(其他文件可以使用)
192
(3)从变量的存储位置来区分 内存中静 态存储区 内存中动态存储区:自动变量 和形式参数 CPU中的寄存器:寄存器变量 静态局部变量
静态外部变量 外部变量(其他文件可以使用)
193
8.10内部函数和外部函数 根据函数能否被其它文件调用,将函数区分为内部函数和外部函数。 8.10.1 内部函数
内部函数只能被本文件的其它函数调用。 Static 函数名(形参表) 外部函数 (1)在定义函数时加 extern 表示可以被其他文件调用。 如 extern int fun (int a, int b) extern 可以省略 (2)在需要调用此函数的文件中,用extern 声明所用的函数是外部函数
194
{extern enter_string(char str[80]);
例8.22 有一个字符串内有若干个字符,今输入一个字符,要求将该字符删去。 File1.c (文件1) main () {extern enter_string(char str[80]); extern delete_string(char str[ ],char ch); extern print_string(char str[ ]); char c; char str[80]; enter_string(str); scanf("%c", &c); delete_string(str,c); print_string(str); }
195
abcdefcfgtac abdefgta c file2.c (文件2) #include <stdio.h>
enter_string(char str[80]) /*定义外部函数*/ {gets(str);} file3.c(文件3) delete_string (char str[ ], char ch) {int i,j; for (i=j=0;str[ i]!='\0';i++) if (str [i]!=ch) str [j++] = str[i]; str[j] ='\0';} file4.c (文件4) print_string(char str[]) { printf ("%s",str); } abcdefcfgtac c abdefgta
196
本章结束
197
函数的类型决定返回值的类型 main( ) {float a,b; /* a,b 为实型的*/ int c; /* c为整型的*/
scanf(“%f,%f ”,&a,&b); c=max(a,b); printf (“ max is %d\n”,c); } max(x ,y) float x,y; {float z; /* z 为实型的*/ z=x>y? x:y; return(z); } 运行结果: 2.5,3.5 max is 3
198
例8 .1.3 选择法排序: a[0] a[1] a[2] a[3] a[4] 3 6 1 9 4 未排序 1 6 3 9 4
未排序
199
void sort (int array[ ], int n)
{int i ,j,k,t; for (i=0;i<n-1;i++) {k=i; for(j=i+1;j<n;j++) if (array[j]<array[k]) k=j; t=array[k];array[k]=array[i];array[i]=t; } /* 子函数部分*/
200
main( ) {int a[10],i; printf(“enter the array\n”); for (i=0;i<10;i++) scanf(“%d”,&a[i]); sort(a,10); /*函数调用*/ printf(“the sorted array:\n”); for(i=0;i<10;i++) printf(“%d ”,a[i]); printf(“\n”); } /*主函数部分*/
201
23矩阵,求最大元素. max_value(int array[ ][3]) {int i,j,k,max;
max=array[0][0]; for (i=0;i<2;i++) for(j=0;j<3;j++) if(array[i][j]>max) max=array[i][j]; return(max); } main( ) {static int a[2][3]={1,5,8,20,4,7}; printf(“max is %d\n”,max_value(a));}
202
变量定义的使用范围 main( ) {int a, b; …. {int c; c=a+b; ….} }
203
形参和实参类型必须一致 long max (long x,long y) { long z; z=x>y? x:y return z;
} main() {long max; max=max(153L,12L); printf("max=%ld",max); long max (long x,long y) { long z; z=x>y? x:y return z; } main() {long max; max=max((long)153,(long)12); printf("max=%ld",max);
204
例8 .6 用弦截法求方程的根 x3-5x2+16x-80=0 f(x2) f(x) x1 x x x2 f(x1)
(2) (3) 如f(x1)与f(x)同号,根在x,x2之间 (4)重复2和3直到f(x)<e 为止
205
root=x 输出root 输入x1,x2, 求f(x1),f(x2) 直到f(x1)和f(x2)异号
求f(x1)与f(x2)连线与x轴的交点x y=f(x),y1=f(x1) x2=x y2=y x1=x y1=y y与y1同号 真 假 直到|y|<e root=x 输出root
206
#include <math.h>
float f(float x) { float y; y=((x-5.0)*x+16.0)*x-80.0; return (y);} float xpoint(float x1,float x2) {float y; y= (x1*f(x2)-x2*f(x1))/(f(x2)-f(x1)); float root(float x1,float x2) {int i; float x,y,y1; y1=f(x1); do {x=xpoint(x1,x2); y=f(x); if(y*y1>0) {y1=y; x1=x;} else x2=x; }while(fabs(y)>=0.0001); return(x); } main() { float x1,x2,f1,f2,x; do { printf ("input x1,x2:\n"); scanf("%f,%f",&x1,&x2); f1=f(x1); f2=f(x2); }while(f1*f2>=0); x=root(x1,x2); printf("a root of equation is %8.4f",x);}
207
{int large (int x, int y); int a[10],b[10],i,n=0,m=0,k=0;
例8.10 数组a,b 各有10个元素,,将他们对应地逐个相比,分别统计出a数组大于,等于和小于b数组的次数.如果a数组大于b数组的次数比b数组大于a数组的次数多,则a数组大于b数组 main() {int large (int x, int y); int a[10],b[10],i,n=0,m=0,k=0; printf ("enter array a:\n"); for (i=0;i<10;i++) scanf ("%d",&a[i]); printf ("\n"); printf ("enter array b:\n"); for (i=0;i<10;i++) scanf ("%d",&b[i]);
208
else flag=0; return (flag);} for (i=0;i<10;i++)
{if (large(a[i],b[i])= =1) n=n+1; else if (large(a[i],b[i])= =0) m=m+1; else k=k+1;} printf ("a[i]>b[i] %d times\n a[i]=b[i] %d times\n a[i]<b[i] %d times\n",n,m,k); if (n>k) printf ("array a is large than array b\n" ); else if (n<k) printf ("array a is smaller than array b\n" ); else if (n==k) printf ("array a is equal to array b\n" );} large (int x, int y) { int flag; if (x>y) flag=1; else if (x<y) flag=-1; else flag=0; return (flag);}
209
例8.11 有一个一维数组score,内放10个学生成绩,求平均成绩。
float average (float array[10]) {int i; float aver,sum=array[0]; for (i=1;i<10;i++) sum=sum+array[i]; aver =sum/10; return (aver);} main() { float score[10],aver; int i; printf ("input 10scores:\n"); for (i=0;i<10;i++) scanf ("%f", &score[i]); printf ("\n"); aver=average(score); printf ("average score is %5.2f",aver);} Input 10 scores: …… average score is 83.40
210
例8.12 返回 float average (float array[], int n) {int;
float aver,sum, sum=array[0]; for (i=1;i<n;i++) sum=sum+array[i]; aver =sum/n; return(aver);} main() { float score_1[5]={98,5,97,5,60,55} float score_2[10]= {67.5,98.5,99,69.5,76.5,54,60,99.5}; printf (average of calss A is %6.2f\n",average(score__1,5)); printf (average of calss A is %6.2f\n",average(score_2,10)); } 返回
211
8.15 求学生的平均分,最高分,最低分 float max=0,min=0;
float average(float array[],int n) { int i; float aver,sum=array[0]; max=min=array[0]; for(i=1;i<n;i++) { if(array[i]>max) max=array[i]; else if (array[i]<min) min=array[i]; sum=sum+array[i]; } aver=sum/n; return (aver);
212
Max Min 全局变量 Ave score 10 Max Min Ave array n Max Min main()
{ float ave,score[10]; int i; for (i=0;i<10;i++) scanf("%f", &score[i]); ave=average(score,10); printf("max=%6.2f\nmin=%6.2f\naverage=%6.2\n", max,min,ave); } Max Min 全局变量 Ave score Max Min Ave array n Max Min
213
例8.14 求3x4矩阵最大值 返回 max_value (int array[][4]) {int i,j,k,max;
max=array[0][0]; for(i=0;i<3;i++) for (j=0;j<4;j++) if (array[i][j]>max) max =array[i][j]; return(max);} main() {int a[3][4]={ {1,3,5,7},{2,4,6,8},{15,17,34,12}}; printf("max value is %d\n",max_value( a ) ); } 返回
214
例8.16 外部变量与局部变量同名 int a=3,b=5; max(int a, int b) {int c;
c=a>b? a:b; return (c ); } main() { int a=8; printf ("%d",max(a,b));
Similar presentations