⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 教学--第十五章 存储类型、作用域、可见性和生存期.htm

📁 电子书籍(白话c++)非常好用的一本书
💻 HTM
📖 第 1 页 / 共 3 页
字号:
      <P> </P>
      <P>说明:在Unit1.cpp 文件中用到了外部变量:a, a在Unit2.cpp文件内定义。</P>
      <P> </P>
      <P>现在,我们要限定 Unit2.cpp 里的变量 a 只能在 Unit2.cpp 内可以使用:</P>
      <P> </P>
      <P>Unit2.cpp 文件:</P>
      <P>...</P>
      <P><B>static </B>int a;</P>
      <P> </P>
      <P>我们为 a 的定义加了一个修饰:static。现在再编译,编译器提示一个链接错误,我们在本章前面说过的:“变量 a 没有定义”:</P>
      <P><IMG height=20 src="教学--第十五章 存储类型、作用域、可见性和生存期.files/ls15.h8.gif" 
      width=642 border=0></P>
      <P> </P>
      <P> </P>
      <P>静态函数的例子类似:</P>
      <P> </P>
      <P>Unit1.cpp 文件:</P>
      <P> </P>
      <P>... </P>
      <P>void func();</P>
      <P>int main(int argc, char* argv[])</P>
      <P>{</P>
      <P>&nbsp;&nbsp; func();</P>
      <P>&nbsp;&nbsp; return 0;</P>
      <P>}</P>
      <P> </P>
      <P>Unit2.cpp 文件:</P>
      <P>int i;</P>
      <P><B>static</B> void func()</P>
      <P>{</P>
      <P>&nbsp; i = 100;</P>
      <P>}</P>
      <P>按Ctrl+F9,得到以下链接错误:</P>
      <P> </P>
      <P><IMG height=19 src="教学--第十五章 存储类型、作用域、可见性和生存期.files/ls15.h7.gif" 
      width=653 border=0></P>
      <P> </P>
      <P>又是两个制造错误例子,不要偷懒,务必亲手制造出这两个错误,并且再改正后,才继续看下面的课程。千万不要仅满足于“看得懂”就不动手。那样绝对不可能学会编程。</P>
      <P> </P>
      <P>static 还有一种用法,称为函数局部静态变量,作用和这里的“全局静态”关系不大,我们在后面的“生存期”中会讲到。</P>
      <P> </P>
      <P>由于静态变量或静态函数只在当前文件(定义它的文件)中有效,所以我们完全可以在多个文件中,定义两个或多个同名的静态变量或函数。</P>
      <P> </P>
      <P>比如在A文件和B文件中分别定义两个静态变量a:</P>
      <P> </P>
      <P>A文件中:</P>
      <P>static int a;</P>
      <P> </P>
      <P>B文件中:</P>
      <P>static int a;</P>
      <P> </P>
      <P>这两个变量完全独立,之间没有任何关系,占用各自的内存地址。你在A文件中改a的值,不会影响B文件中那个a的值。</P>
      <P> </P>
      <H3><A name=15.2>15.2</A> 作用域和可见性</H3>
      <P> </P>
      <P>作用域和可见性可以说是对一个问题的两种角度的思考。</P>
      <P> </P>
      <P>“域”,就是范围;而“作用”,应理解为“起作用”,也可称为“有效”。所以作用域就是讲一个变量或函数在代码中起作用的范围,或者说,一个变量或函数的“有效范围”。打个比方,就像枪发出的子弹,有一定的射程,出了这个射程,就是出了子弹的“有效”范围,这颗子弹就失去了作用。</P>
      <P> </P>
      <P>代码中的变量或函数,有的可以在整个程序中的所有范围内起作用,这称为“全局”的变量或函数。而有的只能在一定的范围内起作用,称为“局部”变量。</P>
      <P> </P>
      <H4><A name=15.2.1>15.2.1</A> 局部作用域</H4>
      <P> </P>
      <P>我们在 5.1.3 “如何为变量命名”这一小节中讲到: <B>“不能</B>在同一作用范围内有同名变量”。因此,下面的代码是错误的:</P>
      <P>...</P>
      <P>int a;&nbsp;&nbsp;&nbsp; //第一次定义a</P>
      <P>int b;</P>
      <P>b = 2*a;</P>
      <P>int a;&nbsp;&nbsp; //错误:又定义了一次a</P>
      <P>...</P>
      <P> </P>
      <P>那么,在什么情况下,变量属于不同的作用范围呢?我们这里说的是第一种:<B>一对{}括起来的代码范围,属于一个局部作用域</B>。如果这个局部作用域包含更小的子作用域,那么子作用域的具有较高的优先级。在一个局部作用域内,变量或函数从其声明或定义的位置开始,一直作用到该作用域结束为止。</P>
      <P> </P>
      <P>例一:变量只在其作用域内有效</P>
      <P> </P>
      <P>void func()</P>
      <P>{</P>
      <P>&nbsp;&nbsp; int a;&nbsp; </P>
      <P>&nbsp;&nbsp; </P>
      <P>&nbsp;&nbsp; a = 100;</P>
      <P> </P>
      <P>&nbsp;&nbsp; cout &lt;&lt; a &lt;&lt; endl; //输出a的值</P>
      <P>}</P>
      <P> </P>
      <P>int main(int argc, char* argv[])</P>
      <P>{</P>
      <P>&nbsp;&nbsp; cout &lt;&lt; a &lt;&lt; endl;&nbsp; // &lt;-- 错误: 
      变量a未定义</P>
      <P> </P>
      <P>&nbsp;&nbsp; return 0;</P>
      <P>}</P>
      <P> </P>
      <P> </P>
      <P>说明:在函数 func()中,我们定义了变量a,但这个变量的“作用域”在 } 
      之前停止。所以,出了花括号以后,变量a就不存在了。请看图示:</P>
      <P><IMG height=241 src="教学--第十五章 存储类型、作用域、可见性和生存期.files/ls15.h9.gif" 
      width=353 border=0></P>
      <P> </P>
      <P>结论:在局部作用域内定义的变量,其有效范围从它定义的行开始,一直到该局部作用域结束。</P>
      <P>在局部作用域内定义的变量,称为“局部变量”。</P>
      <P> </P>
      <P>上例中的局部作用域是一个函数。其它什么地方我们还能用到{}呢?很多,所有使用到复合语句的地方,比如:</P>
      <P> </P>
      <P>//if 语句</P>
      <P>if( i&gt; j)</P>
      <P>{</P>
      <P>&nbsp;&nbsp;&nbsp; int a;&nbsp;&nbsp; </P>
      <P>&nbsp;&nbsp;&nbsp; ... ... </P>
      <P>}</P>
      <P>上面的a是一个局部变量,处在的if语句所带的那对 {} 之内。</P>
      <P> </P>
      <P>//for 语句:</P>
      <P>for(int i=0;i&lt;100;i++)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; int a; </P>
      <P>&nbsp;&nbsp; ... ...</P>
      <P>}</P>
      <P>上面的a也是一个局部变量。处在for语句带的{}之内。</P>
      <P>for 语句涉及局部作用域时,有一点需要特别注意:上面代码中,变量 i 的作用域是什么?</P>
      <P>根据最新的 ANSI C++ 
      规定,在for的初始语句中声明的变量,其作用范围是从它定义的位置开始,一直到for所带语句的作用域结束。而原来老的标准是出了for语句仍然有效,直到for语句外层的局部作用域结束。请看对比:</P>
      <P>假设有一for语句,它的外层是一个函数。新老标准规定的不同作用域对比如下:</P>
      <P><IMG height=189 src="教学--第十五章 存储类型、作用域、可见性和生存期.files/ls15.h11.gif" 
      width=410 border=0></P>
      <P>如果按照旧标准,下面的代码将有错,但对新标准,则是正确的,请大家考虑为什么:</P>
      <P> </P>
      <P>void func()</P>
      <P>{</P>
      <P>&nbsp;&nbsp; for(int i=0;i&lt;9;i++)</P>
      <P>&nbsp;&nbsp; {</P>
      <P>&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; i &lt;&lt; endl;</P>
      <P>&nbsp;&nbsp; }</P>
      <P> </P>
      <P>&nbsp; for(int i=9;i&gt;0;i--) //&lt;-- 在这一行,旧标准的编译器将报错,为什么?</P>
      <P>&nbsp; {</P>
      <P>&nbsp;&nbsp;&nbsp; cout &lt;&lt; i &lt;&lt; endl; </P>
      <P>&nbsp; }</P>
      <P>}</P>
      <P> </P>
      <P>Borland C++ Builder 
      对新旧标准都可支持,只需通过工程中的编译设置来设置采用何种标准。默认总是采用新标准。记住:如果你在代码中偶尔有需要旧标准要求的效果,你只需把代码码写成这样:</P>
      <P>int i;</P>
      <P>for(i=0;i&lt;9;i++)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; ...</P>
      <P>}</P>
      <P>这时候,i的作用域就将从其定义行开始,一直越过整个for语句。</P>
      <P> </P>
      <P>其它还有不少能用到复合语句(一对{}所括起的语句组)的流程控制语句,如do..while等。请复习以前相关课程。</P>
      <P>其实,就算没有流程控制语句,我们也可以根据需要,在代码中直接加上一对{},人为地制造一个“局部作用域”。比如在某个函数中:</P>
      <P>void func()</P>
      <P>{</P>
      <P>&nbsp;&nbsp; int a = 100;</P>
      <P>&nbsp;&nbsp; cout &lt;&lt; a &lt;&lt; endl;</P>
      <P> </P>
      <P><FONT color=#ff0000>&nbsp;&nbsp; {</FONT></P>
      <P><FONT color=#ff0000>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int a = 
      200;</FONT></P>
      <P><FONT color=#ff0000>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; a 
      &lt;&lt; endl;</FONT></P>
      <P><FONT color=#ff0000>&nbsp;&nbsp; }</FONT></P>
      <P> </P>
      <P>&nbsp;&nbsp; cout &lt;&lt; a &lt;&lt; endl;&nbsp;&nbsp; </P>
      <P>}</P>
      <P> </P>
      <P>代码中红色部分即是我们制造的一个局部作用域。执行该函数,将有如下输出:</P>
      <P><FONT color=#ffffff><SPAN 
      style="BACKGROUND-COLOR: #000000">100</SPAN></FONT></P>
      <P><FONT color=#ffffff><SPAN 
      style="BACKGROUND-COLOR: #000000">200</SPAN></FONT></P>
      <P><FONT color=#ffffff><SPAN 
      style="BACKGROUND-COLOR: #000000">100</SPAN></FONT></P>
      <P>你能理解吗?</P>
      <P> </P>
      <H4><A name=15.2.2>15.2.2</A> 全局作用域 和 域操作符</H4>
      <P> </P>
      <P>如果一个变量声明或定义不在任何局部作用域之内,该变量称为<B>全局变量</B>。同样,一个函数声明不处于任何局部作用域内,则该函数是全局函数。</P>
      <P> </P>
      <P>一个全局变量从它声明或定义的行起,将一起直接作用到源文件的结束。</P>
      <P> </P>
      <P>请看下例:</P>
      <P>//设有文件 Unit1.cpp,内定义一个全局变量:</P>
      <P> </P>
      <P>int a = 100;</P>
      <P> </P>
      <P>void func()</P>
      <P>{</P>
      <P>&nbsp; cout &lt;&lt; a &lt;&lt; endl; </P>
      <P>}</P>
      <P> </P>
      <P>输出:</P>
      <P><FONT color=#ffffff><SPAN 
      style="BACKGROUND-COLOR: #000000">100</SPAN></FONT></P>
      <P> </P>
      <P>我们今天还要学习到一个新的操作符,域操作符 
      “::”。域操作符也称“名字空间操作符”,由于我们还没学到“名字空间”,所以这里重点在于它在全局作用域上的使用方法。</P>
      <P> </P>
      <P>:: 
      域操作符,它要求编译器将其所修饰的变量或函数看成全局的。反过来说,当编译器遇到一个使用::修饰的变量或函数时,编译器仅从全局的范围内查找该变量的定义。</P>
      <P> </P>
      <P>下面讲到作用域的嵌套时,你可以进一步理解全局作用域如何起作用,同时,下例也是我们实例演示如何使用域作用符::的好地方。</P>
      <P> </P>
      <H4><A name=15.2.3>15.2.3</A> 作用域嵌套及可见性</H4>
      <P> </P>
      <P>例二:嵌套的两个作用域</P>
      <P> </P>
      <P>在例一的基础上,我增加一个全局变量:</P>
      <P> </P>
      <P><B>int a = 0; //&lt;-- 全局变量,并且初始化为0</B></P>
      <P> </P>
      <P>void func()</P>
      <P>{</P>
      <P>&nbsp; int a;</P>
      <P> </P>
      <P>&nbsp; a = 100;</P>
      <P> </P>
      <P>&nbsp; cout &lt;&lt; a &lt;&lt; endl; //输出a的值</P>
      <P>}</P>
      <P> </P>
      <P>int main(int argc, char* argv[])</P>
      <P>{</P>
      <P>&nbsp; cout &lt;&lt; a &lt;&lt; endl; //输出a的值</P>
      <P>} </P>
      <P> </P>
      <P>我们在 5.1.3 “如何为变量命名”这一小节中讲到: <B>“</B>不能在同一作用范围内有同名变量”。 
      上面的代码中,定义了两个a,但并不违反这一规则。因为二者处于不同的作用范围内。下图标明了两个a的不同作用范围:</P>
      <P><IMG height=215 src="教学--第十五章 存储类型、作用域、可见性和生存期.files/ls15.h10.gif" 
      width=357 border=0></P>
      <P>从图示中看到:两个变量a:1个为全局变量,一个为局部变量。前者的作用域包含了后者的作用域。这称为作用域的嵌套。</P>
      <P>如果在多层的作用域里,有变量同名,那么内层的变量起作用,而外层的同名变量暂时失去作用。比如在上例中,当代码执行到<FONT 
      color=#ff0000>①</FONT>处时,所输出的是函数 func()内的a。而代码<FONT 
      color=#0000ff>②</FONT>处,输出的是全局变量a。</P>
      <P>这就引出一个“可见性”这个词,当内层的变量和外层的变量同名时,在内层里,外层的变量暂时地失去了可见性。</P>
      <P> </P>
      <P>不过,如果外层是全局作用域,那么我们可以使用::操作符来让它在内层有同名变量的情况下,仍然可见。</P>
      <P> </P>
      <P>int a = 0;</P>
      <P>void func()</P>
      <P>{</P>
      <P>&nbsp; int a;</P>
      <P>&nbsp; a = 100;</P>
      <P> </P>
      <P>&nbsp; cout &lt;&lt; a &lt;&lt; endl; //输出内层的a;</P>
      <P><B>&nbsp; cout &lt;&lt; ::a &lt;&lt; endl;&nbsp; </B>//输出全局的a。</P>
      <P>}<BR> </P>
      <P>最后请大家把本节中讲到例子,都在CB上实例际演练一下。</P>
      <P> </P>
      <H3><A name=15.3>15.3</A> 生存期</H3>
      <P> </P>
      <P>一个变量为什么有会不同的作用域?其中一种最常见的原因就是它有一定的生存期。什么叫生存期?就像人一样,在活着的时候,可以“起作用”,死了以后,就不存在了,一了百了。</P>
      <P>那么,在什么情况下一个变量是“活”着,又在什么情况下它是“死”了,或“不存在”了呢?</P>
      <P>大家知道,变量是要占用内存的。比哪一个int类型的变量占用4个字节的内存,或一个char类型的变量占用1个字节的内存。如果这个变量还占用着内存,那么我们就认为它是“活着”,即,它存在着。而一个变量释放了它所占用的内存,我们就认为它“死了”,“不存在”了。</P>
      <P>有哪个同学能告诉我,在我们的教程中,我这是第几次讲到“变量和内存”的关系?呵,我也记不得了。不管怎样,这里又是一次——我们必须从整体上讲一讲:一个程序在内存中如何存放?</P>
      <P> </P>
      <H4><A name=15.3.1>15.3.1</A> 程序的内存分区</H4>
      <P> </P>
      <P>先从程序上看“生”和“死”。</P>
      <P>用CB编译出一个可执行文件(.exe),它被存放在磁盘上。当它没有运行时,我们认为它是“死”的。而当我们双击它,让它“跑”起来时,我们认为它是“活”的,有了“生命”。等我们关闭它,或它自行运行结束,它又回到了“死”的状态下。在这个过程里。</P>
      <P>程序运行时,它会从操作系统那里分得一块内存。然后程序就会把这些内存(严格讲是内存的地址)进行划分,哪里到哪里用来作什么。这有点像我们从老板那里领来2000大洋,其中1000无要交月租,500元做生活费……真惨。</P>
      <P>那么,程序有哪些需要入占用内存呢?</P>
      <P>首先,代码需要一个空间来存放。因此,到手的内存首先要分出一块放代码的地方,称为代码区。剩下的是数据。根据不同需要,存放数据有区域有三种:数据区,栈区,堆区。为什么存放数据的内存需要分成三个区域?这个我先不说,先来说说数据(变量等)被放入不同的区内,将遇上什么样不同的命运。</P>
      <P> </P>
      <P>第一、放入数据区的数据。</P>
      <P>生存期:这些数据的命运最好。它们拥有和程序一样长的生存期。程序运行时,它们就被分配了内存,然后就死死占着,直到程序结束。</P>
      <P>谁负责生死:这些数据如何产生,如何释放,都是程序自动完成的,我们程序员不用去费心为产生或释放这些变量写代码。</P>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -