C基础及其教程 8_第1页
C基础及其教程 8_第2页
C基础及其教程 8_第3页
C基础及其教程 8_第4页
C基础及其教程 8_第5页
已阅读5页,还剩78页未读, 继续免费阅读

下载本文档

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

文档简介

第5章函数C语言大学实用教程内容提要

函数定义、函数调用、函数原型、函数的参数传递与返回值

递归函数和函数的递归调用

函数封装,函数复用,函数设计的基本原则,程序的健壮性

变量的作用域与存储类型,全局变量、自动变量、静态变量、寄存器变量

“自顶向下、逐步求精”的模块化程序设计方法5.1程序设计的艺术算法设计艺术程序的灵魂DonaldE.Knuth,

“TheArtofComputerProgramming”,

清华大学出版社(英),国防工业出版社(中)结构设计艺术程序的肉体模块化(Parnas,1972)结构化(Structural)面向对象(Object-Oriented)面向组件(Component-Oriented)面向智能体(Agent-Oriented)……问题的提出读多少行的程序能让你不头疼?假如系统提供的函数printf()由10行代码替换,那么你编过的程序会成什么样子?实际上一个printf()有上千行代码main()中能放多少行代码?如果所有代码都在main()中,怎么团队合作?如果代码都在一个文件中,怎么团队合作?问题的提出《三国演义》中有这样一段描写:懿问曰:“孔明寝食及事之烦简若何?”使者曰:“丞相夙兴夜寐,罚二十以上皆亲览焉。所啖之食,日不过数升。”懿顾谓诸将曰:“孔明食少事烦,其能久乎?”此话音落不久,诸葛亮果然病故于五丈原。“事无巨细”,“事必躬亲”管理学的观点是极其排斥这种做法的,认为工作必须分工,各司其职其中的思想,在程序设计里也适用分而治之与信息隐藏分而治之(DivideandConquer,Wirth,1971)函数把较大的任务分解成若干个较小的任务,并提炼出公用任务信息隐藏(InformationHiding,Parnas,1972)设计得当的函数可把具体操作细节对程序中无需知道它们的那些部分隐藏掉,从而使整个程序结构清楚使用函数时,不用知道函数内部是如何运作的,只按照我们的需要和它的参数形式调用它即可数学中的函数自变量因变量函数名程序设计中的函数程序设计中的函数不局限于计算计算类,如打印阶乘表的程序……判断推理类,如排序、查找……函数是C语言中模块化编程的最小单位可以把每个函数看作一个模块(module)如把编程比做制造一台机器,函数就好比其零部件可将这些“零部件”单独设计、调试、测试好,用时拿出来装配,再总体调试。这些“零部件”可以是自己设计制造/别人设计制造/现成的标准产品5.2函数的定义和使用MoeCurlyLarry5.2函数的定义和使用若干相关的函数可以合并成一个“模块”一个C程序由一个或多个源程序文件组成一个源程序文件由一个或多个函数组成5.2.1函数的分类函数生来都是平等的,互相独立的,没有高低贵贱和从属之分main()稍微特殊一点点C程序的执行从main函数开始调用其他函数后流程回到main函数在main函数中结束整个程序运行5.2.1函数的分类标准库函数ANSI/ISOC定义的标准库函数符合标准的C语言编译器必须提供这些函数函数的行为也要符合ANSI/ISOC的定义第三方库函数由其它厂商自行开发的C语言函数库不在标准范围内,能扩充C语言的功能(图形、网络、数据库等)自定义函数自己定义的函数包装后,也可成为函数库,供别人使用5.2.2函数定义(Functiondefinition)类型函数名(类型参数1,类型参数2,……)

{

声明语句序列可执行语句序列

return

表达式;

}返回值类型函数名标识符,说明运算规则参数表相当于运算的操作数返回运算的结果函数出口5.2.2函数定义(Functiondefinition)类型函数名(类型参数1,类型参数2,……)

{

声明语句序列可执行语句序列

return

表达式;

}函数体的定界符参数表里的变量(叫形式参数,FormalParameter)也是内部变量函数体5.2.2函数定义(Functiondefinition)void

函数名(void)

{

声明语句序列可执行语句序列

return;

}函数无返回值,用void定义返回值类型用void定义参数,表示没有参数return语句后无需任何表达式计算整数n的阶乘n!/*函数功能:用迭代法计算n!

函数入口参数:整型变量n表示阶乘的阶数函数返回值:返回n!的值*/longFact(intn)/*函数定义*/{inti;longresult=1;for(i=2;i<=n;i++){ result*=i;}returnresult;}返回值类型函数名说明函数的功能返回值作为函数调用表达式的值形参表,函数入口函数内部可以定义只能自己使用的变量,称内部变量

函数名(表达式1,表达式2,……);实际参数(ActualArgument)函数调用(FounctionCall)时提供的表达式有返回值时放到一个数值表达式中

c=Max(a,b);作为另一个函数调用的参数

c=Max(Max(a,b),c);

printf("%d\n",Max(a,b));无返回值时函数调用表达式Display(a,b);

返回值=函数名(实参表列);函数名(实参表列);5.2.3函数的调用、参数传递和返回值函数的每次执行都会建立一个全新的独立的环境在栈中为函数的每个变量(包括形式参数)分配内存把实参值复制给形参开始执行函数内的第一条语句intmain(void){inta=12,b=24,larger;larger=Max(a,b);…return0;}intMax(intx,inty){intresult;result=(x>y)?x:y;returnresult;}①函数调用的过程y?x?result?b24a12larger?1224函数内的代码在这个独立的环境内工作当函数执行到return语句或}时函数退出程序从当次调用函数的地方继续执行函数可有多个return,但最好只有一个且是最后一行intmain(void){inta=12,b=24,larger;larger=Max(a,b);…return0;}intMax(intx,inty){intresult;result=(x>y)?x:y;returnresult;}③②函数调用的过程y?x?result?b24a12larger?122424函数退出时求出返回值,存入一个可被调用者访问的地方收回分配给所有变量(包括形式参数)的内存程序控制权交给调用者,调用者拿到返回值,将其作为函数调用表达式的结果intmain(void){inta=12,b=24,larger;larger=Max(a,b);…return0;}intMax(intx,inty){intresult;result=(x>y)?x:y;returnresult;}函数调用的过程y24x12result24b24a12larger?24函数的参数传递实参和形参必须匹配数目一致,类型一一对应(否则会发生自动类型转换)#include<stdio.h>intMax(intx,inty);intmain(void){inta=12,b=24,larger;larger=Max(a,b);printf("%dislarger.\n",larger);return0;}intMax(intx,inty){intresult;result=(x>y)?x:y;returnresult;}5.2.4函数原型(FunctionPrototype)在调用函数前先声明其返回值类型、函数名和参数函数原型有助于编译器对函数参数类型的匹配检查

末尾有一个分号,声明时不要省略形参和返回值的类型例5.1使用了Max函数的main()

#include<stdio.h>intMax(intx,inty);//函数功能:返回x和y中较大的数intMax(intx,inty){intresult=(x>y)?x:y;returnresult;}intmain(void){inta=12,b=24;intlarger=Max(a,b); //在这里进行函数调用

printf("%dislarger.\n",larger);return0;}函数声明(函数原型),声明时不要省略形参和返回值的类型函数调用函数定义也有声明函数的效果,当返回值为整型或者函数定义在函数调用前面时,可以省略函数原型函数定义与函数声明的区别函数定义指函数功能的确立指定函数名、函数类型、形参及类型、函数体等是完整独立的单位

函数声明是对函数名、返回值类型、形参类型的说明不包括函数体是一条语句,以分号结束,只起一个声明作用5.3变量的作用域和存储类型变量的作用域(Scope)指在源程序中定义变量的位置及其能被读写访问的范围分为局部变量(LocalVariable)全局变量(GlobalVariable)局部变量(LocalVariable)在语句块内定义的变量形参也是局部变量特点生存期是该语句块,进入语句块时获得内存,仅能由语句块内语句访问,退出语句块时释放内存,不再有效定义时不会自动初始化,除非程序员指定初值并列语句块各自定义的同名变量互不干扰形参和实参可以同名全局变量(GlobalVariable)在所有函数之外定义的变量生存期是整个程序,从程序运行起占据内存,程序运行过程中可随时访问,程序退出时释放内存有效范围是从定义变量的位置开始到本程序结束全局变量(GlobalVariable)【例5.2】演示语句块嵌套的示例#include<stdio.h>intmain(void){ inta=1; { inta=2; printf("IntheINNERblock,a=%d\n",a); } printf("IntheOUTERblock,a=%d\n",a); return0;}全局变量(GlobalVariable)【例5.3】全局变量的示例#include<stdio.h>intglobal; //定义全局变量voidGlobalPlusPlus(void);intmain(void){ global=1; printf("BeforeGlobalPlusPlus(),itis%d\n",global); GlobalPlusPlus(); printf("AfterGlobalPlusPlus(),itis%d\n",global); return0;}//函数功能:对全局变量global加1,并打印加1之前与之后的值voidGlobalPlusPlus(void){ printf("Before++,itis%d\n",global); global++; printf("After++,itis%d\n",global);}使函数之间的数据交换更容易,也更高效,但建议尽量少用,因为谁都可以改写全局变量,所以很难确定是谁改写了它全局变量全局变量(GlobalVariable)【例5.4】同名局部变量各自作用域的示例#include<stdio.h>voidGlobalPlusPlus(void);intmain(void){intglobal=1;printf("BeforeGlobalPlusPlus(),itis%d\n",global);GlobalPlusPlus();printf("AfterGlobalPlusPlus(),itis%d\n",global);return0;}//函数功能:对局部变量global加1,并打印加1之前与之后的值voidGlobalPlusPlus(void){intglobal=1;printf("Before++,itis%d\n",global);global++;printf("After++,itis%d\n",global);}5.3.3变量的存储类型(StorageClass)指数据在内存中存储的方式即编译器为变量分配内存的方式,它决定变量的生存期

存储类型数据类型变量名;C程序的存储类别auto型(自动变量)static型(静态变量)extern型(外部变量)register型(寄存器变量)…stackstack静态存储区动态存储区形参、auto变量、函数调用的现场等全局变量、static变量5.3.3变量的存储类型(StorageClass)Sincestorageisfreedinthereverseorderofallocation,astackisaconvenientdatastructuretorepresentitwith(theruntimestack)先进后出静态存储区中的变量:与程序“共存亡”

动态存储区中的变量:与程序块“共存亡”寄存器中的变量:同动态存储区变量的生存期(Lifetime)

Thelifetime

ofavariableistheperiodoftimeduringwhichmemoryisallocatedtothevariable决定何时“生”,何时“灭”5.3.3变量的存储类型(StorageClass)自动变量和静态变量auto

数据类型变量名;auto体现在进入语句块时自动申请内存,退出时自动释放内存动态局部变量,缺省的存储类型静态变量static

数据类型变量名;staticstorageclassforlocalvariables(declaredinsideablockorfunction)-thelifetimeoftheentireprogram生存期为整个程序运行期间例5.5#include<stdio.h>void

Func(void);intmain(void){

int

i;

for(i=0;i<5;i++) {

Func(); }}/*函数功能:打印被调用的次数

函数入口参数:无函数返回值:无*/voidFunc(void){

inttimes=1; /*自动变量*/

printf("Func()wascalled%dtime(s).\n",times++);}Func()wascalled1time(s).Func()wascalled1time(s).Func()wascalled1time(s).Func()wascalled1time(s).Func()wascalled1time(s).

自动变量:每次执行都重新初始化,在函数退出后失效,再次进入函数,变量值重新初始化【例5.5】静态变量的示例例5.5#include<stdio.h>void

Func(void);intmain(void){

int

i;

for(i=0;i<5;i++) {

Func(); }}/*函数功能:打印被调用的次数

函数入口参数:无函数返回值:无*/voidFunc(void){

static

inttimes=1; /*静态局部变量*/

printf("Func()wascalled%dtime(s).\n",times++);}Func()wascalled1time(s).Func()wascalled2time(s).Func()wascalled3time(s).Func()wascalled4time(s).Func()wascalled5time(s).静态变量:仅初始化一次,变量的值可保存到下次进入函数,使函数具有记忆功能【例5.5】静态变量的示例寄存器变量寄存器CPU内部容量有限、但速度极快的存储器

register

类型名变量名;

使用频率比较高的变量声明为register

,可使程序更小、执行速度更快现代编译器有能力自动把普通变量优化为寄存器变量,并且可以忽略用户的指定所以一般无需特别声明变量为register

全局变量静态外部变量(只限本文件使用)外部变量

(非静态外部变量允许其它文件引用)局部变量

自动变量,(离开函数,值就消失)寄存器变量(离开函数,值就消失)定义点之前使用,需用extern声明静态局部变量(离开函数,值仍保留)动态局部变量变量的作用域与存储类型5.4函数封装函数的封装(Encapsulation)使外界对函数的影响仅限于几个参数。好处:函数的设计者可以专心于参数的处理和函数的实现,完全不必关心调用者是什么。函数对外界的影响也仅限于一个返回值和指针、数组类型的参数。编程者需要考虑的问题的范围大大缩小,有利于编写更完美的代码,还便于各个函数单独测试、排错,也便于多人合作开发。5.5预处理指令把编译的过程细分一下就是“预处理”、“编译”和“链接”三个步骤。“编译”做的工作是把源代码翻译为机器代码,然后由“链接”将机器代码组合为可执行文件。“预处理”在编译之前进行,根据源代码中的预处理指令,在后台调整源代码。编译器编译的都是经过预处理的代码。预处理指令的典型特色以“#”开头5.5预处理指令#include#include<filename>在编译器指定的目录(也可以由用户通过设置编译器选项指定这个目录)内查找filename文件,它通常就是一个叫“include”的目录,目录下有很多.h文件#include"filepath"按照filepath所描述的路径查找文件。通常我们给定的filepath里并不含有路径,只是一个文件名,表示在与源文件相同的目录下查找filepath。如果能成功定位文件(否则会出现编译错误),则预处理器会用该文件的内容替换#include指令所在的行。替换后的代码再被编译器编译。5.5预处理指令#define和#undef宏定义指令#defineMACRO_NAMEreplacement取消宏定义#undefMACRO_NAME5.5预处理指令条件编译预处理指令还有剪裁代码的能力,使某些代码仅在特定的条件成立时才会被编译进可执行文件。这项功能由#if、#ifdef、#ifndef、#else、#elif和#endif组合实现。使用断言(assert)防止某些参数获得非法值,在程序调试和测试时发现错误5.6使用assert()查错

#include<assert.h>

voidassert(intexpression);expression为真,无声无息;为假,中断程序。断言仅用于调试程序,不能作为程序的功能5.6使用assert()查错用来测试某种不可能发生的状况确实不会发生Debug版有效Release版失效考虑使用断言的几种情况检查函数入口参数的合法性在一段计算的结束处检验计算结果是否在合理的范围内检查程序中的各种假设的正确性证实或测试某种不可能发生的状况确实不会发生

5.6使用assert()查错函数设计的基本原则

信息隐藏1函数规模要小2函数功能要单一3函数接口定义要清楚入口参数有效性检查敏感操作前的检查调用成功与否的检查任何C语言程序都可以只用一个源文件实现,但在现实中,几乎没有只用一个源文件实现的软件。C语言允许将一个程序分解成若干模块,分别放在几个源文件中,形成一个项目(Project)。编译器会单独编译每个源文件,再将它们的目标代码连同标准函数库中的函数链接在一起,形成可执行文件。好处使程序的模块结构更清晰,更易于维护,给多个程序员共同编制一个大型项目的代码提供了方便。5.7模块和链接一般每个模块是一个扩展名为.c的源文件和一个扩展名为.h的头文件的组合。模块之间通过互相调用函数和共享全局变量联系起来,头文件是联系的纽带。函数的原型声明、宏定义和全局变量的声明等都可以放在头文件中,这样使用此模块的程序在#include这个头文件后,就知晓可以用什么、怎么用了。头文件里对全局变量的声明要加上extern关键字,用以说明该变量为外部变量(编译器并不对其分配内存),即这个变量的内存是在其他模块分配的。模块的内部经常包含一些不允许模块外使用的函数和全局变量。只要在定义它们时加上static关键字,就能保证仅该模块内可用5.7模块和链接“分而治之”是一种解决复杂问题的常用方法。大的问题可以分解成若干小问题,小问题还可以分解为更小的问题。模块的划分方法功能分解法面向对象法功能分解是一个自顶向下、逐步求精的过程。5.8模块化程序设计方法简介5.8模块化程序设计方法简介模块各司其职每个模块只负责一件事情,它可以更专心便于进行单个模块的设计、开发、调试、测试和维护等工作一个模块一个模块地完成,最后再将它们集成开发人员各司其职按模块分配任务,职责明确并行开发,缩短开发时间什么时候需要模块化?某一功能,如果重复实现3遍以上,即应考虑模块化,将它写成通用函数,并向小组成员发布5.8模块化程序设计方法简介模块化的优点——复用构建新的软件系统可以不必每次从零做起,直接使用已有的经过反复验证的软构件,组装或加以合理修改后成为新的系统,提高软件生产率和程序质量在其他函数的基础上构造程序拿来拿去主义,指尽可能复用其它人现成的模块不是人类懒惰的表现,而是智慧的表现一般要靠日积月累才能建立可以被复用的软件库前期投入多,缺乏近期效益,大部分公司都注重近期效益,是为了生存,所以软件复用对公司来说不是最高优先级5.8模块化程序设计方法简介功能分解自顶向下、逐步求精的过程模块分解的基本原则保证模块的相对独立性——高聚合、低耦合模块的实现细节对外不可见——信息隐藏外部:关心做什么;内部:关心怎么做设计好模块接口接口指罗列出一个模块的所有的与外部打交道的变量等定义好后不要轻易改动在模块开头(文件的开头)进行函数声明高聚合对模块个体的要求。符合此原则的模块功能必须单一,不能身兼数职低耦合也叫弱耦合对模块之间关系的要求。耦合度用来评价模块间彼此影响的程度。低耦合模块提供的函数和参数很简练,没有或只有受限的全局变量5.8模块化程序设计方法简介5.8模块化程序设计方法简介逐步求精(StepwiseRefinement)由不断的自底向上修正所补充的自顶向下(Top-down)的程序设计方法【例5.7】用函数完成猜数游戏猜多个数,10次猜不对就猜下一个数模块分解过程

开始结束初始化退出处理主功能为程序运行所作的准备工作在退出前要做的事情,如打印结果、资源释放等自底向上自顶向下的模块化程序设计【例5.7】用函数完成猜数游戏开始结束生成数字猜数字【例5.7】用函数完成猜数游戏开始结束生成数字猜数字是否继续?NY【例5.7】用函数完成猜数游戏开始结束猜得对吗?NY提示大小次数<10?输入数字NY处理用户输入,判断是否有输入错误,是否在合法的数值范围内

【例5.7】用函数完成猜数游戏【例5.7】用函数完成猜数游戏【例5.7】用函数完成猜数游戏【例5.7】用函数完成猜数游戏5.9递归递归问题的提出【例5.9】“汉诺塔”(Hanoi)这是一个必须用递归方法才能解决的问题n=64时,18,446,744,073,709,551,615次1844亿亿次每次1微秒,需要60万年递归问题的提出

A→C,A→B,C→B,A→C,B→A,B→C,A→CABCn=3递归问题的提出

A→C,A→B,C→B,A→C,B→A,B→C,A→CABC递归问题的提出

A→C,A→B,C→B,A→C,B→A,B→C,A→CABC递归问题的提出

A→C,A→B,C→B,A→C,B→A,B→C,A→CABC递归问题的提出

A→C,A→B,C→B,A→C,B→A,B→C,A→CABCn更大些怎么办?递归问题的提出第一步:将问题简化。假设A杆上只有2个圆盘,即汉诺塔有2层,n=2。ABC递归问题的提出对于一个有n(n>1)个圆盘的汉诺塔,将n个圆盘分为两部分:上面的n-1个圆盘和最下面的n号圆盘。将“上面的n-1个圆盘”看成一个整体。将n-1个盘子从一根木桩移到另一根木桩上将1个盘子从一根木桩移到另一根木桩上ACB递归问题的提出将n个盘子从一根木桩移到另一根木桩上问题分解为:将n-1个盘子从一根木桩上移到另一根木桩上将1个盘子从一根木桩移到另一根木桩上设计一个函数,入口参数为n:将n个盘子从一根木桩移到另一根木桩上将n-1个盘子从一根木桩上移到另一根木桩上也要调用这个函数来实现出现了函数调用自己的问题递归调用(RecursiveCall)函数的嵌套调用嵌套调用在调用一个函数的过程中,又调用另一个函数C语言规定函数不能嵌套定义,但可以嵌套调用函数是相互平行的intmain(){……a();return0;}a

函数{b();…return;}b函数{……return;}①③④⑤⑥⑦②递归调用(RecursiveCall) long

fact(intn)

{

if(n<0)

return-1;

elseif(n==0||n==1)

return1;

else

returnn*fact(n-1);

}【例5.8】计算n!=n*(n-1)*(n-2)*…*1

函数直接或间接调用自己递归函数

unsigned

long

fact(unsigned

intn)

{

if(n==0||n==1)

return1;

else

returnn*fact(n-1);

}基线情况(basecase)一般情况(generalcase)无需考虑n<0了【例5.8】计算n!=n*(n-1)*(n-2)*…*1

例5.8求整数n的阶乘n!#include<stdio.h>intmain(void){intn,i;longresult=1;

printf("Inputn:");

scanf("%d",&n);for(i=1;i<=n;i++){result*=i;//计算n!}if(n>=0){

printf("%d!=%ld\n",n,result);}return0;}迭代法//函数功能:当n>2时递归计算n!的值//当n为0或1时,返回1;当n小于0时,返回0longfact(longn){if(n<0){return0; //如果n<0,则返回0}elseif(n==0||n==1) //递归终止条件{return1;//当n为0或1时,返回1}else{returnn*fact(n-1); //递归调用计算n!}}例5.8求整数n的阶乘n!递归法#include<stdio.h>longfact(longn);intmain(void){intn;longresult;

printf("Inputn:");

scanf("%d",&n);result=fact(n);if(result!=0){

printf("%d!=%ld\n",n,result);}

温馨提示

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

评论

0/150

提交评论