




已阅读5页,还剩126页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1,第6章函数,本章的学习目标:了解模块化程序设计的概念;了解库函数、自定义函数的区别;掌握函数的定义和调用;掌握变量的性质,包括作用域和生命期;能创建多文件的程序:项目。,2,本章概要,6.1概述6.2函数的定义6.3函数的调用6.4常见的库函数6.5变量的性质,3,本章概要,6.6变量的作用域6.7变量的生命期6.8内部函数与外部函数6.9项目6.10创建库函数,4,6.1概述,引言有次去福州看秦始皇兵马俑的展出,工作人员向我们讲述了秦国军队武器的过人之处。秦国军队使用的弩是当时世界上最先进的,但其实最先进的不仅是弩的制作工艺,而更在于科学的管理制度。工匠们在制作弩的过程中,并不是每个工匠都独立制作一个完整的弩,而是每种工匠专职制作一种部件,最后进行拼接。这样进行分工则每种工匠只需要专注于一种工作,技术相对更加娴熟,使得工作更象流水线作业,大大地提高了效率不说,培训这样的工匠也相对成本比较低。而且大家都只专注自己的那部分技术而并不明白其他部件的制作工艺,还可以防止个人可以独立制作武器而造成不安全的因素。,5,6.1概述,上述的管理模式其实是一种模块化分工与合作的思想。为了在C语言中实现模块化的思想,就要用到函数。本章主要介绍函数的基础知识。,6,6.1概述,6.1.2什么是函数函数是一组逻辑相关语句的集合。C语言中,模块就是一组逻辑相关(这也体现了软件工程中的“内聚性”)的语句的集合或说是用于完成特定任务的程序代码的单元。根据规模的大小,C语言中的模块有三种形式,它们是:语句块(即复合语句)、函数和文件。函数是C语言中最为常用的模块单元,因此通常说C程序是由函数组成的,函数是C语言程序的基本组成部分。,7,6.1概述,8,C程序结构,C是函数式语言必须有且只能有一个名为main的主函数C程序的执行总是从main函数开始,在main中结束函数不能嵌套定义,可以嵌套调用,6.1概述,9,6.2函数的定义,6.2.1函数的分类1.库函数C提供了一个标准库,其中包含许多完成大部分常规功能所需的函数,这就是库函数。当程序中调用一个未曾定义的函数时,TurboC记住这个函数名,然后由连接程序将该程序与标准库中该函数的目标代码连接起来,组成可执行文件。,10,6.2函数的定义,2.自定义函数程序员也可以按自己的意愿编写自己的函数,完成任意功能。把相关的函数集合在一起,也就成了你自己的函数库。如果这个库真的很好、很有用,就可以考虑把它商业化,拿出去换钞票了。如果被标准化组织采纳,您的函数库也就成了标准函数库了。,11,6.2函数的定义,注意:函数都是平等的,没有高低贵贱之分,只不过main()特殊一点。不过还是可以从使用者的角度进行分类。注:平等是指函数在定义的时候是平等的,即一个函数不能由另一个函数所定义;但是一个函数可以被另一个函数所调用。换句话说,函数可以嵌套调用但是不可以嵌套定义。本章重点集中于常用库函数和自定义函数。,12,6.2.2函数的定义,函数体,合法标识符,函数返回值类型缺省int型无返回值void,函数类型函数名(形参类型说明表)说明部分语句部分return语句,以上红色的为函数的6个元素,函数头部,6.2函数的定义,13,6.2函数的定义,说明参数:参数一般用于一个函数把数据传递给另一个函数(调用者和被调用者之间),实现函数之间的通信。这是很重要的,因为在模块化分工合作的工作模式中,函数参数实现了各个模块之间的通信才使得各个模块得以彼此协作。定义函数的时候,函数的参数是是形式参数,是对一件具体事件可能需要数据的假设。,14,6.2函数的定义,返回值:返回值就好比做了一件事情,这个事情的完成带回来了个结果。在定义函数的时用“返回值类型”指定函数的类型,即函数带回来的值的类型。如果类型标识符为void则表示不需要带回函数值;而如果没有类型标识,则TC2.0默认返回值类型是int(在vc+6.0中则认为默认为void,视乎于编译器的不同而不同)。,15,6.2函数的定义,函数体:一对花括号里面的内容(包括声明部分和语句部分)又被称为函数体,当函数体为空的时候,该函数也被叫做空函数,就是说它什么也不做。,16,例6-1定义一个函数,功能为:计算两个整数的平均数。/*6_1.c函数功能:计算平均数函数入口参数:整型x,存储第一个运算数整型y,存储第二个运算数函数返回值:平均数*/intAverage(intx,inty)intresult;result=(x+y)/2;returnresult;,6.2函数的定义,17,6.2.3函数的参数和返回值1.函数的参数大多数情况下,调用者(主调函数)和被调用者(被调用函数)之间有数据传递关系,也就是说该函数有参数(可以称为有参函数,否则叫做无参函数)。在定义函数时函数名后面括号中的参数称为“形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括号中的参数(可以是一个“表达式”)称为“实际参数”(简称“实参”)。,6.2函数的定义,18,2.返回值通常,希望通过函数调用使主调函数能得到一个确定的值,这就是函数的返回值,也称函数值。,6.2函数的定义,19,6.3.1函数调用的一般形式函数名(实参表列);如果是调用无参函数,则“实参表列”可以没有,但是括号不能省略。如果实参表列包含多个实参,则各个参数间用逗号隔开。实参和形参个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。,6.3函数的调用,20,6.3函数的调用,但应说明,如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左的顺序。许多C版本(例如TruboC+)是按自右至左的顺序求值。,实参与形参个数相等,类型一致,按顺序一一对应;实参表求值顺序视系统而定(TurboC,VC+自右向左)。,21,6.3函数的调用,例6-2调用函数时的数据传递。/*6_2.c*/#includevoidmain()intmax(intx,inty);inta,b,c;scanf(%d,%d,intmax(intx,inty)/*定义有参函数max*/intz;z=xy?x:y;return(z);,运行情况:7,8Maxis8,22,6.3函数的调用,例6-3返回值类型与函数类型不同。/*6_3.c*/#includevoidmain()intmax(intx,inty);floata,bintc;scanf(%f,%f,intmax(floatx,floaty)intz;/*z为实型变量*/z=xy?x:y;return(z);,运行情况:1.5,2.5Maxis2,23,6.3函数的调用,c=max(a,b);(max函数),c=max(x,y);(main函数)intz;z=xy?x:y;return(z);,图6-2函数调用,参数传递,返回(有返回值),函数调用,24,6.3函数的调用,例6-4实参求值的顺序。/*6_4.c*/#includevoidmain()intf(inta,intb);/*函数声明*/inti=2,p;p=f(i,+i);printf(%dn,p);/*函数调用*/,intf(inta,intb)/*函数定义*/intc;if(ab)c=1;elseif(a=b)c=0;elsec=-1;return(c);,在TurboC2.0和TurboC+3.0系统上运行的结果如下:0,25,函数调用过程中形参和实参间数值传递的说明:,在定义函数中指定的形参,在函数尚未被调用时,它们并不占用内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。实参可以是常量、变量或表达式,例如:max(3,a+b);但要求它们有确定的值。在调用时将实参的值赋给形参。在被定义的函数中,必须指定形参的类型。实参与形参的类型应相同或赋值兼容。例6_2中实参和形参都是整型,这是合法的、正确的。如果实参为整型而形参x为实型,或者相反,则按照前面章节介绍的不同类型数值的赋值规则进行转换。例如实参值a为3.5,而形参x为整型,则将实数3.5转换成整数3,然后送到形参b。字符型与整型可以互相通用。在C语言中,实参向形参的数据传递是“值传递”,单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元,如图6-3所示。,6.3函数的调用,26,在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保持并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。例如,若在执行函数过程中x和y的值变为10和15,而a和b仍为2和3,见图6-4。,考虑到参数的传递,我们很容易知道:实参可以是变量表达式,也可以是常量表达式;而形式参数一定是变量表达式。,6.3函数的调用,27,6.3函数的调用,2,2,a,x,2,2,b,y,2,10,a,x,3,15,b,y,图6-3参数值传递,图6-4实参值不变,28,6.3.2函数调用的方式,6.3函数的调用,按函数在程序中出现的位置来分,可以有以下3种函数调用的方式。,1.函数语句把函数调用作为一个语句。这时不要求函数带来返回值,只要求函数完成一定的操作。,29,2.函数表达式函数出现在一个表达式中,这种表达式称为函数表达式。这是要求函数带回一个确定的值以确定参加表达式的运算。例如:C=2*max(a,b);函数max是表达式的一部分,它的值乘2再赋给c。,6.3函数的调用,30,3.函数参数函数调用作为一个函数的实参。例如:m=max(a,max(b,c);其中max(b,c)是一次函数调用,它的值作为max另一次调用的实参。M的值是a、b、c三者中的最大者。又如:printf(“%d”,max(a,b);也是把max(a,b)作为printf函数的一个函数。函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。,6.3函数的调用,31,6.3.3对被调用函数的声明和函数原型,6.3函数的调用,在一个函数中调用另一个函数(即被调用函数)需要具备的条件如下。,1.首先被调用的函数必须是已经存在的函数(库函数或自定义函数)。,32,6.3函数的调用,2.如果使用库函数,还应该在文本开头用#include命令将调用有关库函数时所需用到的信息(这些信息是一些常量和函数原型)“包含”到本文件中来,否则函数将不能正常地得到调用。,例如,前面经常用到过:#include其中“stdio.h”是一个“头文件”。在stdio.h文件中包含了输入输出库函数所用到的一些宏定义信息。如果不包含“stdio.h”文件中的信息,就无法正常使用输入输出库中的函数。同样,使用数学库中的函数,应该用#include。其中,.h是头文件所用的后缀,标志头文件(headfile)。有关宏定义等概念见后面的章节。,33,6.3函数的调用,3.如果使用用户自定义的函数,则如果该函数被定义的位置在调用它的函数(即主调函数)的后面(在同一文件中),应该在主调函数中对被调函数作声明。如果被调用函数的定义出现在主调函数之前,可以不必加以声明。,34,“声明”一词的原文是declaration,过去在许多书中把它译为“说明”,近年来,愈来愈多的计算机专家提出应译为声明,译为“声明”更确切,表意更清楚。声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。,6.3函数的调用,35,#includevoidmain()floatadd(floatx,floaty);/*对被调用函数add的声明*/floata,b,c;scanf(%f,%f,运行情况如下:3.6,6.5sumis10.100000,6.3函数的调用,例6-5编制子函数实现求两个单精度实数的和,main函数调用该函数的时候要先声明。,36,6.3函数的调用,综上所述,函数的原型的一般形式有两种,分别为(1)函数类型函数名(参数类型1,参数类型2,参数类型n);(2)函数类型函数名(参数类型1参数名1,参数类型2参数名2,参数类型n参数名n);,37,函数调用机制(参数及其传递方式)实际参数:调用函数时函数名后面括号中的表达式形式参数:定义函数时函数名后面括号中的变量名,main()floatadd(floatx,floaty);floata,b,c;scanf(%f,%f,6.3函数的调用,38,实参必须有确定的值形参必须指定类型形参与实参类型一致,个数相同若形参与实参类型不一致,自动按形参类型转换函数调用转换形参在函数被调用前不占内存;函数调用时为形参分配内存;调用结束,内存释放,实参单元仍保留并维持原值。也就是说,形参与实参占用不同的内存单元单向传递值传递,6.3函数的调用,39,6.3.4嵌套调用,6.3函数的调用,在C语言中,函数的定义是平行的,不允许进行函数的嵌套定义,即在一个函数体中不允许定义另一个函数。而函数之间的调用可以是任意的,即允许在一个函数体内再调用其他函数,在函数体中再调用其它函数称为函数的嵌套调用。在C语言中,函数的嵌套调用是很常见的,下面给出一个函数嵌套调用的例子。,40,C规定:不可嵌套定义函数,但可以嵌套调用函数,6.3函数的调用,例6-6函数的嵌套调用。,41,main()aia();,aia()ber();,ber()cal();,cal().,图6-5函数的嵌套调用,1,3,5,7,函数main调用了aia,aia调用了ber,ber又调用了cal。其调用过程和返回过程如图所示。,voidaia(),ber(),cal();main()printf(Iminmain.n);aia();voidaia()printf(NowIminaia.n);ber();voidber()printf(NowIminber.n);cal();voidcal()printf(NowImincal.);,6.3函数的调用,42,一个函数直接或间接地调用函数本身,这种调用称为递归调用,前者称为直接递归,后者称为间接递归。递归调用的函数称为递归函数。由于递归非常符合人们的思维习惯,而且许多数学函数及许多算法或数据结构都是递归定义的,因此,递归调用颇具实用价值。,6.3函数的调用,6.3.4递归调用,定义:函数直接或间接的调用自身叫函数的递归调用。,43,intf(intx)inty,z;z=f(y);.return(2*z);,6.3函数的调用,44,6.3函数的调用,下面的程序例题,就是利用函数的递归调用来求阶乘n!。数n的阶乘公式是:n!=n*(n-1)!n的阶乘的展开形式为:n!=n*(n-1)*(n-2)*(n-3)2*1,零的阶乘规定等于1,即0!=1,45,例6-7利用函数的递归调用来求n的阶乘。#includelongfact();/*声明被调用函数返回值为长整型*/main()/*主函数*/inth,i;printf(请输入任一正整数:n);scanf(%d,运行结果:请输入任一正整数:60!等于11!等于12!等于23!等于64!等于245!等于1206!等于720,6.3函数的调用,46,6.3函数的调用,6.3.6程序设计举例,例6-8输出5之内的数字金字塔。,/*6_8.c*/#includeintmain(void)voidpyramid(intn);/*函数声明*/pyramid(5);/*调用函数,输出数字金字塔*/return0;,voidpyramid(intn)inti,j;for(i=1;i=n;i+)/*需要输出的行数*/for(j=1;j=n-i;j+)/*输出每行左边的空格*/printf();for(j=1;j=e)/*当|item|e时,执行循环*/item=flag*1.0/denominator;/*计算第i项的值*/sum=sum+item;/*累加第i项的值*/flag=-flag;/*改变符号,为下一次循环做准备*/denominator=denominator+2;/*分母递增2下一次循*/returnsum*4;,运行结果为:Entere:0.0001pi=3.1418,48,6.3函数的调用,例6-10采用递归方式实现二分查找。二分查找算法是对有序数列进行查找操作的一种有效操作。实际上,这种查找方法是一个递归的过程。,【问题分析】二分查找也是信息处理中常用的一个算法。为了提高这个算法的重用性,单独设置一个函数来实现该算法是适当的。二分查找算法可以描述为:针对一个已经从小到大排序的数据序列,用给定数据key与查找区间中央位置的数据比较,如果相等则表明查找成功;否则,如果key比中央位置的数据小,则在前半个区间用同样的方法继续查找;否则在后半个区间用同样的方法继续查找。因此,这是一个递归的过程。当查找区间的长度为0时,说明查找不成功。【算法描述】在这个程序中,需要按照顺序执行下列操作:输入有序数列;输出有序数列;输入待查找的数值;在输入的有序数列中查找;输出查找结果。,49,6.3函数的调用,#include#include#defineNUM10voidinput(intvalue);voidoutput(intvalue);intsearch(intvalue,intkey,intlow,inthigh);main()intvalueNUM,result,key;input(value);/*输入有序序列*/output(value);/*输出有序数列*/printf(nEnterakey:);/*输入待查找的数值*/scanf(%d,voidinput(intvalue)/*创建有序数列*/inti;for(i=0;iNUM;i+)scanf(%d,voidoutput(intvalue)/*输出数列*/inti;printf(n);for(i=0;ihigh)return-1;/*查找区间为空*/mid=(low+high)/2;/*求中间位置*/if(valuemid=key)returnmid;/*得到查找的数据位置*/if(keyvaluemid)returnsearch(value,key,low,mid-1);/*在下半区*/elsereturnsearch(value,key,mid+1,high);/*在上半区*/,运行结果为:102030405060708090100102030405060708090100Enterakey:90The90isthe8thelement.,50,6.3函数的调用,#include#include#defineNUM10voidinput(intvalue);voidoutput(intvalue);intsearch(intvalue,intkey,intlow,inthigh);main()intvalueNUM,result,key;input(value);/*输入有序序列*/output(value);/*输出有序数列*/printf(nEnterakey:);/*输入待查找的数值*/scanf(%d,运行结果为:122333444455555,51,课堂练习1.输入两个正整数m和n(1m,n500),统计并输出m和n之间的素数的个数以及这些素数的和。要求定义并调用函数prime(m)判断m是否为素数。注:素数就是只能被1和自身整除的正整数,1不是素数,2是素数。2.计算s=1!+2!+3!+.+10!,6.3函数的调用,52,6.4常见库函数,6.4.1库函数概述目前为止,我们所学习的printf(),scanf()等都是ANSIC定义的标准库函数。任意符合ANSIC的编译器,不管它支持什么平台,都必须提供这些函数供用户使用。仅调用ANSIC库函数的程序,具有很好的移植性,可以在多种平台上编译运行。还有数量巨大的第三方函数库,完成ANSIC中不包含,而程序设计者又需要的功能。比如无大小限制的数值计算、微积分运算、网络、数据库和图形界面,等等。有的库可以免费获得和使用,有的则需要购买。,53,6.4常见库函数,使用ANSIC的库函数,只要在程序的开头把该函数原型所在的头文件包含进来即可。比如,你想用sin()函数,通过查联机帮助或用户手册得知该函数在math.h内声明,那么就在程序里面加上:#include只要编译器的配置没有问题,便可直接使用math.h内声明的所有库函数。使用第三方库函数,除了要包含头文件外,往往还需要一些额外配置(比如加载库文件.lib或.dll)。具体情况因库和编译器而异,请查阅相关文档。,54,6.4常见库函数,6.4.2字符与字符串函数TurboC标准函数库具有许多涉及到字符和字符串的函数。在C语言中,字符是一个单字节的值,而字符串是以空字符(0)结尾的字符数组;字符函数要求用头文件ctype.h提供它们的说明,字符串函数需要使用头文件string.h,字符、字符串输入输入函数则是用stdio.h作为它们的头文件。在TurboC中一个可打印的字符(也就是一个能在终端上显示的字符),它们的值在0 x20(空格)到0 x7E之间;控制字符的值在0至0 x1F之间,还包括0 x7F,它们是标准ASCII字符集。另外还有扩展ASCII字符集,其码值在0 x80到0 xFF之间。第4章4.3.6介绍了一些常用的简单字符串函数,学习和使用它们有利于体会函数的概念以及初步掌握系统库函数的运用。,55,6.4常见库函数,6.4.3数学函数TurboC库定义了一些数学函数,我们可以利用它们方便地进行数学运算。这些函数大致可以分成以下几个种类:三角函数双曲函数指数与对数函数其他函数所有这些数学函数的原型都包含在头文件math.h当中,因此用到这些数学库函数的程序都应包含这个头文件。绝大部分数学库函数的返回值都是双精度(double)类型的,这样可以保证数学运算的精度。这里只介绍其中几个简单的函数。,56,6.4常见库函数,1.sin()、cos()及tan()函数调用方式:doublesin(doublearg)doublecos(doublearg)doubletan(doublearg)功能:函数sin()、cos()及tan()分别用于计算弧度参数arg的正弦、余弦及正切值。2.exp()和pow()函数调用方式:doubleexp(doublea)doublepow(doubleb,doublec)功能:函数exp()返回以自然数e为底,函数参数a为幂的指数值;pow()函数则是返回b的C次幂值。,57,6.4常见库函数,3.log()和log10()函数调用方式:doublelog(doublex)doublelog10(doublex)功能:函数log()返回函数参数x的自然对数值;而函数log10()则返回以10为底的x的对数值。4.sqrt()函数调用方式:doublesqrt(doublenum)功能:该函数返回函数参数num的平方根。5.fabs()和abs()、labs()函数调用方式:doublefabs(doublenum),58,6.4常见库函数,intabs(intnum)longlabs(longnum)功能:这些函数均返回函数参数num的绝对值,fabs()、abs()和labs分别适用于浮点型(双精度型)、整型和长整型数的绝对值计算。6.fmod()函数调用方式:doublefmod(doublex,doubley)功能:改函数返回参数之商x/y的余数。,59,6.4常见库函数,6.4.3类型转换函数程序设计中常需要处理“数字串”:有时将它作为字符串来加工,有时使用该“数字串”所代表的数值。类型转换函数将提供帮助,它们的函数原型都包含在头文件stdlib.h中。,60,1.atoi()、atol()和atof()函数调用方式:intatoi(char*str)longaotl(char*str)doubleatof(char*str)功能:函数atoi()、atol()或atof()将函数参数str所指向的字符串分别转换成整型(int)、长整型(long)或双精度实型(double)值,并返回这个值。在str中应包含一个有效的整型、长整型或实型数,如果不是这样,则函数返回值为0。在函数参数str所指向的(数值)字符串中的数值可以以任何字符结尾,但不能是整型(或实型)数的有效部分。例如函数调用atoi(“12.3”)将返回整数值12,atof(“1.2ABC”)将返回实数值1.2。,6.4常见库函数,61,6.4常见库函数,2.itoa()和ltoa()函数调用方式:char*itoa(intnum,char*str,intradix)char*ltoa(longnum,char*str,intradix)功能:函数itoa()或ltoa()将整型或长整型数num转换成与其等价的字符串,且将其结果放在由str所指向的字符串中。字符串输出的进制由参数radix确定,它可以在2到36之间的范围内变化。函数返回str所指向的指针。Str所指向的字符串应有足够的长度来保存转换后的结果。,62,6.4常见库函数,6.4.4基本屏幕控制函数TurboC2.0的字符屏幕函数主要包括文本窗口大小的设定、窗口颜色的设置、窗口文本的清除和输入输出等函数。,63,例6-11在屏幕上显示7个颜色不同的窗口,#include#includemain()inti;textbackground(0);/*设置屏幕背景色*/clrscr();/*清除文本屏幕*/for(i=1;i8;i+)window(10+i*5,5+i,30+i*5,15+i);/*定义文本窗口*/textbackground(i);/*定义窗口背景色*/clrscr();/*清除窗口*/getch();,运行结果:,6.4常见库函数,64,例6-12在屏幕上显示7个颜色不同的窗口,且在每个窗口上都用文字表明该窗口的颜色。,#include#includeintmain()inti;char*c=BLACK,BLUE,GREEN,CYAN,RED,MAGENTA,BROWN,LIGHTGRAY;textbackground(0);/*设置屏幕背景色*/clrscr();/*清除文本屏幕*/for(i=0;iy?x:y;return(z);13,运行结果为:13,83,定义外部变量A和B的位置在函数main之后,因此在main函数中不能引用外部变量A和B。在main函数的第3行用extern对A和B进行声明,表示A和B是已经定义的外部变量。这样在main函数中就可以合法地使用外部变量A和B了。如果不作extern声明,编译时出错,系统不会认为A和B是已经定义的外部变量。一般作法是将外部变量的定义放在引用它的所有函数之前,这样就可以避免在函数中多加一个extern声明了。用extern声明全局变量时,类型名可以写也可以省写。例如,“externintA,B”也可以写成:“externA,B;”。,6.6变量的作用域(结合变量的性质),84,b)在一个文件外声明外部变量一个C程序可以由一个或多个源程序文件组成。如果程序只由一个源文件组成,使用全局变量的方法前面已经介绍。如果程序由多个源程序文件组成,那么一个文件中想引用另一个文件中已经定义的外部变量,有什么办法呢?,6.6变量的作用域(结合变量的性质),85,如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一文件中定义全局变量Num,而在另一个文件中用extern对Num做“外部变量声明”。即“externNum;”。在编译和连接时,系统会由此知道Num是一个已在别处定义的外部变量,并将在另一个文件中定义的外部变量的作用域扩展到本文件,在本文件中可以合法地引用外部变量Num。,6.6变量的作用域(结合变量的性质),86,文件file1.c的内容为:#includeinta;/*定义外部变量*/voidmain()intpower(int);/*函数声明*/intb=3,c,d,m;printf(enterthenumberaanditspowerm:n);scanf(%d,%d,6.6变量的作用域(结合变量的性质),例6-17用extern将全局变量的作用域扩展到其他文件。(外部变量)本程序的作用是给定b的值,输入a和m,求a*b和a*m的值。,说明:如何组织和运行多文件程序(即项目),请参考6.9和6.10。,文件file2.c的内容为:externa;/*声明a为一个已定义的外部变量*/intpower(intn)inti,y=1;for(i=1;i=n;i+)y*=a;return(y);,87,6.6变量的作用域(结合变量的性质),2.静态全局变量(用static声明全局变量)有时在程序设计中希望某些外部变量只限定于被本文件引用,而不能被其他文件引用。这是可以在定义外部变量时加一个static声明。,88,file1.cstaticintA;voidmain(),file2.cexternintA;voidfun(intn)A=A*n;,6.6变量的作用域(结合变量的性质),例如:,在file1.c中定义了一个全局变量A,但它用static声明,因此只能用于本文件,虽然在file2.c文件中用了“externintA;”,但file2.c文件中无法使用file1.c中的全局变量A。,89,6.6.4总结虽然局部变量和全局变量是根据作用域来对变量分类的,但是他们的生命期的规律也很简单。在下一节中我们将针对它们的生命期(即存在性)做出讨论。下面以表格的形式对本结内容作一个全面的总结。,6.6变量的作用域(结合变量的性质),90,6.6变量的作用域(结合变量的性质),表6-1变量的作用域,91,6.7变量的生命期(结合变量的性质),变量的生命期是指变量什么时候开始占据内存空间,又在什么时候其内存空间被释放。简单地说就是变量什么时候开始存在,什么时候被销毁(回收)。,92,对于局部变量,如果生命期是当前源文件,我们说该变量的存在性是静态的(当前源文件中的程序结束才释放)。如果生命期是定义该变量的函数(函数调用结束就释放),我们说该变量的存在性是动态的。因此对于局部变量,我们还可以分成两类:动态局部变量和静态局部变量。,6.7变量的生命期(结合变量的性质),93,对于全局变量,其生命期一定是整个程序,无论该程序是由单个还是多个源文件组成。全局变量的生命期规律是单一而简单的,因此本节着重讨论局部变量的生命期。,6.7变量的生命期(结合变量的性质),94,6.7.1动态局部变量函数中的局部变量如果用关键字auto限定,则是动态局部变量变量(也称自动变量)。实际上关键字auto可以省略,因此实际上前面我们所学程序中大多数变量属于自动变量。动态局部变量的生命期为:在调用该函数(定义该局部变量的函数)时,执行定义该变量的语句后,为变量开辟存储单元,函数调用结束时就自动释放该变量。,6.7变量的生命期(结合变量的性质),95,例如:intf(inta)/*定义f函数,a为形参*/intb,c=3;/*定义b、c为动态变量*/其中,形参a、变量b、c都是动态变量。执行完f函数后,自动释放a、b、c所占的存储单元。,6.7变量的生命期(结合变量的性质),96,例6-18利用函数实现交换两个变量的值。,/*6_18.c*/voidchange(intm,intn)inttemp;temp=m;m=n;n=temp;voidmain()inta=1,b=2;change(a,b);printf(a=%d,b=%d,a,b);a=1,b=2,运行结果为a=1,b=2,6.7变量的生命期(结合变量的性质),97,6.7变量的生命期(结合变量的性质),显然,a和b是定义在main函数中的动态局部变量,m和n是定义在change函数中动态局部的变量。当change函数被调用的时候,变量a和b虽然存在,但是在change函数中并不能访问变量a和b(请综合考虑变量的生命周期和作用域),在change中交换了m和n。当函数change函数调用结束时,变量m和n被释放,随后main结束,变量a和b被释放。可见变量a和b并没有被交换,实际上在函数change中连访问a和b的机会都没有!,98,现在我们应该更加理解函数的参数传递是“单向值传递”了,同时可以发现函数对形参的改变并不能反映到实参本身。,通过这里的学习,读者应该对例6.2的前因后果有更清楚的认识。,6.7变量的生命期(结合变量的性质),99,6.7.2静态局部变量函数中的局部变量,如果被关键字static限定,则是静态变量(即静态局部变量)。静态局部变量的生命期为:该变量在函数被第一次调用的时候开始占据内存单元,直到程序结束才释放。,6.7变量的生命期(结合变量的性质),100,不难分析出它具有两个特性:除了第一次之后任何一次对函数的调用,定义该变量的语句并不执行,因为该变量已经存在了,否则就会出现对同一变量重复定义而出现错误。因此定义变量时的初始值只对第一次调用产生影响。第n+1次调用时函数时,该变量的值保留了第n次调用函数结束时该变量的值。,6.7变量的生命期(结合变量的性质),101,注意:定义静态局部变量的函数调用结束后,该变量仍然存在,因为其生命期是整个程序。但是由于其作用域是局部的,因此尽管它仍存在(存活),可我们是不能访问它的。,6.7变量的生命期(结合变量的性质),102,例6-19考察静态局部变量的值。/*6_19.c*/#includevoidmain()intf(int);inta=2,i;for(i=0;i3;i+)printf(%d,f(a);intf(inta)autointb=0;/*auto可以省略*/intc=3;b=b+1;c=c+1;return(a+b+c);789,运行结果为789,6.7变量的生命期(结合变量的性质),分析:在第1次调用f函数时,b的初值为0,c的初值为3,第1次调用结束时,b=1,c=4,a+b+c=7。由于c是静态局部变量,在函数调用结束后,它并不释放,仍保留c=4。在第2次调用f函数时,b的初值为0,而c的初值为4(上次调用结束时的值),见图6-6。先后3次调用f函数时,b和c的值见表6-1所示。,103,第一次调用开始,0,3,b,c,第一次调用结束,1,4,第二次调用开始,0,4,图6-6程序分析,6.7变量的生命期(结合变量的性质),104,表6-2三次调用前后各变量的值,6.7变量的生命期(结合变量的性质),105,总结本节讲述了变量的生命期,下面以表格的形式对本结内容作一个全面的总结。各种变量的生命期如表6-4中所示。,6.7变量的生命期(结合变量的性质),106,表6-3变量的生命期,6.7变量的生命期(结合变量的性质),107,课堂练习例6-14中函数f中的第一行代码改为autointb;b=0;那么该程序运行结果会发生变化吗?把函数f中的第二行代码改为staticintc;c=3;呢?有种说法是“intb=0;”和“intb;b=0;”一定是等价的,这对吗?,6.7变量的生命期(结合变量的性质),108,前面讨论了变量的有效范围问题,对于函数,它的有效范围又是如何的呢?即我们可以在哪里有效地调用它?这就是本节要讨论的问题。,函数在本质上是外部的,函数名同变量名一样遵循作用域规则。C语言不允许函数的嵌套定义,各个函数之间是平行并列的关系,互相之间可以调用,因而函数具有全局的有效范围。但每个函数定义都附属于某个程序文件,类似于外部变量,我们可以决定该函数的可视范围是所有程序文件还是仅仅限于该函数定义所处的文件,从而对应着外部函数和内部函数。,6.8内部函数和外部函数,109,1.外部函数在定义函数的时候,如果冠以关键字extern,则表示该函数是一个外部函数,如:externvoidmyfun().这样定义的函数myfun()的可视范围是所有的程序文件,即它可以被应用程序中的任何其他函数所调用而不论这个函数是否与函数myfun()所处同一个程序文件。如果定义函数时省略关键字extern,则隐含为外部函数,前面学习中所遇到的情况均如此。与外部变量一样,当在其他程序文件中调用此函数之前,应该先用关键字extern说明该函数是一个外部函数,此说明可以放在调用它的函数体说明部分,也可是在调用它的函数定义前面。,6.8内部函数和外部函数,110,2.内部函数在定义函数时,如果冠以关键字static,表示该函数为一个内部函数,如:staticvoidmyfun()这样定义的函数myfun()的可视范围是定义它的程序文件,即该函数被限制为仅能被本程序文件中的函数所调用,如果在不同的文件中有同名的内部函数,它们互不干扰,这样可以方便大型应用程序的编写工作。通常仅由某程序员负责的并且有互相联系的外部变量及函数放在一个文件中,并用关键字static来定义使之本地化,这个文件也就相应地独立了,减少了程序出错的机会。,6.8内部函数和外部函数,111,什么是项目呢?项目就是相关程序文件的集合,通俗地说就是一个有着多个文件的程序。前面讲述模块化分工合作的思想的时候已经提到,文件也是C语言中模块化的单元。因此可以考虑把各个模块分别在各个文件中进行实现,那么这多个文件都属于实现某个特定功能的程序,我们可以说这些文件是逻辑相关的,那么这些文件的集合就是一个项目。,6.9*多文件程序项目*,112,前面讲过,创建和运行C程序的过程是:编辑源文件(.C)编译成为目标文件(.OBJ)最后链接成为可执行文件(.EXE)那么对于一个项目来说这个过程又是怎样的呢?首先各个程序员根据模块化分工,各自编写各自那一块的任务,生成了自己的源文件,分别编译成为各个.OBJ文件,最后link成为.EXE可执行文件,这就是分块编译。分块编译的优点在于修改一个源程序的代码后,只是对这一个文件进行编译,而不必对整个程序中的所有文件都编译一遍,这样可以节省大量的时间。总之,把程序用项目的多文件形式进行实现的方法,就是充分利用模块化的很好的手段。,6.9*多文件程序项目*,113,注:如果一个程序本身很小,为了体现模块化分工的思想,编写了多个函数又甚或把各个函数放在不同的源文件中,但是一个函数中只有几行代码。这样做出发点是好的,但效果是得不偿失的。所以程序模块化为函数、项目文件的时候要根据实际情况具体考虑,不能生搬硬套。,6.9*多文件程序项目*,114,6.10.1创建并运行项目例6-20编写一个求和函数intsumto(intx,inty),功能是求从x到y的所有整数的和。并把它做成一个库函数供编程者使用。,6.10*怎样创建项目、自己的库函数*,115,步骤:分别编辑myfun.c、myfun.h和mypro.c。源文件myfun.c中是对实现题目要求的sumto函数的定义,而myfun.h则是sumto函数的原型。源文件mypro中是main函数的定义,是项目的主文件,在main函数中调用了myfun.c中的sumto函数,因此在该源文件中要通过include包含头文件myfun.h;文件内容如图6-7中所示。对myfun.c、mypro.c进行编译,生成对应的目标文件myfun.obj、mypro.obj。编辑项目文件my.prj,在里面列出项目清单如图6-7(d)中所示。利用TC生成my.exe。首先点击TC的Project菜单中的Projectname项,在里面输入my.p
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 部编版一年级语文上册开学第一课
- 玻璃幕墙工程承包合同
- 黄蓝扁平风志愿者服务模板
- 领导力发展成为高效能领导者
- 革新理念开启新纪元-新能源车技术变革的研究与应用展望
- 音乐产业中的数据挖掘与价值发现
- 颠覆传统模式新零售技术推动商业体验升级
- 顾客体验为核心的零售营销策略优化
- 防灾减灾安全教育培训
- 青少年传统文化与艺术培训的未来展望
- 高标准农田泵房使用协议书(2篇)
- 第45届世界技能大赛烹饪(西餐)项目全国选拔赛技术工作文件
- 科幻小说阅读(原卷版)-2023年浙江中考语文复习专练
- 化妆品代加工保密协议
- 2024年高等教育法学类自考-00229证据法学考试近5年真题附答案
- 新媒体环境下的品牌策划学习通超星期末考试答案章节答案2024年
- 股东之间股权转让合同协议书(2篇)
- 人体器官讲解课件
- 惠州市惠城区2024-2025学年数学四年级第一学期期末调研模拟试题含解析
- DB3301-T 0256-2024 城市生态河道建设管理规范
- 2024中考满分作文9篇
评论
0/150
提交评论