生存期作用域与可见域_第1页
生存期作用域与可见域_第2页
生存期作用域与可见域_第3页
生存期作用域与可见域_第4页
生存期作用域与可见域_第5页
已阅读5页,还剩28页未读 继续免费阅读

下载本文档

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

文档简介

生存期作用域与可见域第1页,共33页,2023年,2月20日,星期一19.1内存分配变量名、函数名等都对应着内存中的一块区域,那这些实体在内存中是如何存放的呢,程序又是如何使用这些变量的,首先从C程序内存分配入手,一步步回答这些问题。第2页,共33页,2023年,2月20日,星期一19.1.1内存分区一个由C编译的程序占用的内存大致分为以下几部分:栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。堆区(heap):一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收。全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。常量区:字符串常量和其他常量的存储位置,程序结束后由操作系统释放。程序代码区:存放函数体的二进制代码。第3页,共33页,2023年,2月20日,星期一19.1.2变量的存储类别C语言中,变量的存储类别大致分为4种:auto(自动)、register(寄存器)、static(静态)和extern(外部)。其中,auto和register变量属于自动分配方式,而static和extern变量属于静态分配方式。不同分配方式下,变量的生存期、作用域和可见域各不相同。按作用域分,变量可分为局部变量和全局变量,所谓局部变量,是指在函数内部定义的变量,局部变量仅在定义它的函数内才能有效使用,其作用域仅限在函数内,即从变量定义的位置开始,到函数体结束。通常,编译器不为局部变量分配内存单元,而是在程序运行中,当局部变量所在的函数被调用时,系统根据需要临时为其分配内存。当函数执行结束时,局部变量被撤销,占用内存被收回。在函数外定义的变量称为全局变量,也称外部变量,全局变量的作用域较广,全局变量不属于任何一个函数,理论上可被其作用域中的所有函数访问,因此,提供了一个不同函数间联系的途径,使函数间的数据联系不只局限于参数传递和return语句。全局变量一经定义,编译器会为其分配固定的内存单元,在程序运行期间,这块内存单元始终有效,一直到程序执行完毕才由操作系统收回该块内存。第4页,共33页,2023年,2月20日,星期一19.1.3生存期通俗地讲:生存期指的是在程序运行过程中,变量从创建到撤销的一段时间。生存期的长短取决于前面所讲的存储方式,对于自动分配(栈分配),变量与其所在的代码块共存亡;对于静态分配(编译器预分配),变量与程序共存亡,程序开始执行时即已存在,一致到程序运行完毕退出后才撤销;对于动态存储的内存块(注意:不是指向该内存块的指针),由程序员决定其生存期。对程序代码区的函数、常量区的字符串常量和其他常量等、结构体和共用体的定义等来说,生存期的讨论没有意义,因为它们都是与程序共存亡的。第5页,共33页,2023年,2月20日,星期一19.1.4作用域与可见域在程序代码中,变量有效的范围(源程序区域)称为作用域,能对变量、标识符进行合法的访问的范围(源程序区域)称为可见域,可以这样说,作用域是变量理论上有效的区域,而可见域是变量实际有效的区域,可见域是作用域的子集。可以将C语言的作用域分为以下几类:(1)块作用域自动变量(auto、register)和内部静态变量(static)具有块作用域,在一个块内声明的变量,其作用域从声明点开始,到该块结束为止。函数定义中声明的形参,其作用域限定在该函数体内,与其他函数中声明的同名变量不是一回事,允许在不同的函数中使用相同的变量名,编译器将为这些变量分配不同的存储单元,不会混淆。(2)文件作用域外部静态变量(static)具有文件作用域,从声明点开始到文件末尾,此处所指的文件是编译基本单位—c文件。(3)全局(程序)作用域全局变量(extern)具有全局作用域,只要在使用前对其进行声明,便可在程序(由若干个文件组成)的任意位置使用全局变量。第6页,共33页,2023年,2月20日,星期一19.2auto变量函数的形参及代码块中定义的变量都属于auto变量,这是C语言中应用最广的一种变量,这类变量是栈分配的,是动态分配存储空间的。举函数形参为例,当调用该函数时,为形参分配存储空间,当函数调用结束时,就自动释放这些存储空间。对代码块中定义的变量(包含函数中定义的变量),当执行到变量声明语句时,系统为这些auto变量分配空间,当程序流程离开代码块时,这些变量被自动撤销,其占用的内存空间被释放。第7页,共33页,2023年,2月20日,星期一19.2.1定义格式自动变量的定义格式为[auto]数据类型变量1[=初始化表达式],变量2[=初始化表达式]……;其中,方括号表示可以省略,此处变量不仅指普通内置类型的变量,还包括数组、结构体和指针等复合结构。C语言默认所定义的变量是auto变量,在以前所举例子中,函数和代码块中的局部变量并没有使用关键字auto,这实际上是遵循了C语言的默认规定,举个例子来说,在一个函数中,如下定义:inta;floatb:自动被C编译器解释为:autointa;autofloatb;第8页,共33页,2023年,2月20日,星期一19.2.2作用域和生存期auto变量的作用域和生存期都局限在定义它的代码块中,所谓代码块,是指用两个花括号包裹起来的代码行,函数只是代码块的一种,常见的代码块还有if结构、for结构等等,哪怕只是两个成对花括号,也能构成一个独立代码块。此外,结合“先声明,后使用”的原则,可知,auto变量的作用域和生存期对应着从定义到所在代码块结束这块时空区域。来看下面的函数:1 intfunc(intm,intn)2 {3 intx; /*等价于autointx;*/4 {5 inta,b,c; /*等价于autointa,b,c;*/6 ……7 }8 inty,z; /*等价于autointy,z;*/9 ……10 return0;11 }第9页,共33页,2023年,2月20日,星期一19.2.3屏蔽代码块可以嵌套应用形成一定的层次结构,那内外层代码块中可否定义同名变量呢?如果可以,这些同名变量有什么关系呢?先来看一段示例:第10页,共33页,2023年,2月20日,星期一19.2.4重复定义auto变量不能重复定义,所谓重复,是指在同一代码块中,出现两个同名变量。此处所指的同一代码块,不包括屏蔽的情况。下面的代码就犯了重复定义的错误:if(……){intx,y;doublex,y;}并列层次的代码块中可以出现同名变量而不会引起混淆,最普遍的一个例子就是函数,由于所有的函数都是在外部定义的,包括main函数在内的所有函数都是并列的,因此,函数A内定义的auto变量在函数B内是完全不可见的,即使两个函数中定义了同名变量,编译器也能很好地将其区分开,这大大方便了函数的编写。第11页,共33页,2023年,2月20日,星期一19.2.5初始化编译器并不会自动为auto变量初始化,这项工作必须在变量定义时由程序员显式完成,否则,变量的值是随机不确定的。不论是指针还是普通变量,时刻提醒自己注意初始化,能有效防止一些稀奇古怪错误的发生。第12页,共33页,2023年,2月20日,星期一19.3register变量一般来说,CPU访问内部寄存器的速度大大高于访问内存的速度,因此,有人提议,能否将一些应用频繁的变量放在CPU的通用寄存器中,这样,在使用该变量时便不必再访问内存,直接从寄存器中取,将大大提高程序运行的效率,因此,C语言引入了register变量,称为寄存器变量。第13页,共33页,2023年,2月20日,星期一19.3.1定义格式寄存器变量的定义格式为:register数据类型变量1[=初始化表达式],变量2[=初始化表达式]……;和auto变量一样,register变量也属于局部变量。只能在函数体内定义register变量,CPU使用寄存器中数据的速度要远远快于使用内存中的数据速度,因此,应用好CPU内的寄存器可以大大提高程序的运行效率和速度。但是,CPU中寄存器的数量有限,所以,通常是把使用频繁的变量定义为寄存器变量。第14页,共33页,2023年,2月20日,星期一19.3.2使用举例来看一个计算的近似值的例子,求解的一个近似公式如下:为了达到较高精度,需要进行的循环次数较多,为提高效率,可将循环控制变量定义为寄存器变量,如所示:第15页,共33页,2023年,2月20日,星期一19.4extern变量extern变量又称全局变量,放在静态存储区,所谓全局,是说该变量可以在程序的任意位置使用,其作用域是整个程序代码范围内,和auto变量不同的是,extern变量有定义和声明之分。第16页,共33页,2023年,2月20日,星期一19.4.1全局变量定义全局变量定义的基本格式为:extern类型变量名=初始化表达式;此时,初始化表达式不可省略,此指令通知编译器在静态存储区中开辟一块指定类型大小的内存区域,用于存储该变量。下列语句创建了一个初始值为100的int型全局变量m:externintm=100;C语言规定,只要是在外部,即不是在任何一个函数内定义的变量,编译器就将其当作全局变量,无论变量定义前是否有extern说明符。也就是说,只要在函数外部书写下述形式即可:intm=100;当全局变量定义时,当且仅当省略了extern时,初始化表达式才可省略,系统默认将其初始化为0,对于定义的全局数组或结构,编译器将其中的每个元素或成员的所有位都初始化为0。第17页,共33页,2023年,2月20日,星期一19.4.2全局变量声明全局变量是与程序共存亡的,因此,全局变量的生存期不是关心的重点,经常讨论的是其作用域与可见域。全局变量的作用域是整个程序,不论该程序由几个文件组成,理论上,可以在程序的任意位置使用定义的全局变量,但在特定位置处,全局变量是否可见取决于是否对其进行了合理声明。不进行任何声明时,全局变量的可见域为从定义到本文件结束,来看一段示例代码:第18页,共33页,2023年,2月20日,星期一19.4.3可见域声明扩展了全局变量的可见域,但将可见域扩展到了什么程度呢?实际上,声明的位置在很大程度上决定了其可见域,读者可以在的基础上做个实验,看在main函数中能否输出x和y呢?编译链接后,系统报错,找不到x和y,这是因为在main函数中只声明了全局变量z,却没有声明全局变量x和y。在块内对声明全局变量时,其可见性仅局限于该块,在该块外部访问该全局变量时,编译器会给出变量未定义的错误提示,来看下面一段:第19页,共33页,2023年,2月20日,星期一19.4.4屏蔽在auto变量一节中提到,内层代码块中声明的变量将屏蔽外层代码块中声明的同名变量,屏蔽准则对全局变量同样适用,见示例:第20页,共33页,2023年,2月20日,星期一19.4.5利与弊全局变量带来的好处是显而易见的:(1)为函数间数据传递提供了新的途径,函数返回值仅仅只能有1个,很多情况下,这不能满足要求,而全局变量可用于更多处理结果。(2)利用全局变量可以减少形参和实参的个数,省去函数调用时的时空开销,提高程序运行的效率。但是,全局变量在程序执行期间都有效,一直占据着存储单元,不像局部变量等在调用执行期间临时占用内存,退出函数时便将其释放。最大的问题是降低了函数的封装性和通用性,由于函数中存在全局变量,因此,如果想把函数复用在其他文件中,必须连所涉及的全局变量一块移植过去,容易引发各种问题,造成程序不可靠。全局变量使得函数间独立性下降,耦合度上升,可移植性和可靠性变差。综上,在可以不使用全局变量的情况下应尽量避免使用全局变量。第21页,共33页,2023年,2月20日,星期一19.5static变量static变量又称静态变量,是种特殊的变量,近乎一种折衷。此类变量也存放在静态存储区,一旦为其分配了内存,则在整个程序执行期间,他们将固定地占有分配给它们的内存。和extern变量不同的是,static变量只有定义,没有声明。第22页,共33页,2023年,2月20日,星期一19.5.1定义格式static变量的定义格式为:static数据类型变量1[=初始化表达式],变量2[=初始化表达式]……;与extern变量都是全局变量不同,static变量有静态全局变量和静态局部变量之分:静态局部变量,除了生存期是整个程序执行期间(与程序共存亡)外,其作用域与可见域与普通auto变量完全一样。静态全局变量和extern变量的不同体现在作用域上,extern作用域是本程序的所有源代码文件,只要在一个文件中定义,在其他文件中使用时只要对其进行声明即可。而静态全局变量只有定义,没有声明,其作用域仅限于从定义位置起到本文件结束的一段代码区域,不能被其他文件中的函数所使用。静态全局变量实际上是对extern变量破坏封装性和可靠性的一种改良。当省略初始化表达式时,编译器自动以0初始化静态变量。对于数组或结构,编译器将其中的每个元素或成员的所有位都初始化为0。第23页,共33页,2023年,2月20日,星期一19.5.2静态局部变量静态局部变量定义在函数内部,可以当成是种不会被撤销的自动变量来使用,其可见域、作用域与普通的自动变量完全一致。常使用静态局部变量在函数调用间歇保存某些变量的值,如所示:第24页,共33页,2023年,2月20日,星期一19.5.3静态全局变量比较普通的extern全局变量和静态全局变量,二者在存储方式上没有不同,都分配在静态存储区,与程序共存亡。二者的区别在于extern变量的作用域是整个源程序,当源程序由多个文件组成时,extern全局变量在各个文件中都是有效的,而静态全局变量则限制了其作用域,只在定义该变量的源文件内有效,同一源程序的其他源文件中不能使用它。第25页,共33页,2023年,2月20日,星期一19.5.4extern变量和static变量的初始化在编译时,编译器为extern变量和static变量静态分配内存,因此,如果程序员不对其进行显式初始化,编译器将默认以0填充变量结构。如果要对变量进行显式初始化,只能使用常量表达式来初始化extern变量和static变量,常量表达式包括直接常量、const常量、枚举常量和sizeof()运算符,下面的初始化代码都是合法的:intnum; /*编译器自动将num初始化为0*/intnum1=20; /*直接常量*/constintx=10;intnum2=x; /*const常量*/intnum3=sizeof(double); /*sizeof运算符*/第26页,共33页,2023年,2月20日,星期一19.6函数的作用域与可见域C语言中的函数都是独立的代码块,以二进制形式存储在程序代码区,函数名可以看成是指向其对应代码块的常量指针。以前接触到的函数都是外部的,类似于extern变量的用法,只要在一个文件中定义一次,并通过声明使其可见,便可以被源程序中其他源文件中的其他函数调用,实际上,也可定义只能在本文件调用的内部函数。第27页,共33页,2023年,2月20日,星期一19.6.1内部函数所谓内部函数,是指一个源文件中定义的函数只能被本文件中的函数调用,而不能被其他源文件中的函数调用。定义一个内部函数的方法是在其函数前使用关键字static,格式如下:static返回类型函数名(参数表){函数体}来看一段示例:第28页,共33页,2023年,2月20日,星期一19.6.2外部函数如果一个函数可以被其他源文件中的函数调用,称为外部函数,用关键字extern修饰,定义格式为:[extern]返回类型函数名(参数表){函数体}中括号表示可省略,即C语言默认所定义的函数是外部的,这就是本书前面所举例子函数定义都直接采用“返回类型函数名(参数表)”的原因。和外部变量一样,在源程序中,外部函数只能定义一次,其作用域为所有的源程序文件,但其默认可见域为从函数定义位置起到该源文件结束,如果要在其他源文件中调用外部函数时,需要对该函数进行声明以扩展其可见域。可见域扩展的程度同样取决于声明的位置,如果是在代码块中声明的,扩展的范围是从声明位置起到代码块结束,如果是外部声明的,扩展范围是从声明位置起到该文件结束。声明的格式为:[extern]返回类型函数名(参数表);第29页,共33页,2023年,2月20日,星期一19.7结构体定义的作用域与可见域在结构体和共用体一章中曾经讨论过结构体定义位置

温馨提示

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

评论

0/150

提交评论