1、第四章 函 数,本章主要内容,函数概述 应用函数的编程(函数之间的数据传递) 值传递 地址传递 变量的作用域与变量的存储类型 auto变量、static 变量 局部变量、全局变量,C程序是由函数组成的(可以多个) 有且只有一个main() 其它函数 库函数 自定义函数 函数是结构化程序设计的基本模块。 具有某种功能的代码段 通用性强,可以被重复使用。 易于维护,可以独立设计,单独调试。 划分函数时,考虑功能单一完整。,4.1 函数概述,结构化程序设计思想,结构化程序设计:也称为,自顶向下、逐步细化。 将复杂的系统划分为功能相对独立的子系统的组合, 对各子系统再进行如此划分,每个模块, 在C语言
2、中表现为函数。,C中函数分类:,用户角度分:系统函数和用户函数; 参数角度分:有参函数和无参函数; 返回值角度分:有返回值函数和无返回值函数。,标准库函数的调用,Turbo C中提供了300多条标准库函数, 都放在函数库(.lib)中,函数的说明按功能 分类放在标题文件(.h,也称头文件)中。,添加一行: #include “头文件名称” 或者 #include ,函数例子: #include void PrintStar() printf(“*n“); void main() PrintStar (); printf(“Hello Everybody!n“); PrintStar (); ,
3、使用函数组装程序好处:代码重用程序模块化维护方便,4.2 函数的定义与调用,4.2.1 函数的定义,#include void main(void ) int a,b,c;scanf ( “%d,%d” , ,主函数,函数体,函数的类型,返回值的类型。,函数名,形式参数说明表。,定义!,调用!,函数头,函数的定义格式:,返回值类型 函数名(形式参数列表) 声明部分功能语句返回语句 ,/*函数头*/,/*函数体*/,(1)函数头 返回值类型 函数名 形参列表:0个或多个,逗号分隔,给出参数名称及类型。 (2)函数体代码段,实现功能。一对 括起来。 返回语句return : 有返回值:return
4、 (表达式);或return 表达式;返回 无返回值:可无return语句,末尾的右“”作为函数的返回。,4.2.2函数的调用,1、函数调用形式: 函数名(实参列表) ;,单独语句形式: max (a ,b );printf(“fdsa”); 表达式形式: c= max (a ,b ); 作为函数参数: printf (“%d” , max (a ,b ) ;,1、函数调用形式: 函数名(实参列表) ;,2、函数返回值:被调函数执行后返给主调函数的值。,return(表达式);或 return 表达式; 可有多条return语句,遇到一条return时,程序即返回。 函数没有返回值,可以没有r
5、eturn语句,利用 “” 返回。,3、函数的声明: 调用之前,对被调函数要进行声明。如: int max (int x ,int y );,#include long Multiply(int , int ); void main() int num1,num2;long result;scanf(“%d%d”, ,【例4-2】,4.3 函数间的数据传递,函数在调用时,主调函数和被调函数之间一般存在着数据的传递。 传递有两种方式:值传递和地址传递,将值或地址传递给被调函数;,将被调函数的结果返回给调用函数。,主调函数,被调函数,向被调函数传递数据(值或地址)。,将结果返回给主调用函数。,传值
6、调用方式:形参是普通变量-值参,(1)形参是普通变量:必须传同类型的数值形参及被调函数内的变量只有在被调用时才分配内存单元,此时将实参的值初始化给相应的形参。形参与实参完全脱离了关系,形参的值不影响实参。调用结束时,形参所占据的内存单元被释放。(2)实参必须有确定的值,以便把这些值传递给形参。(3)实参与形参在数量、位序和类型上必须一致。,【例4-5】观察程序运行结果。#include void swap (int , int ) ; void main (void ) int a,b ;a=10;b=20 ;printf ( “调用前:na= %d,b=%d n ”,a,b ) ;swap
7、(a,b);printf ( “调用后:na= %d,b=%d n ”,a,b ) ; void swap (int x ,int y ) int z ;z=x; x=y; y=z ; ,指针变量作形参,必须传同类型的地址(指针)数据。 此时在主调函数和被调函数之间可以实现信息的“双向传递”。,4.3.2 传地址调用方式,例4-6 /*函数参数为指针变量*/ void swap (int *x, int *y) ; void main ( ) int a,b ;a=10;b=20 ;printf ( “调用前:na= %d,b=%d n ”,a,b ) ;swap ( ,2. 形参为数组类型,
8、实参为数组名,#include void fun(int b ); main ( ) int a10,i;for(i=0;imax) max=bi;k=i;max=b0;b0=bk;bk=max; ,例: 编写函数,将数组中的最大值与第一个数交换。要求在main()中进行数据的输入输出,,【例4-7】输入5个数到数组中,求这5个数的平均值。求平均值编写函数实现,并将该值作为返回值。,#include #define N 5 float aver( int data, int) ; void main (void) int i, arrayN, av;for(i=0;iN;i+) scanf(“
9、%d“, ,【例4-8】输入10个数到数组中,再输入一实数,判断该数在数组中是否存在?如果存在,输出该数在数组中下标;如果不存在,输出-1。查找编写函数实现。,#include int search(float a,float x); void main( ) int i,position;float a10,x;printf(“输入10个实数:n“);for(i=0;i10;i+) scanf(“%f“, ,4.4 函数的嵌套调用和递归调用,C中不允许作嵌套的函数定义,各函数之间都是平等和独立的。 C允许在一个函数的定义中出现对另一个函数的调用。,【例4-9】函数嵌套调用的例子。 #incl
10、ude float fun2(float x); float fun1(float x); void main() float a;scanf(“%f“, ,【例4-10】使用函数的嵌套调用编程,计算S。,void main( ) int N,K;scanf(“%d%d“, ,分析(1)求累加和编一函数fsum();(2)累加的每一项,再编一函数fexp()(3)循环中,进行累加时,调用fexp()求出该项,double fsum(int n,int k) double sum=0; int i;for(i=0;in;i+)sum+=fexp(i,k);return sum; ,double
11、fexp(int n,int k) double power=1; int i;for(i=0;ik;i+)power*=n;return power; ,4.4.2 函数的递归调用,含义:一个函数在它的函数体内直接或间接地调用它自身称为递归调用。 用递归法解决的问题,应该符合以下2个条件: 边界问题(也称基本问题)有直接解。 原问题能够转化成一个规模较小的新问题,新问题的求解方法与原问题相同。,【例4-11】计算n! 。 使用递归函数实现。,long fac(int n) if(n=1) return(1);elsereturn n*fun(n-1); ,void main() int n;
12、scanf(“%d”, ,【例5- 】汉诺塔问题(Hanoi),问题的提出:相传是在古印度圣庙中的一种游戏。在一块铜板装置上,有三根杆A、B、C,在A杆自下而上、由大到。小放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子; 移动过程中三根杆上始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。,4.5 变量的作用域与存储类型,C程序中变量的使用规则是:先定义后使用。定义形式中:类型: 表明变量占用的内存空间的大小;位置(函数内与外): 表明变量在程序中的作用域。一般,还要说明其存储类型,说明变量的存储方式。不同的存
13、储方式影响变量在内存中存在的时间。,4.5.1 局部变量与全局变量,局部变量:在函数内部或复合语句内部定义的变量(包括函数的形参)。 不同函数内的局部变量之间没有关系,同名也互不影响。 局部变量同名时,当前的局部变量有效。,【例4-12】函数的局部变量应用举例。,void func1(int a, int b) int x; printf(“func1 x :%x”, ,void func2() int x;printf(“func2 x :%x”, ,void main() int x, y; func1(3,4); func2(); printf(“main x DS:%x”, ,【例4-
14、13】复合语句中的局部变量举例。,void main() int i,j; i=2; j=3; int i; i=5; printf(“Inter i = %d,j=%dn“,i,j); printf(“Outer i = %d,j=%dn“,i,j); ,2. 全局变量,在函数体外部定义的变量称为全局变量。 局部变量属于所在的函数,而全局变量属于所有的函数,可以被当前文件的其他函数使用。 全局变量和局部变量同名时, 当前的局部变量有效,全局变量被屏蔽。 一般来说,在较大的程序中要避免出现全局变量和局部变量同名的情况。,【例4-14】全局变量应用举例。,int qNum=10; void ma
15、in() printf(“%dn“, qNum);func1();printf(“%dn“, qNum);func2(); printf(“%dn“, qNum); void func1() qNum =1; void func2() qNum =2; ,全局变量的说明: extern 数据类型 全局变量名;,#include void func1() extern int sum; /声明sum=1; int sum=0; void main() func1();printf(“%dn“,sum); ,全局变量的优缺点:,使用全局变量的优点减少实参和形参数据传递带来的时间消耗。 使用全局变量
16、的缺点: 全局变量保存在静态存贮区,编译时为其分配空间,整个程序结束才释放该空间。因此全局变量的生存期比较长,过多的全局变量占用较多的内存单元。 全局变量破坏了函数的封装性能。 全局变量使函数的代码可读性降低。,4.5.2 变量的存储类型,动态存贮区和静态存贮区 程序运行后,内存中可供程序使用的存贮空间分为三个部分。 代码区存放:程序的二进制代码。 静态存贮区和动态存贮区:存放程序相关的数据。,静态存贮区里:全局变量、静态变量。 动态存贮区里:函数内定义的变量、形参。特点: 调用时,为变量动态分配内存空间; 调用结束,释放该变量的内存空间。 变量的存贮类别有三种: auto、static、re
17、gister。, auto存储类型(关键字可以省略),在默认情况下,所有的局部变量(包括形参)都是auto变量,#include void main(void) auto int a,b;scanf(“%d,%d”, ,a b 的 作 用 域,t 的 作 用 域。,生存期: 变量都拥有自己的内存空间。从申请-被释放, 这段时间称为变量的生存期。, register存储类型,其作用域、生存期与auto相同, 差别在于,如果CPU内部的寄存 器空闲,则使用寄存器作为变量 的存储单元,以提高速度。主要是循环变量,整形和字符型。, static(静态)存储类型,作用域:函数内定义的局部静态变量,在该函
18、数之外不可见。,生存期:从编译-整个程序结束。位于静态存贮区,在函数调用结束后,值仍然存在,并影响到下一次调用的过程。,void row ( ) ; void main ( ) int i ;for (i=1 ; i=9 ; i+ )row ( ) ; void row ( ) static int a=1 ;int i ;for (i=1 ; i=9 ; i+)printf ( “%5d” , a*i ) ;printf ( “ n ”) ;a+ ; ,a的作用域,生存期从编译开始到程序结束。,【例4-15】 static局部变量应用举例。,#include int f( int x) in
19、t a=10;static int stc=1;stc = stc +x+a;return stc; void main() printf(“%dn“,f(1);printf(“%dn“, f(1); ,关于static局部变量提出几点注意:,存放在静态区,生存期比较长,从编译开始它就存在,整个程序结束后才被释放。 从作用域来看,它是一种局部变量,属于当前的函数;即,当前函数内可见,其他函数 不可见。 对初值的处理的不同。 auto变量定义时如果没有初始化,那么它是一个随机值; static局部变量如果没有初始化,编译器自动将其初始化为 值 0 或 空字符。 在程序中尽量不用或少用static
20、局部变量。原因主要有:static局部变量的生存期长,比较浪费内存;使用static局部变量会导致函数的多次调用之间发生联系,使代码的可读性降低。,4. static 型全局变量,C程序可以由多个源文件组成。 一个文件中定义的全局变量,其他的文件中可以使用(用extern方式声明后)。 如果限制本文件中的全局变量不能被其他文件使用,可以在全局变量的定义前加关键字static。 static型全局变量作用域:本文件内。,【举例】static全局变量应用。,/文件f1.c里的代码如下: #include #include static int gs=0; void func(intx); void
21、 main() func (3);printf(“%d“, gs); ,/文件f2.c里的代码如下: extern int gs; void func (int x) gs+=x; ,4.6 外部函数和内部函数,当一个程序由多个源文件组成时, 可以指定一个文件中的函数能被其他文件调用; 也可以指定该函数只能被本文件使用; 从这个意义上,函数可以分为外部函数和内部函数。,4.6.1 外 部 函 数,一个函数如果可以被其他源文件调用,称为外部函数。定义外部函数时,在函数头最左面加关键字extern,表明该函数是一个外部函数省略extern关键字,则系统默认为外部函数,extern int max(
22、int a,int b) return ab?a:b; ,文件f1.c里的代码如下: extern void printstar(); /* extern 可以省略不写*/ void main() printstar (); 文件f2.c里的代码如下: void printstar () printf(“*n“); 这里f1.c的main函数调用了f2.c的外部函数printstar。,4.6.2 内部函数,如果一个函数只能被本文件中的函数调用,该函数就称为内部函数, 定义内部函数时,在函数最左面加关键字static,static int max(int a,int b) return ab?
23、a:b; ,文件f1.c 里的代码: void func(); static void printstar () printf(“printstar in f1.c.n“); void main() printstar (); func(); ,文件f2.c里的代码:static void printstar () printf(“printstar in f2.c.n“); void func() printstar (); ,4.7.2 在VC+下多文件程序的运行,在VC+环境下,编译和运行由多个源文件组成的程序操作如下: 方法一,新建一个工程,向其中添加已有的源文件。方法二,新建一个工程
24、,在其中创建多个源文件 。,补充 编译预处理,C 语言还有一类编译预处理语句。作用是在编译时对程序作一定的处理,满足特定的处理要求。,编译预处理语句的语法形式:,#关键词 参数表, 宏定义预处理,1.1不带参数的宏定义,作用:定义常量名,提高程序的可读性,便于修改。,格式: #define 宏名 字符串,#define PI 3.1415926 void main(void) float r;scanf(“%f ”, ,编译时用字符串替代宏名。,3.1415926,说明:,宏名一般用大写; 编译时用字符串无条件替代宏名;,#define PI 3.1415926; PI*r*r,3.14159
25、26*r*r;,宏名的有效范围,从定义到程序尾。也可以通过#undef修改 定义范围。,#define G 9.8 void main(void ) #undef G,G的范围,可以在宏定义的字符串中使用已定义的宏名。,” ”字符串中的宏名不替换。,#define R 3.0 #define PI 3.14159 #define S PI*R*R,printf(“S=%f”,S);,不替换。,1.2 带参数的宏定义,格式: #define 宏名(参数表) 含参数的字符串,#define S( a , b ) a*b area=S( 4 , 3 );,替换过程:,将实际参数替换宏定义的参数。,替
26、换字符串中的参数。,替换整个宏。,4*3,带参的宏与函数的区别:,带参宏不分配内存单元,不返回值,只是在编译时按规则替换。,关于带参宏的说明:,要严格按格式书写,否则会造成错误。,#define S (a,b) a*b S(3,4)被替换成: (a,b) a*b(3,4),参数有可能用到表达式时,参数字符要加()。,#define S(r) PI*r*r S(a+b)替换为:PI*a+b*a+b,#define S(r) PI*(r)*(r) S(a+b)替换为:PI*(a+b)*(a+b), 文件包含,格式:#include #include ”被包含的文件名”,表示编译系统定义路径; “
27、”表示用户指明路径。,作用:将指定的文件的内容和当前文件一起编译。,说明:,(1)一个#include只能包含一个文件。,(2)一般将宏定义及函数的原型声明放在包含文件中。,(3)被包含的文件一般扩展名为.h,称为头文件。, 条件编译,C可通过条件控制,让编译系统编译不同的程序段。,条件编译有三种形式:,#ifdef 标识符program seg1 #elseprogram seg2 #endif,#ifndef 标识符program seg1 #elseprogram seg2 #endif,1,2,条件?,满足,编译程序段1,不满足,编译程序段2,举例:通过条件编译选择求最大值或最小值,#include void main(void) int a,b;scanf(“%d,%d”, #endif ,#define MAX,#include void main(void) int a,b;scanf(“%d,%d”, ,