第五章函数与程序结构.docx
- 文档编号:8919322
- 上传时间:2023-02-02
- 格式:DOCX
- 页数:21
- 大小:44.93KB
第五章函数与程序结构.docx
《第五章函数与程序结构.docx》由会员分享,可在线阅读,更多相关《第五章函数与程序结构.docx(21页珍藏版)》请在冰豆网上搜索。
第五章函数与程序结构
第五章函数与程序结构
5.1C程序的一般结构
设计一个复杂程序时,将一个复杂任务划分为若干个子任务,每个子任务设计成一个子程序,称为模块。
若子任务较复杂还可以继续分解。
子程序在程序编制(代码设计)上相互独立,而在对数据的处理上相互联系;完成总任务的程序由一个主程序和若干子程序组成。
主程序起着任务调度的总控作用,而每个子程序各自完成一个单一的任务。
这种自上而下逐步细化的模块化程序设计方法就是结构化程序设计。
结构化程序设计的优点是:
程序编制方便,易于修改和调试,可由多人分工完成,子程序代码公用(当需要完成同样任务时,只需要一份代码可多次调用)使程序简洁。
在C语言中,子程序被称为函数(Pascal中,称为过程)。
一个C程序一般有多个函数组成,其中有且仅有一个名为main的主函数。
其余为被main函数或其它子函数调用的子函数。
无论main在什么位置,C程序的执行总是从main开始执行。
C的函数分为两类,一类是由系统提供的标准库函数,如标准输入、输出函数(scanf,printf,getchar,putchar等)数学计算函数(sin,cos,fabs,sqrt等)数据格式转换函数(atoi,atof,sscanf,sprintf等),字符串处理函数(strlen,strcpy,strcmp等)和文件存取函数(fread,fwrite,fopen等),这类函数由用户直接调用。
另一类是用户在自己的程序中定义的函数,即需要用户自己编写的函数(称为用户自定义函数)。
组成一个C程序的各函数可以分开编辑成多个C源文件。
一个C源文件中可以会有0个(源文件中可以没有函数,由一些说明组成,例如定义一些全局变量)或多个函,因而一个C程序可以有一个或多个源文件,每个源文件是一个编译单位,源文件被编译之后生成二进制代码形式的目标程序文件,组成一个C程序的所有源文件都被编译之后,由连接程序将各目标文件中的目标函数和系统标准库函数装配个可执行的C程序。
5.2函数定义和函数说明
使用自己定义的函数时,要做三项工作:
(1)编写函数(定义函数);(2)在某些情况下调用之前要进行函说明;(3)在需要时调用函数。
5.2.1函数定义
先看一个实例,然后给出函数定义的一般形式:
例1:
计算:
xn,x=2,-3;n=1,2,3,…,9.
分析:
x有两个值,每个x值对应9个n值,程序要计算18次xn值。
最好定义成计算xn的函数。
函数名为power,参数为x和n。
#include“stdio.h”
intmain(void)
{inti;
doublepower(int,int);
for(i=1;i<10;i++)
printf(“power(2,%d)=%8.4f,power(-3,%d)=%11.4f\n”,i,power(2,i),i,power(-3,i));
return0;
}
doublepower(intx,intn)
{inti;
doublep;
p=1;
for(i=1;i<=n;i++)
p*=x;
return(p);
}
函数定义的一般形式为:
存储类型区分符类型区分符说明符(参数表)/*函数头*/
{说明部分/*函数体*/
语句部分
}
函数定义由函数头和函数体两部分组成,{}括起来的部分称为函数体。
语法上是一个复合语句。
各部分说明如下:
1.存储类型区分符
说明函数的存储类型,它规定了函数可被调用的范围,可用于函数的存储类型区分符有static和extern,指定为static的函数为静态函数,静态函数只局部于它所在的文件,即只能由和它在同一文件定义的函数调用;不指定存储类型区分符时的缺省的存储类型extern,缺省或指定为extern存储类型的函数为外部函数,外部函数可以被任何函数调用。
2.函数名
函数名一个标识符,一个程序中除主函数main外,其余函数的名字可以任意,最好取有助于记忆的名字,考虑到与外部联接的需要,一般不超过8个字符长。
外部函数的名字是作用于整个程序的全局量,因而外部函数相互之间不能同名。
静态函数可以和外部函数同名,但同一文件中的函数不能同名。
3.类型区分符
类型区分符说明函数返回值的数据类型(常简称为函数值的类型或函数的类型),例如power是一个double类型函数,main为int类型,函数的类型可以为任何基本类型、结构或联合,还可以定义返回值的指针的函数。
但不能定义返回数组的函数。
int函数定义时可以不指出类型区分符int,因为int是有返回值函数的缺省类型。
对于无返回值的函数,类型区分符指定为void,main一般定义为void或int类型。
4.参数表
函数定义中的参数表说明函数参数的名称、类型和数目,参数表由一个或多个参数说明组成,每个和逗号隔开,如果函数没有参数,则参数表应指定为void。
参数说明的一般形式为:
类型区分符说明符
每个类型区分符对应于一个说明符,每个说明符说明一个参数,基本类型参数的说明是一个变量标识符、数组、结构体、联合体、指针和函数。
函数定义中的参数表习惯上称为形参表。
参数是局部变量,参数的说明位于函数头部(可以在函数名后的括号内,或者在括号后与函数体之间),其作用域仅在本函数内部。
在传统C函数参数表示的形式与ANSIC有所不同,传统C说明形参的形式为:
power(x,n)
intx,n;
{…
}
ANSIC为:
power(intx,intn)
{…
}
5.函数体和函数返回值
在函数定义中,由最外层{}括起来的部分称为函数体,函数体由说明部分和语句部分组成。
说明部分是局部说明,说明的变量其有效范围局部于该函数内部,如main和Power使用了同名变量i,但它们各自有各自的存储单元,相互是完全不同的两个变量。
函数体语句法上是一个复合语句,它可以没有说明部分而只有执行部分,也可以两者都没有,最简单的合法函数是形参表为空(void)且函数体也为空的函数(称为哑函数),如:
voiddummy(void)
{
}
dummy被调用时,它不执行任何操作,仅在调用程序的流程上面有一个位置,当调用程序的功能需要扩充时,可编写具有一个新功能的函数。
Void函数不含return或含不带表达式的return语句,有返回值的函数必须至少包含一个带表达式的return语句。
Return表达式;或return(表达式);
其中,表达式的值就是函数的返回值,对于基本类型,表达式的类型和函数的类型不相同时,表达式的值自动转换为函数的类型;对于指针类型,当类型不相同时,须使用类型强制符将表达式的类型转换为函数的类型;对于结构和联合,表达式的类型必须和数据类型相同。
例如:
将power函数定义为:
doublepower(intx,intn)
{iti;
longp;
…
return(p);
}
其中return(p);将表达式p的值作为power的返回值,p被自动转换成double类型。
函数体最外层右花括号“}”等价于“return;”,即对不含return语句的函数,当执行完函数体中最后一个语句时,将自动返回调用处。
5.2.2函数说明
C语言允许函数先调用后定义,也允许被调用函数在其它文件中定义。
对于以上情况的非int函数必须在调用之前作函数说明,作用是指出被调用函数的类型和参数的类型,否则,编译程序认为被调用函数为int类型。
函数说明的一般形式为:
存储类型区分符类型区分符函数说明符(参数表);
外部函数说明时可指定extern或不指定存储类型区分符,静态函数说明时必须指定static;参数表可以只列出参数的类型名而不需给出参数名。
例如:
doublepower(int,int);
或doublepower(intx,intn);
函数说明还可以和同类型的其它变量放在一个说明语句中;如:
doublex,power(int,int);
对于无参数表的函数,说明时参数表应指定为void.
函数说明可位于调用函数体内或函数体外(一般位于程序开头部分),在函数体外说明的函数可在说明之后直到该源文件结束外的任何函数中调用,在函数体内说明的函数只能在说明所在的函数体内调用,如例1中main调用了power函数,power函数的定义在main之后,且类型为非int类型,所以在main的说明部分要对power进行说明,也可在main函数外面说明;
doublepower(int,int);
intmain(void)
{inti;
….
}
传统C的函数说明不给出参数表,无论函数是否有参数()中均为空,如doublepower();
标准C保留了传统C的说明形式,但不提倡使用。
带参数表的函数说明称为函数原形,标准库函数的原型在系统提供的相应头文件中,因此,程序中调用标准库函数时只需用#include预处理控制包含所需的头文件,而不须写函数说明。
如果函数的调用在函数定义之后,则不必在调用函数前进行说明,如例1中若power的定义出现在main的前面,则在main中可省略对power的说明。
5.3函数调用与参数传递
一个函数可以被其它函数多次调用,每次调用时可以处理不同的数据,因此函数是对不同数据进行相同处理的一般程序形式。
通常在函数定义时,在参数表中列出的参数称为形参,形参是函数要处理的数据名称(变量)。
函数调用时系统为形式参数分配与其类型的长度相同的存储单元(注意显形参分配存储单元是在函数调用时进行的,而不是在定义时分配),将实际要处理的参数送到形参对应的存储单元,每次调用时可使用不同的实际数据从而实现不同数据的相同处理,调用时被送到形参单元的实际数据通常称为实际参数。
形参是变量,实参是形参的值。
5.3.1函数调用
1.函数调用的一般形式:
函数名(实参1,实参2,…,实参n)
例如:
power(2,i)
无参数的函数调用形式为:
函数名(),如getchar()
在函数调用中,()部分称为实参表,实参是一个表达式,有多个实参时,相互间用逗号隔开,对于无参数的函数,调用时实参表为空,但()不能省。
实参和形参应在数目上、次序上和类型上一致。
函数调用在程序中起一个表达式或一个语句的作用,对于有返回值的函数,函数调用一般作为表达式出现,即凡程序中允许出现表达式的位置上均可出现函数调用;也可作为语句(即表达式语句)出现。
例如:
①getchar();作为语句出现
②c=getchar();作为表达式出现
③while(putchar(getchar())!
=’?
’);
getchar函数调用作为putchar函数的实参,putchar函数调用作为关系表达式的左操作数(表达式)出现。
④while(c=getchar())!
=’?
’)putchar(c);
2.函数调用的执行过程
(1)函数在执行过程中,一旦遇到函数调用,系统首先为每个形参分配存储单元,并计算实参表达式的值,然后把实参复制到(送到或存入)对应形参的存储单元,实参与开参按位置。
(2)将控制转到被调用的函数执行其函数体内的语句。
(3)当执行return语句或到函数不尾时,控制返回到调用处,如果有返回值,回送一个值并返回控制,然后从函数调用点继续执行,函数调用点指的是:
若函数调用出现在表达式中,则调用点是该表达式,若函数调用单独作为一个语句出现,则返回时执行该语句的下一语句。
3.参数的求值顺序
函数调用时,每一实参为一表达式,实参与实参间用逗号分隔,而不是顺序求值运算符,多数编译程序在计算参数值时从右向左的顺序进行。
例如在TurboC中运行下列程序:
voidmain(void)
{
intx=0;
printf(“x=%d\n”,x);
printf(“x++=%dx++=%d\n”,x++,x++);
printf(“x=%d\n”,x);
}
执行时输出:
x=0
x++=1x++=0
x=2
若是x=0;
printf(“%d,%d,%d”,++x,++x,++x);
则结果为:
3,2,1
4.函数调用转换的规则和参数的一致性
函数调用时会引起下列类型转换:
(1)如果无函数说明,则进行参数提升,即将char,short,枚举转换为int,float转换为double。
(2)如果函数说明为原型形式,则将实参转换为原型相应参数的类型(和赋值转换相似)。
实参与形参的一致性:
实参的数目必须与明确说明的参数数目相同;如果说明的参数表以省略符(,…)结束,则实参的数目必须等于或超过明确指出的参数数目。
如果参数的数目不一致或类型不一致,则调用效果不确定。
5.3.2参数的传递方式
函数调用时将实参传送给开参称为参数传递,C语言中参数传递方式是“传值”(C++中有传地址),即被调用函数的形参接收的是实参的值(实参的副本),而不是实参(变量)的地址,形参和实参变量各有不同的存储单元,被调用函数对形参变量的值的修改,不会影响实参变量的值。
例5.2同例5.1计算:
xn,x=2,-3;n=1,2,3,…,9.
#include“stdio.h”
doublepower(int,int);
intmain(void)
{inti;
for(i=1;i<10;i++)
printf(“power(2,%d)=%8.4f,power(-3,%d)=%11.4f\n”,i,power(2,i),i,power(3,i));
return0;
}
doublepower(intx,inti)
{
doublep;
for(p=1;i>0;i--)
p*=x;
return(p);
}
power中的i是mian中i的一个副本,它的改变不会影响main中的i值。
5.3.3参数数目可变的函数
C语言中可以定义参数数目可变的函数,定义这种函数时,要求至少给出一个形参,在列出的最后一个形参后面用“,…”来说明该函数的参数数目可变。
调用时,实参的数目不能少于形参表中列出的参数的数目,如printf和scanf是C中最常用的参数数目可变的函数。
printf函数调用时的一般形式为:
printf(格式字符串,参数1,参数2,…);
其中第一个参数(格式字符串)是必须的,调用时,系统根据第一个参数中的转换说明项的数目和说明字符来确定其余参数的数目和类型。
5.4 变量的存储类型
C语言的变量,有两种属性:
数据类型和存储类型,存储类型决定变量与函数存储的方式、生命周期、作用域及变量初始化的值和方式。
5.4.1 存储类型区分符
C语言的数据有四种存储类型,分别由四个关键字表示存储类型区分符,auto(自动),static(静态),register(寄存器)和extern(外部)。
完整的变量说明的一般形式:
存储类型区分符 类型区分符 变量名表;
若没有指定存储类型区分符,默认是auto存储类型。
5.4.2 自动变量
自动变量在进入块时分配存储单元(“块”如函数复合语句),在退出块后,它所占用的存储单元被回收,其作用域仅在块内有效,自动变量没有缺省的初值,如果定义时没有明确指出初值,则其初值是不确定的。
对于显示初始化,其初值不限于常量表达式,且每次进入块时,都要执行一次给变量赋值的操作。
例:
下面的程序说明自动变量的初始化和作用域。
#include“stdio.h”
voidtestauto(intn)
{autointi=10;
n++;
i++;
printf(“n=%d\n”,n);
printf(“i=%d\n”,i);
{autointi=100;
i++;
printf((“i=%d\n”,i);
}
printf(“i=%d\n”,i);
}
intmain(void)
{autointi,n=1;
printf(“n=%d\n”,n);
for(i=1;i<3;i++)
testauto(n);
printf(“n=%d\n”,n);
return0;
}
输出:
n=1
n=2
i=11
i=101
i=11
n=2
i=11
i=101
i=11
n=1
5.4.3外部变量
外部变量在程序被编译时分配存储单元,它的生命周期是程序的整个执行过程。
其作用域是从外部变量定义之后,直到该源文件结束的所有函数。
外部变量初始化是在外部变量定义时进行的,且其初始化仅执行一次,若无显式初始化,则系统自动初始化为与变量类型相同的0值(整型0,字符型’\0’,浮点型0.0)
在有显式初始化的情况下,初值必须是常量表达式,外部变量在程序执行之前分配存储单元,在程序运行结束后才被收回,这种存储分配方式称为静态存储分配,外部变量是在函数之外说明的。
例3输入以秒为单位的一个时间值,将其转换化“时:
分:
秒”的形式输出。
#include“stdio.h”
inthh,mm,ss;
voidconvertime(longseconds)
{hh=seconds/3600;
mm=(seconds%3600)/60;
ss=seconds%60;
}
intmain(void)/*testpowerfunction*/
{longseconds;
printf(“hh=%d,mm=%d,ss=%d\n”,hh,mm,ss);
printf(“inputatimeinsecond:
”);
scnaf(“%ld”,&seconds);
convertime(seconds);
printf(“%2d:
%2d:
%2d\n”,hh,mm,ss);
return0;
}
执行程序时:
nn=0,mm=0,ss=0
inputatimeinsecond:
输入:
41574
输出:
11:
32:
54
外部变量还可以显式初始化,如:
#include“stdio.h”
intn=100;
voiddecrement(void)
{n-=20;
}
intmain(void)
{printf(“n=%d\n”,n);
for(;n>=60;)
{decrement();
printf(“n=%d\n”,n);
}
return0;
}
执行时输出:
n=100
n=80
n=60
n=40
外部变量还可以进行引用性说明,如上例
#include“stdio.h”
externintn;/*引用性说明*/
voiddecrement(void)
{n-=20;
}
intn=100;/*定义性说明*/
intmain(void)
{printf(“n=%d\n”,n);
for(;n>=60;)
{decrement();
printf(“n=%d\n”,n);
}
return0;
}
外部变量n的定义性说明在decrement函数之后,编译在该处给变量分配存储单元并执行初始化,decrement函数对n赋值是对n在定义之前的引用(超前引用),因此要用extern对n作引用说明;外部变量引用说明时不分配存储单元,也不执行初始化,引用说明:
externintn;可以放在decrement函数体的局部说明部分,也可以放在decrement函数的前面。
5.4.4静态变量
外部静态变量和局部变量都是在程序被编译时分配存储单元,在程序运行结束后回收所占用的存储单元,静态变量的生命周期是程序的整个执行过程,外部静态变量的作用域是外部静态变量所在的文件内从变量定义之后至文件结束;局部静态变量的作用域是局部静态变量所在的块。
局部静态变量和自动变量一样只有定义性说明,没有引用性说明,因此必须先定义后引用。
外部静态变量的初始化同外部变量。
局部静态变量在编译时执行一次初始化。
在有显工初始化的情况政,初值由说明符中的说明确定,在无显式初始化情况下,同外部变量。
以下程序说明外部静态变量与外部变量的区别:
/*exam-a.c*/
#include“stdio.h”
staticfloatx;
floaty,f2(float,float);
floatf1(floata,floatb)
{return(a*b-x);
}
intmain(void)
{x=500;
y=100;
printf(“f1=%f,f2=%f\n”,f1(x,y),f2(x,y));
return0;
}
/*exam-b.c*/
externfloaty;
floatf2(floata,floatb)
{return(a/b+y);
}
输入:
f1=49500.000000,f2=105.000000
该程序包含两个文件三个函数,函数main和f1在文件exam-a.c中,函数f2在文件exam-b.c,x只能在main和f1中使用,不能在f2中使用,y在三个函数中均可使用,但在f2中必须用externfloaty;作引用说明,另外在main中引用非int型外部函数f2,因此须在main前面或在main的局部说明中对f2作引用性说明。
下面的例子说明局部静态变量与自动变量的区别:
#include“stdio.h”
voidaust(void)
{intau=0;
staticintst=0;
printf(“autovariable=%d,staticvariable=%d\n”,au,st);
au++;
st++;
}
intmain(void)
{intI;
for(I=0;I<3;I++)
aust();
}
执行进输出:
autovariable=0,staticvariabe=0
autovariable=0,staticvariabe=1
autovariable=0,staticvariabe=2
5.4.5寄存器变量
说明为寄存器存储类型的局部变量首先在寄存器中分配存储,如果无足够的寄存器,则和自动变量一样在内存中分配存储单元。
寄存器变量除不能求地址之外,其余用法同自动变量,对于使用频繁的值,使用寄存器变量可以提高运行速度。
例2:
计算s=x1+x2+x3+…+xn,x和n由终端输入。
#include“stdio.h”
longsum(registerintx,intn)
{longs;
intI;
registert;
t=s=x;
for(I=2;I<=n;I++)
{t*=x;
s+=t;
}
returns;
}
intmain(void)
{intx,n;
printf(“inputx,n:
”);
scanf(“%d%d”,&x,&n);
printf(“s=%ld\n,sum(x,n));
return0;
}
执行进输入:
inputx,n:
45
s=1364
计算机的寄存器是有限的,为确保寄存器用于最需要的地方,寄存器变量是不能设置太多,通常用于使用最频繁的整形或字符型值。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第五章 函数与程序结构 第五 函数 程序结构
![提示](https://static.bdocx.com/images/bang_tan.gif)