Download presentation
Presentation is loading. Please wait.
1
C语言程序设计 第八章 函数
2
主要内容 8.1 函数的定义和调用 8.2 变量的存储类型及作用域 8.3 函数的数据传递
3
概述 函数 函数是C语言程序的基本结构。C语言提供了许多函数。 它包括: I/O函数 数学函数 字符串函数、字符函数函数
时间(转换和操作)函数 接口函数 动态地址分配函数 目录函数 过程控制函数 字符屏幕和图形功能函数等。 用户可以根据需要选择使用。这一类函数称为库函数。库函数函数的引用的方法很简单,这里不赘述。
4
概述 库函数 库函数是由编译系统提供的现成函数。以前用过的scanf, printf, getchar等都是库函数。
在编写C程序时,尽可能多用库函数,以提高编程效率和质量。要调用某个库函数,需在程序的头部用包含命令(#include)将说明该函数原型的头文件包含进本程序中。使用库函数应注意以下几个问题: 函数的功能; 参数的数目、顺序和类型; 函数返回值类型; 需要使用的包含文件。
5
概述 常用的库函数 ex log10x 函数格式 所属函数库 功能 int getchar( ); stdio.h 标准输入一个字符
conio.h 不回显从键盘直接读入一个字符 int putch( int ch ); 向屏幕直接输出一个字符 char *gets( char * ); 标准输入一行文字 int puts( char * ); 标准输出一行文字(自动加换行) double sin( double ); math.h 正弦函数sin(x) double pow(double x,double y); xy double exp( double x ); ex double log( double x ); double log10( double x ); lnx log10x void exit( int status ); tdlib.h 强制结束程序
6
8.1 函数的定义和调用 函数的定义 函数的定义就是创造、编制函数。函数的定义过程就是编制函数的过程。 函数定义的格式
数据类型 函数名( 形参类型1 形参1,···形参类型n 形参n) { 说明语句; 可执行语句; return(表达式) ; } 例如:int myfun(int x, int y) { int z; if(x>y)z=x; z=y; return(z); }
7
8.1 函数的定义和调用 函数定义的说明 函数返回类型:若函数无返回值,或返回整型值, 可省略数据类型说明。 形参:
形参是函数接收外部数据的单向通道。函数调用时, 调用函数的实参将其数据传递给形参。 函数可以有多个或0个形参,各形参之间用逗号隔开。 形参可以是变量、指针或数组等; 形参不能是常量和表达式。形式参数是局部变量。作用域、生命期为函数体内。 函数名:函数名的命名规则同标识符的命名规则
8
8.1 函数的定义和调用 使流程返回主调函数,宣告函数的一次执行结束,在调用期间所分配的变量单元被撤消。
return语句:如果函数有返回值,则函数的最后一条语句就是 return语句。返回的意义是: 使流程返回主调函数,宣告函数的一次执行结束,在调用期间所分配的变量单元被撤消。 送函数值到调用表达式中。有些函数有返回值,有些函数没有返回值。 return语句的使用格式是: return; 或者 return (表达式); 或者 return 表达式;
9
8.1 函数的定义和调用 注意 return后的表达式的值类型必须与函数类型相同;
如果函数类型为空(void)类型,则函数体中可以不使用return语句,或使用不带参数的return。 一个函数允许有一个或多个return语句,流程执行到其中一个 return时即返回主调函数。如果有多个return语句,每个return后面的表达式的类型应相同。 如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据自动进行类型转换。 被调用函数中没有return语句,则函数带回一个不确定的值。建议用void标记函数的类型,则系统可保证函数不带回任何值,即禁止在调用函数中使用被调用函数的返回值。
10
8.1 函数的定义和调用 C语言准许定义“空函数”。 例如: dummy( ) { }
调用此函数, 什么工作也不做。它表明有一个函数, 尚未有具体的内容, 待以后将功能补充上去。一般在系统开发初期, 一些没开发出来的部分用此方法。
11
8.1 函数的定义和调用 函数的引用 函数定义好后,便可以在表达式中使用。 函数引用的格式 函数名(实参表列)
实参的作用是在程序发生函数调用时, 将其数据传递给函数的形参, 即向函数传送数据。 int fun1(x1,y1) { int z; z = x1 +y1; return( z ); } void main( ) { int s,x=2,y=6; s = fun1(x,y); printf (“%d”, s); 运行结果: 8
12
8.1 函数的定义和调用 实参与形参的关系 实参与形参的个数要相等。多个实参时要用逗号隔开。 实参与形参的数据类型相一致。
实参与形参的数据的传递是单向。即实参传给形参。同时是按排列顺序依次对应传递。 实参名与形参名可以相同,也可以不相同。 函数返回 如果函数需要返回值, 就用返回值带回调用函数的处理结果。返回值用赋值语句赋给主函数中的变量, 以接收其返回值。 返回值和接受返回值的变量的类型必须一致。
13
8.1 函数的定义和调用 函数调用 实参和形参的个数、类型和顺序必须一致。 实参可以是常量、变量名、数组名、数组元素或表达式。
在调用函数前, 必须先定义。
14
8.1 函数的定义和调用 函数调用的形式 在C语言中, 凡是能调用变量或表达式的地方, 均可调用函数。假设已定义了一个函数,即:
我们有如下三种形式调用: 函数调用语句形式,如: a=max(s1,s2); 这就是函数调用语句, 简称函数语句。 在表达式中引用,如: 例: y=max(x1,x2) / max(y1,y2); 函数调用函数,如: y=abs(max(rand( ),rand( ))); // 其中rand( ) 为随机函数。 int max(x,y) int x,y; { int z; z=(x>y)? x:y; return(z); }
15
8.1 函数的定义和调用 函数的声明 如果定义的函数在使用之后,则函数必须在使用前先声明。目的是告诉编译程序该函数返回值的数据类型, 以便编译程序能够检查对函数的调用是否正确,会不会发生错误的类型转换等。 函数说明的格式 数据类型 函数名( 形参类型 形参,…); 函数声明注意的问题 被调用函数的声明放在源文件头, 则该声明对整个源文件都有效 int f1( ); int f2( ); main( ) {… f1( ); f2( ); … } main( ) { int f1( ); int f2( ); … f1( ); f2( ); } int f1( ) { ... f2( ); } int f2( ) { … 函数f1中没有对函数f2进行说明,其引用是错误的。 √ √ ×
16
8.1 函数的定义和调用 如果被调用函数在调用函数内部声明, 则该声明仅对该调用函数有效。如果被调用函数是在函数外声明, 则该声明对以后的所有调用都有效。 void main( ) { float max(float a, float b, float c ); float x,y,z,m; scanf(“%f %f %f”,&x,&y,&z); m=max(x,y,z); printf(“\n%f:”,m); } float max(float a, float b, float c) { float result; result=a; if(result<b) result=b; if(result<c) result=c; return(result); 函数声明 float max(float a, float b, float c ); 函数使用 m=max(x,y,z); 函数max( )定义 float max(float a, float b, float c) { float result; result=a; if(result<b) result=b; if(result<c) result=c; return(result); }
17
8.1 函数的定义和调用 如果调用函数和被调用函数是在同一个源文件中, 而且程序中被调用函数是在调用函数之前已定义, 此时可以省略被调用函数的声明。 float max(float a, float b, float c) //先定义 { float result; result=a; if(result<b) result=b; if(result<c) result=c; return(result); } void main( ) { float x,y,z,m; scanf(“%f %f %f”,&x,&y,&z); m=max(x,y,z); //后使用,所以不用声明 printf(“\n%f:”,m);
18
8.1 函数的定义和调用 如果所有被调用函数的说明都是在源文件开头, 则在该源文件内的所有调用函数中不必再对被调用函数声明。
如果所有被调用函数的说明都是在源文件开头, 则在该源文件内的所有调用函数中不必再对被调用函数声明。 float f1( ); float f2( ); main( ) { … f1( ); … f2( ); } float f1( ) { … } float f2( ) 声明函数 f1、f2 定义函数 f1 定义函数 f2
19
8.1 函数的定义和调用 如果被调用函数的声明在源文件中间且在所有函数之外某处被说明时, 则在被说明之后引用它时, 不必再声说明; 但在被说明之前调用它时, 需对其进行说明。 如果函数的返回值是整型或字符型时, 可省略说明。
20
8.1 函数的定义和调用 例1 编制求两数最大值的程序。 #include<stdio.h> void main( )
例1 编制求两数最大值的程序。 #include<stdio.h> void main( ) { float max( ); //函数声明 float a, b; scanf(“%f, %f ”,&a,&b); printf(“max is %f ”, max(a,b) ); //函数使用 } float max(float x, float y) // 函数定义 { if (x>y) return x; else return y; 运行结果: ←┘ max is 5.8
21
8.1 函数的定义和调用 思路:对输入的整数, 先求其绝对值, 然后分解各位上的数字,边分解边累加, 直到完毕。 请输入一个整数:456
例2 编程 计算任一输入整数的各位数字之和,请用函数的方法处理。 思路:对输入的整数, 先求其绝对值, 然后分解各位上的数字,边分解边累加, 直到完毕。 #include<stdio.h> #include “math.h” int func(int num); void main( ) { int n; printf(“请输入一个整数: "); scanf("%d",&n); printf(“result:%d\n",func(n)); } int func(int num) { int s=0; num=abs(num); do { s=s+num%10; num=num/10; } while(num); return s; 请输入一个整数:456 result: 15
22
8.1 函数的定义和调用 例3 编制求n! 的函数。 #include<stdio.h>
float factorial(int n) { float product; for( product=1;n>0;n--) product*=n; return(product); } void main() { int n; printf(“请输入一个整数:"); scanf("%d",&n); printf("n! = %e\n",factorial(n)); 运行结果: 请输入一个整数:6 ←┘ n! = e+002
23
8.1 函数的定义和调用 函数的嵌套调用 C函数不能嵌套定义,但允许嵌套调用,即被其他函数调用的函数可以调用另一个函数。请看示意图:
void main() { int i; i = num1(); … } int num1() { int j; j = num2(); … } int num2() { … }
24
8.1 函数的定义和调用 嵌套调用示例:计算 s = 1!+2!+3!+ … + 20! = ?
思路:编写一个主函数和两个子函数, 嵌套调用。 #include<stdio.h> void main() { double sum(int n) ; printf("s = %-12.5le\n", sum(20) ); } double sum(int n) { double fa(int k) , s=0; int i; for (i=1; i<=n; ++i) s = s + fa(i) ; return s; double fa ( int k ) { int i; double t; t = 1; for ( i = 1; i<=k; ++i) t*=i ; return t; 运行结果: S = e+018
25
8.1 函数的定义和调用 函数的递归调用 所谓函数的递归调用,就是函数直接或间接地调用自己。在设计递归函数时应当考虑到递归的终止条件。
递归过程不应无限制地进行下去,不应该出现无终止的递归调用,而只应出现有限次数的、有终止的递归调用。当调用若干次以后,就应当到达递归调用的终点得到一个确定值,然后进行回代,回代的过程是从一个已知值推出下一个值。实际上这是一个递推过程。 递归总是由两部分组成的:递归方式和递归终止条件。 递归三要素 递归形式 递归规则 递归终结条件
26
8.1 函数的定义和调用 1 当 n=1 或 n=0时 例4 计算 n! = n(n-1)! 当n>1时
#include<stdio.h> float fa(int n) { float t; if (n== 1 || n==0) t =1 ; else t= n* fa(n-1) ; return t ; } void main() { int i ; scanf(”%d” , &i ); if (i<0) printf(”Data error !”) ; else printf(”%d ! = %-16.0f”, i, fa(i) ) ; 说明: 递归包括“回推”和“递归” 需有结束递归过程的条件
27
8.1 函数的定义和调用 递推 回归 4 * fa(3) 3 * fa(2) (4* 3* 2*1) 2* fa(1) (3* 2*1) 1
例5 计算 n! = 1 当 n=1 或 n=0时 n(n-1)! 当 n>1时 计算 fa(4) 递推 4 * fa(3) 输出 (4* 3* 2*1) 3 * fa(2) (4* 3* 2*1) 2* fa(1) (3* 2*1) 1 回归 2*1
28
8.2 变量的存储类型及作用域 对变量的访问就是对内存的访问。在C语言中变量的存储是有规定和区别的。
auto :自动型或堆栈型。 register:寄存器型。 static:静态型。 extern:外部型或全局型。 例如:auto int x; x 为自动变量。 static float y; y 为静态变量。
29
8.2 变量的存储类型及作用域 C 语言的存储空间 一般包括三部分: 程序区:存放执行程序的代码和static 静态变量;
静态存储区:存放程序的extern 外部变量、static 静态变量,其存放的数据在程序执行的过程中不释放,所对应变量值是不变的。 动态存储区(运行栈):程序运行时,存放数据的空间所对应变量是变化的。C语言程序在运行时,系统分配一个运行栈,存放以下数据: 函数调用时,保存调用现场的数据, 如返回点、寄存器中的值等。 函数调用时,存放被调用函数的形参。在函数调用时, 给形参分配存储空间, 以存放调用时传送给被调用的实参数据。 存放函数的返回值等。 为auto自动变量(register寄存器变量)分配存储空间。
30
8.2 变量的存储类型及作用域 用户区 存储器 存放执行程序的代码和静态变量 存放 static变量、 extern变量
存放auto变量( register变量) 函数的返回值、被调用函数的形参、保存调用现场的数据。 存储器
31
8.2 变量的存储类型及作用域 几个名词 静态变量:存储在静态存储区, 程序执行中始终不释放
动态变量:存储在动态存储区, 程序执行时 根据需要释放 内部变量:在函数或分程序内定义的变量。作用区域是整个函数或分程序 局部变量:内部变量都是局部变量。 外部变量:在函数或分程序外部定义的变量 全局变量:在程序开始定义的外部变量。作用区域是整个程序。 变量的作用域:变量在程序中的使用范围。 变量的生命期:程序中给变量分配的存储空间所保存数据的时间段 变量的可见性:存储器数据可操作时为可见的, 否则为不可见。
32
8.2 变量的存储类型及作用域 自动型变量(auto型)
概念:也叫局部变量。编译系统为自动型变量分配的是动态存储空间。运行程序如果离开了其作用域,,其变量的存储空间就释放, 故为动态变量。 定义格式: [auto] 数据类型 变量名;//auto可省略 定义的位置:在函数或分程序的开始位置。 性质:自动型变量是局部变量。作用范围限于函数或分程序内。该函数或分程序执行结束时,变量释放其存储空间,变量中的数据消失。在程序中,一般把计算或处理的中间结果的数据定义为自动型变量。
33
8.2 变量的存储类型及作用域 #include<stdio.h> float x,y; { int i,j;
char c1,c2; double d1,d2; int m,n; int u,v; { … auto int k; … } x,y 的作用域 { } 对于这一对花括号 i,j,c1,c2是内部变量 i,j,c1,c2 的作用域 { } 对于这一对花括号 i,j,c1,c2是外部变量 d1,d2,m,n,u,v 的作用域 K的作用域
34
8.2 变量的存储类型及作用域 例: float factorial(int n ) { float product;
for(product=1;n>0;n--) product*=n; return(product); } void main( ) { float factorial( ); int k; float sum; for(k=1,sum=0;k<=10;k++) sum=sum+factorial(k); 10 printf(“ n!=%e\n”,sum); n=1 变量 n和变量 product是自动变量 它们的作用域、生命期是整个factorial函数 当函数调用结束,函数返回值带回主函数后, 释放其存储空间,变量 n和变量product的数据消失。 此函数说明可以省略,因为定义在先,使用在后
35
8.2 变量的存储类型及作用域 寄存器型变量(register型) 概念:是把变量的数据存放在计算机CPU的寄存器中的变量。
定义的位置:在函数或分程序的开始的位置定义,先定义后引用。 存放数据的类型:int型、 short型、 char型。不能为其它类型 性质:存器变量是局部的动态变量。它的作用域、生命期局限在函数或分程序内, 函数或分程序执行结束时, 寄存器变量就被释放, 寄存器变量中的数据消失 适用场合:使用频繁的变量, 如把循环变量定义为寄存器变量,可以大大提高程序处理的速度,因为CPU对寄存器的读写速度远远快于对内存读写的速度 准许使用的数量:一般2-4个,视计算机类型而定。
36
8.2 变量的存储类型及作用域 x 例5 求 n3 =13+23+33+43+.. ..+n3 的值。 n=1 main( )
{ int n; register int i,sum; printf(“Enter n:”); scanf(“%d”,&n); sum=0; for(i=1;i<=n;i++) sum=sum+i*i*i; printf(“sum=%d”,sum); } 运行结果: Enter n: 10<CR> sum=3025 变量n是自动型变量 变量i,sum是寄存器变量 变量i、sum、 n的作用域
37
8.2 变量的存储类型及作用域 静态型变量(static型)
概念:在静态存储区存放的变量。所分配的存储空间在整个程序运行中自始至终归该变量使用, 自始至终都不释放。静态变量分内部静态变量和外部静态变量两种。 内部静态变量 在函数或分程序内定义, 仅在定义它的函数或分程序内使用 内部静态变量定义格式: static 数据类型 变量名表 内部静态变量定义的位置:在函数或分程序内的开始位置 内部静态变量的性质:局部的可见性、全局的生命期 作用域:局部。函数外不可见。但其数据不释放不消失。 生命期:全局。在整个程序的执行过程中,内部静态变量数据当函数调用结束时并不消失,下次调用继续使用原保留值
38
8.2 变量的存储类型及作用域 请看下面示例: float add(x,y) float x,y; { static float z;
z=z+x+y; return(z); } main( ) { float s,x=2,y=3; s=add(x,y); static 型变量在程序编译时,自动赋初值0 变量 z在函数结束时,并不释放,保留原值 S=5 S=10
39
8.2 变量的存储类型及作用域 外部静态变量 在函数外部定义的变量。其作用域是定义它的源文件。 定义格式(与内部静态变量的定义相同):
static 数据类型 变量名表 定义的位置:在源文件的开始和所有函数之外定义。 作用域:是定义它的源文件。 生命期:是程序的整个执行过程。外部静态变量在编译时,在包含它的源文件所在的程序代码区中为其分配存储空间,该空间在整个程序执行过程中都归该变量所有,直到程序执行结束时才释放。 不同源文件中外部静态变量可以同名,各文件的外部静态变量有其自己的存储空间,同名变量不会冲突。
40
8.2 变量的存储类型及作用域 /* file1.c */ static int x,y; main( ) { ... } fun( ) /* file2.c */ static int x,y; fun1( ) { ... } fun2( ) 文件file1.c和文件file2.c中的同名外部静态变量 x, y互不干扰。 外部静态变量提供了把数据隐藏起来的一种手段, 使得外部文件不能访问它们, 也使不同源文件的同名外部变量不会冲突。
41
8.2 变量的存储类型及作用域 例6 读程序, 写出程序运行结果。 #include<stdio.h> void main()
例6 读程序, 写出程序运行结果。 #include<stdio.h> void main() { int k=1; static int x=10; void other(); printf("\n----main----\n"); printf("k=%d x=%d\n",k,x); other(); printf("----main----\n"); printf("k=%d x=%d\n",k,x); } void other() { int k=100; static int x=200; x+=100; printf("----other----\n");
42
8.2 变量的存储类型及作用域 外部型变量(extern型) 外部型变量是全局的静态的变量,它在函数之外定义。
定义的位置:在函数之外定义。 作用域:全局。在静态存储区为它分配存储空间。所分配的存储空间在整个程序执行过程始终归该变量所有,其值不会消失。对整个程序中的所有函数都可见 生命期:整个程序。 注意: 如果外部变量是在源文件中各个函数之前定义,则该源文件中的各函数都可以使用它,不需指定为extern 在一个源文件中定义的外部变量,可以在另一个源文件中引用,但必须在另一个源文件中用extern进行说明。
43
8.2 变量的存储类型及作用域 file1.c int x,y; main( ) { ... } fun( ) file2.c
extern int x,y; f1( ) { y=x*x+1; .... } f2( ) { ... printf(%d %d”,x,y); ... x,y是文件file1.c中定义的变量。在文件file2.c中必须对变量x,y用extern int x,y进行说明, 然后file2.c中的各函数才可以使用他们。
44
8.2 变量的存储类型及作用域 如果外部变量与某函数内的局部变量同名,则在该函数内的同名变量的作用域内,局部变量有效,而外部变量暂不起作用。
45
8.2 变量的存储类型及作用域 例7 读程序,写出程序运行结果。 运行结果: j=10 j=20
例7 读程序,写出程序运行结果。 #include<stdio.h> int i=10; void main() { int j=0; int func(); j=func(); printf("j=%d\n", j ); } int func() { int k=0; k=k+i; i=i+10; return(k); 运行结果: j=10 j=20
46
8.2 变量的存储类型及作用域 静态函数、外部函数使用场合
静态函数局限于它所在的源文件, 对别的源文件中的函数不能引用。所以不同源文件中的内部函数可重名。 外部函数的作用域是整个程序, 在该作用域内的任何其它函数都可引用。 静态函数说明格式: static [数据类型] 函数名 ( ); 外部函数说明格式: [extern] [数据类型] 函数名( );
47
8.3 函数的数据传递 C语言函数间数据的通讯有如下方法: 参数传递方式,包括传值方式和传址方式; 函数返回值; 全局变量; 文件。
8.3 函数的数据传递 C语言函数间数据的通讯有如下方法: 参数传递方式,包括传值方式和传址方式; 函数返回值; 全局变量; 文件。 传地址方式 方法:它是把数据存放的地址传递给被调用函数的形参。这种方式一般以地址量作实参,如数组名或指针变量等;形参也是数组名或指针变量。 特点:一次可传递多个数据。
48
8.3 函数的数据传递 例8:以数组名为实参、形参的函数调用。 #include<stdio.h>
8.3 函数的数据传递 例8:以数组名为实参、形参的函数调用。 #include<stdio.h> int add ( int a[20], int n ) { int i,result=0; for(i=1;i<n;i++) result += a[i]; return result; } void main() { int a[20],i=0,sum=0; printf(“请输入整数到数组a[ ](结束:输入0):) \n"); while(a[i++]!=0 && i<20) scanf("%d",&a[i]); i=i; sum=add( a, i ); printf("\nsum=%d\n",sum); 请输入整数到数组a[ ](结束:输入0): Sum = 15
49
8.3 函数的数据传递 利用全局变量传递数据 方法:在一个程序中, 全局变量对所有函数都是可见的, 可以利用它来实现函数间的通讯。
8.3 函数的数据传递 利用全局变量传递数据 方法:在一个程序中, 全局变量对所有函数都是可见的, 可以利用它来实现函数间的通讯。 优点: 方法简单, 程序的运行效率高 缺点:各个函数都可以对全局变量进行操作, 出错后很难确定错误发生的位置。所以过多的全局变量不利于程序的调试和维护。这种方式不提倡使用。
Similar presentations