• 1. 第6章 函数
  • 2. 6.1 函数的定义和调用 函数是C程序的构造块。每个函数本质上是一个自带声明和语句的小程序。可以利用函数把程序划分成小块,这样便于人们理解和修改程序。此外,函数可以复用:一个函数最初可能是某个程序的一部分,但可以将其用于其他程序中。 ⑴ 从程序设计方法看C函数:它是实现模块化程序设计的语法元素。 ⑵ 从C语言中程序的组成方式看C函数:它是程序的基本组成单位。
  • 3. C程序由主函数(main函数)和若干个子函数构成; 主函数调用子函数; 子函数在定义时是并列的; 子函数可相互调用,也可被多次调用。mainabcdefghhieg从用户使用的角度看,函数有两种 : ①标准函数(库函数); ②用户自己定义的函数
  • 4. 6.2 函数基础 在C语言中,程序从主函数main开始执行,到main函数结束处终止执行。其他函数在由main函数、其他的函数或自身进行调用后方能执行。 自定义函数包括三个部分: 函数定义:定义函数的功能。 函数调用:执行函数。调用一个函数时,程序跳转到被调用函数的第一句开始执行,执行到被调用函数的最后一句,然后程序返回到调用该函数处。 函数说明:通知编译器该函数已经定义过了。库函数一般不需要写函数说明,用户自定义函数,特别是如果函数定义的位置在调用之后,必须在调用前对函数进行函数说明。
  • 5. 6.3 函数的定义函数定义的形式 函数返回值的数据类型说明 函数名称(带类型说明的参数表) { 函数内部数据说明; 语句; } 函数返回值的数据类型说明 函数名称(不带类型说明的参数表) 参数的类型说明; { 函数内部数据说明; 语句; }
  • 6. 6.3 函数的定义float average( ) float a; float b; { returen (a+b)/2; }float average(float a, float b) { returen (a+b)/2; }
  • 7. 函数的参数 形式参数:在定义函数时函数名后面括弧中的变量名,简称形参。 实际参数:在调用函数时函数名后面括弧中的表达式,简称实参。 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); }形参表实参表
  • 8. 函数的返回值 通过函数调用使主调函数得到一个确定的值,称为函数的返回值。每个函数都要有一个返回值。如果忽略返回值类型,某些编译系统默认为int类型。 函数的返回值是通过 return语句 获得的。 return语句将被调函数的一个确定的值带回主调函数中去。return语句的一般格式是 return 表达式 或者 return (表达式) return 后面的值可以是一个表达式,例如: z=x>y? x:y; return(z); return (x>y? x:y);
  • 9. 如果函数不需要返回值,可以定义返回值类型为void。此类函数一般用于完成某项特定的处理任务。一般可以不用写return语句。 一个函数中可以有多个return语句,但是一次函数执行只能执行其中的一个,即只能返回一个值 。当执行到某个return语句时,则终止函数执行,并带回函数值。 如果函数类型和return语句的类型不一致,以函数定义的类型为准。对数值型数据,可以自动进行类型转换,既函数类型决定返回值的类型。 若函数体内没有return语句,就一直将函数执行,再返回调用函数,有一个不确定的值带回。 return后面可以无“返回值”(即 return ;),则该return语句只起到 终止函数执行,返回主调函数的作用。
  • 10. 6.4 函数调用对于不带返回值的函数的调用一般作为独立的语句出现,调用格式是: 函数名(参数表); 参数表是用逗号分隔的若干表达式,每个表达式都是函数的实际参数。表达式中可以包含常量和变量。注意:实际参数前不能加数据类型说明。 带返回值函数的调用一般是出现在表达式里。 如:变量名=函数名(参数表); 调用函数时,不同的编译系统对参数表达式的计算顺序有可能不同。 函数调用时,实际参数的个数和数据类型必须与函数定义的形式参数的个数、数据类型匹配,否则可能出错。
  • 11. 函数是一个一个并列的、独立的模块,并通过调用与被调用相关联。在一个函数定义中不可以定义另一个函数,但是允许在一个函数中调用另一个函数,即函数定义不允许嵌套,但函数调用允许嵌套。
  • 12. 调用函数的一般执行过程 首先计算实参表达式的值,分别传递给对应的形参 将控制传给被调函数,开始执行被调函数 被调用函数保存调用函数的执行现场 执行被调用函数体,遇到调用其他函数,重复执行1,调用其他函数 遇到return语句或函数体的结束括号‘}’,函数执行结束。控制返回调用函数,从调用语句的下一条语句继续执行调用函数。
  • 13. 上例中形参与实参、函数名与返回值之间的关系: c=max(a, b); ----------------------- 实参:在运行时 把函数的 max(int x,int y) 把值传给函数 结果赋给 { ……… 函数名 returu (z); 形参:通知系统 } 要预留内存位置
  • 14. ***例:无返回型函数的调用 *************** #include”stdio.h” void printstar() { printf("*******************************\n"); } void print_message() { printf(" Tsinghua University\n"); } main() { printstar (); print_message(); printstar(); }
  • 15. /*** 例*** 函数返回值为定义的函数类型,*/ /* 由于已定义为int型,输入实数就出错 **/ int max(int x,int y) { int z; z=x>y?x:y; return (z); } main() { int a,b,c,d; float e; printf("Please input three datas\n"); scanf("%d,%d,%d",&a,&b,&c); d=max(a,max(b,c)); e=max(a,max(b,c)); printf("The largest data inputed is %d\n",d); printf("The largest data inputed is %f\n",e); }既是参数,又是函数
  • 16. 6.5 函数说明调用函数前应该对被调用函数进行说明,称为函数说明,类似于变量使用前要进行变量说明。函数说明的目的是使编译系统知道被调用的函数参数和返回值的类型,从而可以在调用函数中按照相应的类型作出相应的处理。 函数说明的一般格式为: 函数返回值的数据类型说明符 被调用函数名(形参表); 括号内的形参表可以给出形参的数据类型和形参名,也可以只给出类型名,便于编译系统进行检错。
  • 17. 6.5 函数说明函数返回值的数据类型说明符 被调用函数名(形参表); main() { float add(float x,float y); float a, b, c; scanf(“ %f, %f”, &a, &b); c=add(a,b) ; printf(“ sum is %f ”, c ); }/*定义add函数*/ float add (float x,float y) { float z; z=x+y; return (z); } 对被调用函数的说明作为表达式被调用
  • 18. 函数说明语句的位置应该在函数定义之前,可以置于函数外,也可以放在函数内。 函数说明语句如果置于函数外,则在说明语句之后的所有函数都可以调用所说明的函数;函数说明语句如果在某个函数内,则只有这个函数可以调用被说明的函数。 在下面两种情况下可以不进行函数说明: 被调用函数的函数定义出现在调用它的函数之前。 对C编译提供的库函数的调用不需要再做函数说明,但必须把该函数的头文件用include命令包含在源程序的最前面。
  • 19. 6.6 参数传递形参调用前不占内存单元,调用时占用, 调用后 释放; 形参是函数的内部变量,只在函数内部才有意义; 对每个形参必须指明其名字和数据类型; 实参可以是常量、变量或表达式,并且必须有确定的值; 实参个数、类型必须与对应的形参一致; 实参对形参的数据传递是值传递,即单向传递, 只由实参传递给形参,反之不可。调用结束后,只有形参单元被释放,实参单元中的值不变。
  • 20. 例:#include“stdio.h” void swap_fail(int a,int b); void main() { int i=2,j=4; printf(“before call i=%d,j=%d”,i,j); swap_fail(i,j); printf(“called i=%d,j=%d”,i,j); } void swap_fail(int x,int y) { int temp; printf(“ before change x=%d,y=%d”,x,y); temp=x;x=y;y=temp; printf(“ changed x=%d,y=%d”,x,y); } 运行结果: before call i=2,j=4 before change x=2,y=4 changed x=4,y=2 called i=2,j=42i4j2x4y4x2y
  • 21. #include“stdio.h” void swap_fail(int a,int b); void main() { int i=2,j=4; printf(“before call i=%d,j=%d”,i,j); swap_fail( &i, &j); printf(“called i=%d,j=%d”,i,j); } void swap_fail( int *x, int *y) { int temp; temp=*x; *x=*y; *y=temp; } 2i4jxy运行结果: before call i=2,j=4 called i=4,j=2调用函数时,将i和j的地址赋值给形参x和y形参数据类型是指针变量,存储整型变量的地址i的地址xj的地址y4i2jj的地址xi的地址y
  • 22. 使用指针类型数据作为形参如果使用指针类型作为函数的形式参数,就可以实现被调用函数对指针所指变量的修改。 通过传递指针变量的值,调用函数与被调用函数可以操作相同的存储单元。 注意形式参数和实际参数的写法,若没有把形式参数说明为指针类型,或者实际参数不是地址值,都会使被调用函数出现错误。 从效果来看,形式参数使用指针变量使得调用函数与被调用函数共享相同的存储单元,使被调用函数在实际上可以传递多个值给调用者。见P142例6.9。
  • 23. 6.7 递归调用
  • 24. 6.8 变量的存储类别对变量在使用前必须先说明其数据类型,除此以外还可以说明变量的存储类型。不同的存储类型可以确定一个变量的作用域和生存期。 变量的作用域是指变量的作用范围,包括全局有效、局部有效和复合语句内有效 变量的生存期是指变量作用时间的长短,分为程序期、函数期和复合语句期 变量的存储类别: 自动变量(auto) 寄存器变量(register) 静态变量(static) 外部变量(extern)
  • 25. 程序区静态存储区动态存储区存储其中的变量从程序开始执行到程序结束这段期间,始终拥有自己已经分配了的固定的存储空间存储其中的变量是在程序运行期间根据需要随时进行动态分配的存储空间外部变量静态变量自动变量函数的形式参数
  • 26. 6.8.1 自动变量和外部变量定义方式 自动变量在函数体内或分程序内的首部定义。关键字auto可以省略,所有未经说明的,在函数体和分程序内部定义的变量以及函数的形式参数都被视为自动变量 外部变量在函数外部定义,外部变量也称为全局变量。 作用域 自动变量的作用域是在定义它的分程序中,并且是在定义后才能使用,是局部变量。 外部变量可以为各函数所共享,从定义处到该源程序结束处都有效,是全局变量。 函数之间的信息传递不但可以通过实参向形参单向传递,还可以使用外部变量进行信息传递,而且传递是双向的。也就是说,各个函数对外部变量的操作都是有效的。
  • 27. 注意: 各函数之间、各并列的分程序中的同名变量代表不同的变量,互不冲突 如果嵌套的分程序中有同名变量,则内层变量将阻塞对外层变量的访问。内层变量与外层变量各占一个存储单元。 自动变量将阻断对同名外部变量的访问。它们也占用不同的存储单元。 生存期 自动变量:存储在动态存储区中,动态分配存储空间,函数开始时分配空间;在函数调用结束时系统自动回收空间 外部变量:存放在静态存储区中,在程序开始时被分配固定的存储区,整个程序执行完毕时释放。 初始化 自动变量不能自动进行初始化 外部变量在定义时会自动进行初始化,只能进行一次。
  • 28. 外部变量的说明 如果想让在外部变量定义之前的函数也能正确使用它,需要对外部变量进行说明,语法格式是: extern 数据类型 变量名; 其中数据类型可以省略 变量定义和变量说明的区别 变量说明是说明变量的特性(存储长度、类型等) 变量定义除了说明变量的特性,还要分配存储空间。 一个外部变量只能被定义一次,否则不能通过编译;而对同一个外部变量的说明可以进行多次。 可以在不同的源文件中使用同一个外部变量。当源程序分布在几个源文件时,对外部变量也只能定义一次,其他文件若想使用该变量,应在那些文件中包含extern说明,一般写在文件开始处,从而使得该文件中的所有函数都能使用该变量。
  • 29. 使用外部变量的原因和副作用 使用外部变量的第一个原因是便于初始化,自动变量在定义后的值是不确定值,系统不负责初始化,而对定义的外部变量会自动的初始化为0. 第二个好处是方便函数之间进行数据交流,解决了函数返回多个值的问题 第三个好处是外部变量作用域广。 但是模块化程序设计思想并不赞成大量使用外部变量,因为模块化程序设计思想强调信息隐藏的概念,函数之间应尽量通过参数传递进行交流,而外部变量会增加许多数据间的联系,破坏了程序结构,为程序修改带来麻烦,使函数的通用性和可移植性降低。
  • 30. 6.8.2 静态变量定义方式 static 类型 变量名; 根据定义的位置分为内部静态变量和外部静态变量 作用域 内部静态变量仅在定义它的函数和分程序内有效,与自动变量相同 外部静态变量作用域是定义它的同一源文件中,其它源文件不能访问。外部静态变量的名称与其他源文件内的同名变量无关。 生存期 静态变量存储在静态存储区,无论内部还是外部静态变量都被永久存储。即使退出函数的执行,该函数内部的静态变量也仍然不被系统释放。
  • 31. 初始化 静态变量若在定义时赋初值,则程序运行中仅在第一次调用时赋初值,第二次调用不再赋初值,而是使用上一次调用的值。 例:#include“stdio.h” static int y=10; void main() { int i; void decr(); for(i=1;i<=3;i++) decr(); }void decr() { static int x=10; x--; y--; printf(“\n x=%d y=%d”,x,y); )运行结果: x=9 y=9 x=8 y=8 x=7 y=7
  • 32. 6.8.3 寄存器变量对于使用频繁的变量,可以存放在计算机的寄存器中,以加快程序的运行速度。 定义方式 在函数内部定义或作为函数的形式参数,格式为 register 类型 变量名; 作用域、生存期和初始化与自动变量基本相同 限制 寄存器变量的实现与硬件配置有关,只有很少的变量可以保存在寄存器中 register说明只适用于自动变量和函数的形参 不允许取寄存器变量的地址