C语言程序设计第4章函数_第1页
C语言程序设计第4章函数_第2页
C语言程序设计第4章函数_第3页
C语言程序设计第4章函数_第4页
C语言程序设计第4章函数_第5页
已阅读5页,还剩67页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第4章函数4.1函数概述

这个程序由三个函数构成:main()、S()和L(),若按照模块化的观点看,这个程序由三个模块构成,每个模块由一个函数实现。

/*求圆面积函数*/

floatS(floatr){floatres;res=3.14*r*r;returnres;}/*求圆周长函数*/

floatL(floatr){floatres;res=2*3.14*r;returnres;}

main(){floatr,area,circle;

printf(“Pleaseinputradius:”);

scanf(“%f”,&r);area=S(r);circle=L(r);

printf(“r=%f,s=%f,l=%f\n”,r,area,circle);}例4.1main()、S()和L()的关系图main()S()L()4.2函数的定义从用户使用的角度看,可将函数分为两种:标准函数(即系统库函数)和用户自定义函数。函数的定义格式:

类型标识符

函数名([<形式参数表>])

{

函数体

}对于函数的定义格式要注意以下几点:

1.“类型标识符”用来说明函数返回值的类型。当函数的返回值为整型(int)时,也可以不加“类型标识符”,因为系统默认的返回类型是整型。

2.“函数名”是函数的存在标识,函数名可以是任何合法字符的组合,但注意不要与系统关键字同名。

3.“<形式参数表>”用于指明调用函数时,传递给函数的数据个数和类型。传递给函数的参数可以有多个,也可以没有。若有多个参数,则相邻参数之间要用逗号“,”间隔;若没有参数,则<形式参数表>为空,但要保留“函数名”后的一对括弧“()”。

4.函数若有多个参数,则必须在<形式参数表>中对每一个参数进行类型声明,格式为:

“类型名1形式参数名1,类型名2形式参数名2,......,类型名n形式参数名n”。

5.“函数体”就是函数的定义主体,包括变量声明、程序语句等。例4.2:定义一个求和函数来说明函数的定义格式

/*定义了两个浮点型的形参x和y,函数返回类型为浮点型*/

floatsum(floatx,floaty){floatz;/*定义一浮点型变量z*/z=x+y; return(z);/*将z的值作为函数sum的结果*/}4.3函数的调用与返回值

函数定义好之后,要能够被调用(即执行函数),以完成该函数的功能。函数的调用格式:

函数名([<实际参数表>])

在函数的定义格式中,函数头为:

类型标识符函数名([<形式参数表>])

这里要注意的是“实际参数表”和“形式参数表”的区别。4.3.1实参与形参的区分

“形参表”是一种形式上的定义,或者说是一种“接口”描述,通过这个接口,调用者就知道应该给函数传递什么样的数据。

调用者在调用某函数时,通过接口传给函数的实际数据,叫做“实参”。通过下面的图,能较好地理解形参和实参的关系:

#include<stdio.h>min(intx,inty){

intz;z=x<y?x:y;return(z);}

main(){inta,b,c;

scanf(“%d,%d”,&a,&b);c=min(a,b);

printf(“Minis%d\n”,c);}例4.3:通过调用求最小值函数min(),来说明函数调用时形参和实参的关系

程序运行结果如下:

6,9↙

Minis64.3.2函数的调用函数的调用方式有以下三种:

1.函数语句

把函数调用作为一个语句出现。这种调用方式无需函数有返回值,只要它完成某项功能。

2.函数表达式

当调用的函数有返回值时,有时会以表达式的方式调用该函数。

3.函数参数

函数调用作为一个函数的实参,这是实际当中用得较多的一种方式。

4.3.3对被调用函数的声明

一个函数要获得对另一个函数的调用成功,除了参数要正确传递以外,还必须具备以下一些条件:1.如果调用系统库函数,应在程序文件开头用#include命令将包含库函数定义信息的“头文件”嵌入到文件中来。

2.如果在程序中调用用户自定义函数,一般还应在程序中对被调函数进行“声明”。

#include<stdio.h>main(){floatsum(floatx,floaty);

floata,b,c;

scanf(“%f,%f”,&a,&b);c=sum(a,b);

printf(“Sumis%f\n”,c);}floatsum(floatx,floaty){floatz;z=x+y;return(z);}

例4.4:该例说明如何对被调用函数进行声明程序运行结果如下:

3.6,7.5↙

Sumis11.100000

函数原型有两种写法:

1.类型标识符

函数名(参数类型1,参数类型2…….)

2.类型标识符函数名(参数类型1参数名1,参数类型2参数名2…….)

使用函数原型还要注意以下两点:

1.如果被调函数的定义出现在主调函数之前,则主调函数中可以不加声明。

2.如果在所有函数定义之前已做了函数声明,则各主调函数不必再对其进行声明。4.3.4函数的返回语句与返回值

在执行被调用函数时,如果要将控制或被调用函数的值返回给调用函数,则需要使用返回语句。返回语句有三种格式:

格式1:return;

格式2:return(表达式);

格式3:return表达式;使用返回语句要注意以下几点:

1.如果被调函数中没有返回语句return,则执行完该函数体的最后一条语句才返回。

2.如果被调函数有返回值,则必须使用格式2或格式3的返回语句,且要在函数定义时声明函数的返回类型。如例4.4中定义的sum()函数:

floatsum(floatx,floaty){floatz;z=x+y;return(z);}

#include<stdio.h>

intmax(float,float);/*函数原型声明*/

main(){floata=1.5,b=3.2;

printf(“Maxis%d\n”,max(a,b));}

intmax(floatx,floaty){return(x>y?x:y);}例4.6:该例说明当返回类型与函数类型不一致时如何处理

程序运行结果为:

Maxis3补充说明:如果函数没有返回值,则应给函数加上类型声明符“void”,意为“无返回值”的意思。

一个函数中可以有多条return语句,执行到哪一个,哪一个语句起作用。

4.4函数的参数传递方式

实参与形参的传递方式有两种:值传递方式和地址传递方式。所谓“值”就是某个常量、变量、函数、表达式的计算结果。变量或常量,是无需计算的,因为它们本身就是一个值;函数的值是指它的返回值;表达式的值就是它的计算结果。

值传递方式的特点是:将实参的值传给形参,实参与形参互不影响,在函数内部对形参所做的任何改变不会影响到实参。4.4.1值传递方式

#include<stdio.h>voidf(intx,inty){++x;--y;

printf(“\nx=%d,y=%d”,x,y);}

main(){inta,b;a=b=10;printf(“\na=%d,b=%d”,a,b);f(a,b);/*传值*/printf(“\na=%d,b=%d”,a,b);}例4.9:该例说明值传递方式的特点

程序运行结果为:

a=10,b=10x=11,y=9a=10,b=104.4.2地址传递方式

程序运行时,其中的任何数据(常量或变量),都要占据一定大小的内存单元(如一个整数占两个字节,一个实型数占四个字节),它所占据的内存单元在整个内存空间中的位置就叫“地址”。要获得某个变量的内存地址,C语言提供了一个运算符“&”,只要将该运算符与某个变量连接起来,运算的结果就是该变量的“地址”。如:“&a”,求得的结果就是变量a所分配的内存单元地址。

#include<stdio.h>voidf(int*x,int*y){++(*x);--(*y);

printf(“\nx=%d,y=%d”,*x,*y);}

main(){

inta,b;a=b=10;

printf(“\na=%d,b=%d”,a,b);f(&a,&b);/*传地址*/

printf(“\na=%d,b=%d”,a,b);}

程序运行结果为:

a=10,b=10x=11,y=9a=11,b=94.5函数的嵌套与递归4.5.1函数的嵌套调用

所谓“嵌套调用”就是一个被调函数,在它执行还未结束之前又去调用另一个函数,这种调用关系可以有嵌套多层。关于嵌套调用关系如下图所示:

fun2(inta,intb){intc;c=a*b%3;returnc;}fun1(inta,intb){intc;a+=a;b+=b;c=fun2(a,b);returnc*c;}

main(){intx=2,y=7;

printf(“Theresultis:%d\n”,fun1(x,y));}例4.10:该例介绍函数的嵌套调用

程序运行结果:

Theresultis:44.5.2函数的递归调用

函数的嵌套调用是指某函数调用另一个不同的函数;如果函数调用本身,则称为“递归调用”。函数a直接调用a本身,称“直接递归”,函数a调用函数b,b再调用a,称“间接递归”。

#include<stdio.h>floatfac(intk){if(k==0)return1;elsereturnk*fac(k-1);}main(){

intm;

printf(“m=”);

scanf(“%d”,&m);

printf(“%d!=%f\n”,m,fac(m));}

程序运行结果:

m=3

↙3!=6.000000

例4.12:用递归计算阶乘k!

递归调用图解:4.6变量的作用域

什么是“变量的作用域”?可以打一个这样的比方:每个变量好比一盏灯,它所能照亮的区域就是它的“作用域”,在该区域内的任何地方都能“看到”它,也就能访问到该变量,出了此区域就访问不到了。4.6.1局部变量

局部变量是局部可见的,主要指在函数体内定义的变量。在函数体内定义的变量只有在该函数范围内才能被访问到,在此函数以外是不能访问的。4.6.2全局变量

如果说局部变量是一个只能照射到局部区域的“灯”,那么全局变量就是一盏能照射到整个程序范围的“灯”,因此,程序中的任何地方都能访问到全局变量(绝大多数情况下是这样的)。

什么样的变量才是全局变量呢?简单地说:在函数之外定义的变量就是全局变量(也可称为外部变量),它的作用域为从定义变量开始的位置到程序的结束所覆盖的范围。

#include<stdio.h>intx;fun(inta,intb){

intt;t=a;a=b;b=t;x=a/b;

printf(“theresultis:%d\n”,x);}

main(){

inta=5,b=12;fun(a,b);

printf(“a=%d,b=%d,x=%d\n”,a,b,x);}

程序运行结果:

theresultis:2a=5,b=12,x=2例4.15:该例说明全局变量的使用

4.6.3分程序

在C语言中,一个大的程序可以由多个函数组成,这些函数的定义是并列的。C语言不允许在函数内部再定义函数,即函数定义不能嵌套。

C语言为弥补这一不足,允许使用“分程序”。所谓“分程序”就是用一对花括号“{”和“}”括起来的含有变量声明语句和执行语句的“复合语句”,其形式如下:

{

变量声明语句

……

执行语句序列

……}

程序运行结果:

x=7,y=9,z=3x

=2,y=3,z=44.7变量的生存期上一节从变量的作用域角度将变量分为局部变量和全局变量两种。

另外,变量是有“生存期”的,即任何变量都有一个“创建”、“存在”、“消亡”的过程。变量的生存期取决于它的存储类别。所谓“存储类别”,是指系统为变量分配的具有某种特性的存储区域。存储区域一般分为两种:静态存储区和动态存储区。存放在静态存储区中的变量在程序运行初期就被创建,它们的寿命往往是与程序同步;存放在动态存储区中的变量是临时性的,在程序运行期间随时会被撤销。

程序执行时的存储区域划分:4.7.1自动变量(auto)

在前面所有例子的函数当中定义的变量实际上都是自动变量,只是省略了关键字“auto”。自动变量只是定义在函数中的,当函数被调用时,自动变量临时被创建于动态存储区中,函数执行完毕,自动撤销。声明自动变量的完整格式如下:

auto类型名

变量表;

#include<stdio.h>voidsub(){/*显式声明y为自动变量*/

autointy=3;

printf(“y=%d\n”,y);}main(){/*隐式声明y为自动变量*/

inty=2;sub();

printf(“y=%d\n”,y);}

程序运行结果:

y=3y=2例4.18:该例说明自动变量的性质和用途

4.7.2静态变量(static)“静态变量”与“自动变量”的区别:

先看它们的相同点:二者都是定义在函数内的局部变量,所以,它们的作用域都是在函数内,出了该函数,二者都不能被访问。

不同点:二者的寿命不同。(自动)局部变量是函数执行时才存在,执行完了自动撤销,是动态的。而静态变量存在于静态存储区中,在函数执行之前就存在,寿命是全局的,即与程序同步。

确切地说,在函数中声明的静态变量不论其所在的函数是否被调用,它一直存在。如果其所在的函数被调用,且在执行期间修改了静态变量的值,则在函数执行完后,该值仍存在,并将作为下一次调用该函数的初值。

声明静态变量的格式为:

static类型名

变量表;

#include<stdio.h>sub(){/*声明静态局部变量i,并置初值为1*/

staticinti=1;i=2*i;return(i);}main(){

inti;for(i=1;i<=4;i++)

printf(“\nresult=%d”,sub());}程序运行结果:

result=2result=4result=8result=16例4.19:该例说明静态变量的性质和作用

4.7.3外部变量(extern)

关键字“extern”用来扩展全局变量的作用域,使得以前不能访问它的函数也能访问到它,这种作用域的扩展,也称为作用域的“提升”。

extern的声明格式为:

extern[类型名]变量表;

由于变量表中的变量往往已定义,类型名通常都省略。#include<stdio.h>intmin(inta,intb){return(a<b?a:b);}main(){externX,Y;

printf(“\nmin=%d”,min(X,Y));}

intX=9,Y=-14;

程序运行结果:

min=-14例4.20:该例说明对外部变量的声明和使用

此例中,在file1.c文件中全局变量A在定义时加上了关键字static,声明为静态全局变量,这就限制了它的作用域只局限于文件file1.c中。即使file2.c中加上了对A的访问声明,也不能将A的作用域扩展到file2.c中,因此函数sub()无法访问全局变量A。

4.7.4寄存器变量(register)

一般情况下,程序运行时各变量的值是存放在内存中的,如要对某变量进行访问,由控制器将该变量的值从内存读入运算器中进行运算。为了提高变量的存取速度,C语言允许将变量的值直接放在CPU的寄存器中,这样由于无需到内存中去访问,存取速度就更快,这样的变量就叫“寄存器变量”。C语言中提供了关键字register来声明寄存器变量,声明语句格式如下:

register类型名

变量表;

要注意的是:

1.只有局部变量和形式参数可以定义为寄存器变量,其它变量(如全局变量)不行;

2.由于CPU中的寄存器数量有限,不要定义过多的寄存器变量,多出的变量自动作为自动变量处理;

3.C语言中,寄存器变量仅限于int、char和指针型。

#include<stdio.h>floatfac(intm){registerinti;floatf=1;for(i=1;i<=m;i++)f=f*i;return(f);}main(){inti;for(i=1;i<=10;i++)

printf(“\n%d!=%.0f”,i,fac(i));}

程序运行结果:

1!=12!=23!=64!=245!=1206!=7207!=50408!=403209!=36288010!=3628800例4.23:该例说明寄存器变量的作用

4.8内部函数和外部函数

一个C语言程序在设计阶段往往是由多个源文件构成的,每个源文件都可以包含若干个函数,实际上每个函数(除了主函数main())也有一个存储类别的问题。函数从存储类别的角度划分,可分为“内部函数”和“外部函数”两种。4.8.1内部函数

所谓内部函数,是指只能被本文件中其它函数所调用的函数,其它文件中的函数不能调用之。定义内部函数要用到关键字static,形式如下:

static类型标识符

函数名([<形式参数表>]) { …… }

内部函数又称静态函数。4.8.2外部函数

外部函数是指可供程序中所有文件调用的函数。定义外部函数要用到关键字extern,形式如下:

[extern]类型标识符

函数名([<形式参数表>]) { …… }

实际上在定义外部函数时,extern可省略。省略后,C语言默认为外部函数。

将函数定义为外部函数是否就可以被其它文件访问了呢?还不行。因为函数也有一个作用域问题。

要想调用成功,还必须在其它文件中用函数原型对其进行声明。函数原型的完整形式为:

[extern]类型标识符

函数名([<形式参数表>]);

与定义外部函数的格式差不多,只是要在后面加上一个分号“;”。在实际使用中,关键字extern可省略。

程序运行结果:

s=5166

对上述建立的两个文件,如何将它们编译在一起,形成一个最终的exe文件而运行呢?按照下面的步骤进行即可:

1.在编辑状态下建立一个“项目文件”,它不包含任何程序语句,只包括组成程序的所有源文件名。如本例创建的项目文件只包括下面两个源文件:

file_1.c file_2.c

将以上建立的项目文件存盘,主文件名自定,但扩展名必须为

.prj

。假设本例所建的项目文件名为

exam.prj。

2.在TurboC主菜单中选择Project菜单,按回车键后出现下拉菜单,选择ProjectName菜单项并按回车键后,在弹出的对话框中输入要选择的项目文件名。本例中输入exam.prj

,这样做的目的,是为了选择当前要连接和编译的项目,这时,可看到Project菜单下的子菜单ProjectName中的名称为exam.prj

,说明它为当前项目。如果要重新选择一个项目,重复此步即可。

3.选择好项目后,就可进行编译连接了。选择主菜单中的Compile菜单项,在其下拉菜单中选择MakeEXEfile菜单项,按回车键后,系统就能将exam.prj中指定的两个源文件编译连接成一个可执行文件

exam.exe。也可直接按F9进行编译连接,最后按Ctrl+F9运行程序exam.exe。4.9编译预处理命令

在书中的众多例子中,在程序的开头都有一个“#include<stdio.h>”的命令。这个命令就是一个“预处理命令”。什么叫预处理命令?它有什么作用呢?

预处理命令用来告诉编译程序在对源程序进行编译之前应作些什么。这些命令在行首以#开头,C中的预处理命令主要有三种:宏定义、文件包含、条件编译。4.9.1宏定义

宏定义(#define)能有效地提高程序的编程效率,增强程序的可读性、可修改性。C语言的宏定义分为“不带参的宏定义”和“带参的宏定义”两种。

1.不带参数的宏定义

格式:

#define宏名

宏体

作用:

为宏名指定宏体。在对源程序进行预处理时,将程序中出现宏名的地方均由宏体替换,这一过程也称为“宏展开”。

2.带参数的宏定义

格式:

#define宏名(形参表)宏体

作用:

定义带参数的宏。在对源程序进行预处理时,将程序中凡是出现宏名的地方均用宏体替换,并用实参代替宏体中的形参。例4.25:该例说明如何在程序中使用宏定义

#include<stdio.h>#definePI3.1415926#defineR4#defineL2*PI*R#defineSPI*R*R#defineMSG“Thisisamacro-defineexample.\n”main(){

printf(MSG);

printf(“L=%f,S=%f\n”,L,S);}

程序运行结果如下:

Thisisamacro-defineexample.L=25.132741,S=50.265482例4.26:该例说明带参数的宏定义在程序中的使用

#include<stdio.h>#definePI3.1415926#defineL(r)2*PI*rmain(){floatcircle,a;

printf(“\na=”);

scanf(“%f”,&a);circle=L(a);

printf(“\ncircle=%f”,circle);}

程序运行结果如下:

a=2.5circle=15.7079634.9.2文件包含

C语言中提供了许多系统函数、宏定义、结构体类型、全局变量等,它们的声明都分门别类地放在不同的“头文件”中(扩展名为.h)。例如,程序中经常要用到“printf()”、“scanf()”等一些输入输出库函数,而头文件“stdio.h”中有这些输入输出函数的原型声明,这样,通过

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论