第八章 函数 §8.1 概述 一个较大程序一般分为若干个程序模块,每一个模块实现一个特定的功能。所有的高级语言中都有子程序的概念,在C中子程序就是函数。 一个C程序可由一个主函数和若干个函数构成,由主函数调用其它函数,其它函数也可以相互调用
在程序设计中,常将一些常用功能模块编写成函数,放在库中供调用,减少重复工作量。 main() {printstar();print_message();printstar();} printstar() {printf(“***************\n”);} print_message() { printf(“ I am a student.\n”);}
说明: 1、一个源程序文件由一个或多个函数组成。一个源程序文件是一个编译单位,不是以函数为单位进行编译 2、一个C程序由一个或多个源程序文件组成。分别编写、分别编译。一个源文件可为多个C程序公用。 3、C程序执行是从main开始的,调用其它函数后流程回到main函数,在main函数中结束整个程序的运行
4、所有函数都是并行的,定义时相互独立,不能嵌套定义,但可互相调用,不能调用main函数 5、从用户使用的角度,函数有两种 (1)标准函数,即库函数,由系统提供 (2)用户自己定义的函数,解决用户专门问题
6、函数形式看,函数分为两类 (1)无参函数 (2)有参函数,调用函数与被调用函数之间有参数传递
§8.2 函数定义的一般形式 1、无参函数的定义形式 类型说明符 函数名() {说明部分 语句} “类型说明符”指定函数值的类型,即函数带回来的值的类型.如果不带回函数值,也可以不写类型标识符
2、有参函数的定义形式 类型说明符 函数名(形式参数表列) {说明部分 语句} 例如: int max(int x, int y) { int z; z=x>y?x:y; return(z);}
3、可以有“空函数”的定义形式 类型说明符 函数名() { } 空函数的作用 4、对形参声明的传统形式
§8.3 函数参数和函数的值 8.3.1 形式参数和实际参数 主调函数与被调用函数之间有数据传递关系. 在定义函数时函数名后面的括弧中的变量名称为“形式参数”(简称“形参”),在调用函数时,函数名后面括弧中的表达式称为“实际参数”(简称“实参”)
main() { int a,b,c; scanf(“%d,%d”,&a,&b); c=max(a,b); printf(“Max is %d”,c);} int max(int x, int y) { int z; z=x>y?x:y; return(z);}
关于形参与实参的说明: 1、在定义函数中指定的形参变量,它们并不占内存单元。只有在被调用时形参才分配内存单元,在调用结束后,形参所占内存单元也被释放。
2、实参可以是常量、变量和表达式,如 max(3,a+b) 但要求有确定的值.在调用时实参的值赋给形参(如果形参是数组名,则传递的是数组首地址而不是变量的值.) 3、在被定义的函数中,必须指定形参的类型 4、实参与形参的类型应一致。但字符型与整形可以互相通用。
5、C语言规定,实与形的数据传递是“值传递”,单向传递即实传给形,而不能由形参传值给实参。 在调用函数时,给形参分配存储单元,并传值,调用结束后,形参单元释放,而实参单元不变。 a b 实参 a b x y 形参 x y 形实结合 结合后 2 3 2 3 2 3 10 15
8.3.2 函数的返回值 通常希望通过函数调用能得到一个确定的值,这就是函数的返回值。 1、函数的返回值是通过函数中的return语句获得的,将被调用函数中的一个确定值带回主调函数中。有时没return语句 return语句的几个语法规则 2、函数值的类型。在定义函数时进行定义。不加类型定义的,一律按整型处理
3、如果函数值类型与return语句中表达式类型不一致,则以函数类型为准 5、为了明确“不带回值”,可以用“void”定义“无类型”(或称“空类型”)
§8.4 函数的调用 8.4.1 函数调用的一般形式 函数调用的一般形式为: 函数名(实参序列); 对无参函数 对有参函数,实参与形参有个数上相等,顺序与类型上一致,但应注意求值顺序
main() { int i=2,p; p=f(i,++i); printf(“%d”,p);} /*p是多少?*/ int f(int a,int b) {int c; if(a>b)c=1; else if(a==b)c=0; else c=-1; return c;}
8.4.2 函数调用的方式 按函数在程序中出现的位置分为三种 1、函数语句。要求完成一个操作而不带回值。如: printstar(); 2、函数表达式。函数调用出现在表达式中,如:c=2*max (a,b); 3、函数参数。函数调用作为函数的实参 如:m=max(a,max(b,c)); 又如: printf(“%d”,max(a,b));
8.4.3 对被调函数的说明 在一个函数中调用另一个函数需要具备什么条件呢? 1、首先要已经存在,即已定义 2、如果使用库函数,则应使用#include命令。 如: #include “stdio.h” 3、如果使用用户自己定义的函数,而且在同一个文件中,一般还应在主调函数中对被调用函数作声明,即向编译系统声明将要调用此函数
main() {float add(float x,float y); float a,b,c; .......} float add(float x,float y) { float z; ...... }
(2)被调用函数的定义出现在主调函数之前,可以不声明 形式为: 函数类型 函数名(......); 注意: 对函数的“定义”和“声明”不是一回事 C语言规定: (1)如果函数是int或char时,可以不声明 (2)被调用函数的定义出现在主调函数之前,可以不声明
float add(float x,float y) { float z; ...... } main() {/*float add(float x,float y);*/ float a,b,c; .......}
(3)如果已在所有函数定义之前,即在文件开头, 在函数外部作了说明.
float add(float x,float y) main() {/*float add(float x,float y);*/ float a,b,c; .......} { float z; ...... }
§8.5 函数的嵌套调用 C语言的函数定义都是相互平行的、独立的,也就是说在定义函数时,一个函数内不能包含另一个函数,即不允许嵌套定义。但允许嵌套调用,即调用一个函数过程中,又调用另一个函数。 例:用弦截法求方程的根
§8.6 函数的递归调用 在调用一个函数时出现直接或间接地调用该函数本身,称为函数的递归调用,这是C语言的特点之一。如: int f(int x) { int y,z; ∶ z=f(y); ∶ return(2*z);}
如: #include "stdio.h" int f(int n) { int m; if(n==1)m=1; else m=n*f(n-1); return(m); }
注意:递归程序分析方法 void main() { int n,m; scanf("%d",&n); m=f(n); printf("%d\n",m); } 递归过程可以分为两个阶段:一是“回推”;二是“递推”。 注意:递归程序分析方法
§8.7 数组作为函数参数 可以用变量做函数参数,此外数组元素也可以做函数实参,其用法与变量相同,数组名也可以做实参和形参,传递的是整个数组。 一、数组元素做函数实参 见书中例题,与变量相同、单向传递、值传递 二、可以用数组名作为函数参数,此时实参和虚参都应用数组名
void sub(int a[10],int n) {int i,b; for(i=0;i<n/2;i++){b=a[i];a[i]=a[n-1-i]; a[n-1-i]=b;}} main() {int b[10]={0,1,2,3,4,5,6,7,8,9},m=10; sub(b,m); for(i=0;i<m;i++)printf(“%d”,b[i]); }
说明: 1、形参数组大小可以不指定 2、数组名做函数参数时,不是“值传送”,不是单向传递,而是“地址传递”。 三、用多维数组名作为函数参数 注意多维数组元素在内存中的存放顺序
§8.8 局部变量和全局变量 8.8.1 局部变量 在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是在本函数才能使用它们。如
float f1(int a ) {int b,c; a,b,c有效 ……} main() { float a,m,n; a,m,n有效 8.8.2 全局变量 程序的编译单位是源程序文件,一个源文件包含一个或若干个函数。在函数内
定义的变量是局部变量,而在函数之外定义的变量是外部变量,外部变量是全局变量。全局变量可以为本文件中其它函数所共用。它的有效范围为:从定义变量的位置开始到本源文件结束。 int a; main() {sub();printf(“%d\n”,a);} sub() {a=5;} 在第三行加上int a=3;后的结果如何?
在一个函数中既可使用局部变量,也可使用全局变量。 说明: (1)全局变量的作用是增加了函数间的数据联系的渠道。 (2)不在必要时不使用全局变量,原因有三。 (3)在同一源文件中,如全局变量与局部变量同名,以局部变量说明为准。
§8.9 变量的存贮类别 8.9.1 动态存贮方式与静态存贮方式 从变量作用域(空间角度): 从变量存在的时间(时间角度):静态和动态 §8.9 变量的存贮类别 8.9.1 动态存贮方式与静态存贮方式 从变量作用域(空间角度): 从变量存在的时间(时间角度):静态和动态 静态存贮:是指在程序运行期间分配固定的方式 动态存贮:是指在程序运行期间根据需要进行动态的分配的方式
内存中供用户使用的存贮空间情况: 1、程序区 2、静态存贮区 3、动态存贮区 数据分别放在静态和动态存贮区中。 全局变量分配在静态区,程序开始执行时分配,执行完毕时释放。 动态存贮区存放: (1)函数形式参数 (2)自动变量 (3)函数调用时现场保护和返回地址等
局部变量是在函数调用时分配,要函数结束时释放这些空间 C中每一个变量和函数有两个属性:数据类型和存贮类别(在内存中的存贮方法) 存贮方法分为两类:静态和动态 具体可分为四种: (1)自动的auto (2) 静态的static (3)寄存器的register(4)外部的extern
8.9.2 auto变量 函数中的局部变量,不专门声明为static,则是动态分配存贮空间。函数中的形参和在函数中定义的变量。 函数被调用时才分配存贮空间,函数调用结束时释放空间。 int f(int a) { auto int b,c=3; :} 注:auto可省auto int b,c=3;相当于int b,c=3;
8.9.3 static声明局部变量 有时希望函数中的局部变量的值在函数调用结束后不消失保留,所占单元不释放。 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)); }
如果一个变量使用频繁,则将它放在寄存器中。 int fac(int n) { register int i,f=1; …..} 存 数 取 数 如果一个变量使用频繁,则将它放在寄存器中。 int fac(int n) { register int i,f=1; …..} 只有局部自动变量和形式参数可作为register变量 内 存 运 算 器
8.9.5 用extern声明外部变量 外部变量(即全局变量)是在函数之外定义的,它的作用域是从定义处起至本程序文件的末尾。编译时它放在静态存储区。 1、在一个文件内声明外部变量 2、在多个文件和程序中声明外部变量
8.9.6 用static声明外部变量 有时在程序设计中希望某些外部变量只限于被本文件引用,定义外部变量时加一个static声明 在不同文件中使用相同的外部变量名而不相互干扰,利于程序的模块化。
8.9.7 关于变量的声明和定义 定义:是需要申请存贮空间的 声明:对变量存贮方式进一步说明 如用static来声明一个变量作用 1、对局部变量用static来声明是指在整个程序执行期间始终存在 2、 全局变量用static来声明,该变量只在本文件模块中有效
8.9.8 存贮类别小结 数据定义需要指定两种属性:数据类型和存贮类别 1、从作用域分:局部变量和全局变量 2、从生存期分:动态和静态,静态是程序整个运行时间存在;动态是调用函数时临时分配单元 3、从存放的位置: 内存(静态存贮区,动态存贮区) 寄存器
§8.10 内部函数和外部函数 函数本质上是全局的,可以被其它函数调用,但可以指定函数不被其它文件调用。据函数能否被其它源文件调用,将函数分为内部函数和外部函数 8.10.1 内部函数(静态函数) 在函数定义时,其首部加:static 如: static int fun(int a,int b)
8.10.2 外部函数 1、在函数定义时,其首部加:extern 如: extern int fun(int a,int b) 在定义函数时,省略extern,则隐含是外部函数 2、在需要调用此函数的文件中,用extern声明所用的函数是外部函数
§8.11 如何运行一个多文件程序 一个程序由多个文件构成: 1、用Turbo C集成环境 2、用#include命令
重点内容 一个程序由多个函数构成,一个程序可由多个源文件组成 函数定义,调用(嵌套,递归) 变量的作用域 变量的生存期 作业: 8.1 8.11 8.13
第九章 预处理命令 C源程序中加入一些“预处理命令”,它不是C语言的组成部分,但它能提高编程效率。 有三种: 1、宏定义 2、文件包含 第九章 预处理命令 C源程序中加入一些“预处理命令”,它不是C语言的组成部分,但它能提高编程效率。 有三种: 1、宏定义 2、文件包含 3、条件编译