作用域和存储类型
作用域和存储类型
在过程化程序设计中最基本的思想是将一个较大的、较复杂的问题,分解成若干个较小的、更为容易解决的子问题。把一个程序划分为若干个模块,每一个模块只完成一种任务,每一个种的每一个标识符都有其作用的范围,这样设计出来的程序层次分明、容易读懂、便于维护、并可被其他程序调用。本章介绍标识符的作用域,局部变量、全局变量、静态变量以及动态变量,还将介绍变量的存储类型、编译预处理、程序的多文件组织,最后给出综合应用实例。
一.作用域
(1)作用域
标识符包含变量名、常量名、函数名、类名、对象名等。 标识符只能在其定义或说明的范围内是可见的,在该范围之外是不可见的。一个标识符只要是可见的系统就可以对其进行访问操作。 作用域是指标识符在程序中可引用的区域。 标识符的作用域分为块作用域、文件作用域、函数声明、函数作用域、类作用域5种。
1)块作用域
块是用一对花括号括起来的一段程序。在块内说明的变量具有块作用域,其作用域是从变量说明处到块的结束处(即块的右花括号处)。
复合语句是一个块。复合语句中定义的变量,作用域仅在该复合语句中。
2)文件作用域
文件作用域也称全局作用域。定义在所有函数之外的标识符,具有文件作用域,作用域为从定义处到整个源文件结束。文件中定义的全局变量和函数都具有文件作用域。
如果某个文件中说明了具有文件作用域的标识符,该文件又被另一个文件包含,则该标识符的作用域延伸到新的文件中。如cin 和cout 是在头文件iostream.h 中说明的具有文件作用域的标识符,它们的作用域也延伸到所有嵌入iostream.h 文件中。
3)函数声明作用域 函数声明不是定义函数,函数声明时,形参作用域只在声明中,即作用域结束于函数声明结束时的右括号。正是由于形参不能被程序的其他地方引用,所以通常只要声明形参个数和类型,形参名可省略。
4)函数作用域 函数中定义的变量,包括形参和函数体中定义的局部变量,作用域都在该函数内,也称为函数域。
5)类作用域 类作用域是指类定义范围(包括类的声明部分和相应成员函数实现整个范围)。在该范围内,类的成员函数对数据成员有完全访问权限。
总结前四种作用域范围如下:
块作用域:从块内变量定义开始到块结束。
文件作用域:从函数外标识符定义开始到文件结束(可用extern 进行扩展)。
函数声明作用域:函数声明内部有效。
函数作用域:从函数开始和函数结束。
(2)局部变量与全部变量
根据作用域的不同,可将程序中的变量分为:局部变量和全局变量。
1)局部变量 在函数或者块内定义的变量是局部变量。局部变量仅在定义它的函数或块内起作用。这个范围之外不能使用这些变量。局部变量的作用域也成为块作用域。 函数内部使用的局部变量包括形式参数和函数体内定义的变量。 在程序运行到局部变量所在的函数或块时,在栈中建立并分配存储空间,该函数或块执行完毕,局部变量占有的空间被释放。 局部变量在定义时若未初始化,其值为随机数。程序中使用的绝大数变量都是局部变量。
2)全局变量 全局变量是在函数外部定义的变量。全局变量的作用域从定义点开始直到文
件的结束,具有文件作用域特点。全局变量在编译时建立在全局数据区,在未给出初始化值时系统自动初始化为0。 全局变量在所有函数之外说明,可以为本源程序文件中位于该全局变量定义之后的所有函数共同使用。全局变量可以在各个函数之间建立数据传输通道,但滥用全局变量会破坏程序模块化结构。
注意:当具有块作用域的局部变量与具有文件作用域的全局变量同名时,与局部变量同名的全局变量不起作用,即局部变量优先。这时可以通过“::”域访问同名的全局变量。
(3)动态变量与静态变量 根据生存期的不同,可将程序中的变量分为:动态变量和静态变量。它们所占用的存储空间区域不同。
C++的存储空间区域分为:
1代码区:存放可执行程序的程序代码;
2静态存储区:存放静态变量和全局变量;
3栈区(Stack ):存放动态局部变量;
4堆区(Heap ):存放new 和malloc ()申请的动态内存。栈区和堆区统称为动态存储区。
1)动态变量 在程序执行过程中分配存储空间的变量。这种变量的生存期是定义动态变量的函数运行期,它对存储空间的利用是动态的。其初值每次为动态变量分配存储空间后都要重新设置。
2)静态变量 在程序开始运行前就分配存储空间的变量。这种变量的生存期就是整个程序的运行期,在程序开始运行前就为其分配相应的存储空间,在程序的整个运行期间一直占用,直到结束。
二.变量的存储类型 在C++中,变量的存储类型分为4种:自动类型(auto )、寄存器类型(register )、静态类型(static )、外部类型(extern )。 自动类型、寄存器类型的变量属于动态变量;静态类型、外部类型的变量属于静态变量。
(1)自动类型(auto ) 用自动类型关键词auto 说明的变量成为自动变量。
1)定义格式: auto 类型 变量名;
2)特性: 自动变量是动态局部变量,具有块作用域特点,变量存放在动态存储区。定义时可加auto 说明符,也可以省略,所以在程序中没有进行特殊声明时都默认是auto 型变量,系统以栈(Stack )方式为auto 变量分配内存空间,在变量作用域结束后,栈空间由系统进行自动回收。
(2)寄存器类型(register ) 用寄存器类型关键词register 说明的变量称为寄存器变量。
1)定义格式: register 类型 变量名;
2)特性: 寄存器变量是动态局部变量,具有块作用域,存放在CPU 的寄存器或动态存储区;这样可以提高存储速度,如果没有存放在通用寄存器中便按自动变量处理。 使用register 变量应注意以下几点:
1))由于通用寄存器的数量有限,寄存器类型的变量不宜过多。
2))变量的长度应与通用寄存器的长度相当。一般为int 型或者char 型。
3))要把一些使用频率高的变量定义为寄存器变量。
(3)静态类型(static ) 用静态类型关键词stati c 说明的变量成为静态变量。
定义格式: static 类型 变量名;
静态变量分为静态局部变量和静态全局变量。在C++语言中规定静态局部变量有默认值,默认值分别为int 型等于0,float 型等于0.0,char 型为'\0' ,静态全局变量也是如此。而自动类型和寄存器类型变量没有默认值,值为随机数。
1)静态局部变量。定义在函数内的静态变量成为静态局部变量。
特点:1))静态局部变量本身也是局部变量,具有局部变量的性质。因此其作用域也是局限
在定义它的本函数体内,当离开本函数体,该变量就不再起作用,但其值还继续保留。
2))另一方面,静态局部变量又是静态存储类别的变量,所以,在整个程序运行开始就被分配固定的存储单元(占用静态存储区),整个程序运行期间不再被重新分配,所以其生存期是整个程序运行期间。
3))静态局部变量的赋初值时间在编译阶段,并不是每发生一次函数调用就赋一次初值。静态局部变量仅初始化一次,当再次调用该函数时,静态局部变量保留上次调用函数时的值。
2)静态全局变量。在定义全局变量时前面加说明符static ,就成为静态全局变量。
在前面,由于程序由一个源程序文件实现,所以一个全局变量和一个静态全局变量在作用域上是没区别的。但在多文件组成的程序中,一个全局变量和一个静态全局变量在作用域上是完全不同的,其他文件通过外部变量声明可以使用一个全局变量,但却无法使用静态全局变量,静态全局变量只能被定义它的文件所独享。
静态全局变量的特点是:
1))特点与全局变量基本相同,只是其作用范围(即作用域)是定义它的程序文件,而不是整个程序。
2))静态全局变量属于静态存储类别的变量,它在程序一开始运行时,就被分配固定的存储单元,所以其生存期是整个程序运行期间。
3))使用静态全局变量的好处是同一程序的两个不同源程序文件中可以使用相同名称的变量名,而互不干扰。
(4)外部类型(extern ) 用外部类型extern 说明的全局变量成为外部变量。
定义的格式: extern 类型 变量名; 在由多个源程序文件组成你的程序中,如果一个文件要使用另一个文件中定义的全局变量,这些源程序文件之间通过外部类型的变量进行沟通。 在一个文件中定义的全局变量默认为外部的变量,即其作用域可以延伸到程序的其他文件中。但其他文件如果要使用这个文件中定义的全局变量,必须在使用前用“extern ”作外部声明,外部声明通常放在文件的开头。 变量定义时编译器为其分配存储空间,而变量声明指明该全局变量已在其他地方说明过,编译系统不再分配存储空间,直接使用变量定义时所分配的空间。 静态存储(static )类型的作用域与外部(extern )存储类型相反,一旦定义为静态存储类型,就限制变量只能在定义它的文件中使用,为文件作用域。
三.编译预处理 C++的预处理是编译器在编译程序之前,先由预处理器处理预处理指令,由于在C++源程序中有各种编译命令,而这些编译命令是在程序被正常编译之前执行的,故称为预处理命令(或指令)。这些命令所实现的功能称为预处理功能。这些预编译命令用来扩充C++程序设计的环境,使得程序书写变得简练清晰。
C++提供的预处理功能主要有以下3种:1. 宏定义命令 2. 文件包含命令。 3. 条件编译命令。 注意:为了与一般C++语句相区别,这些命令以符号“#”开头,而且末尾不包括分号。预处理命令开放在程序开头、中间和末尾。习惯上预编译命令都是放在程序的开头。
(1)宏定义 宏定义命令是将一个标识符定义为一个字符串,该标识符成为宏名,被定义的字符串称为替换文本。宏定义命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义 。1. 宏定义的一般格式为: #define 宏名 字符串 其中,define 是宏定义的关键字,“宏名”是需要替换的标识符,“字符串”是被指定用来替换的字符序列。 说明:
1)#define 、宏名和字符串之间一定要有空格。
2)宏名一般用大写字母表示,以区别于普通标识符。
3)宏被定义以后,一般不能再重新定义。但可以用#undef来终止宏定义。
4)一个定义过的宏名可以用来定义其他新的宏,但要注意其中的括号。
在C++中,虽然用宏定义可以定义符号常量,但常用const 来定义符号常量。两者之间的主要区别是:用const 定义的符号具有一定的数据类型,而用#define命令产生文本替换,而不检查数据类型和内容是否正确。
2. 带参数的宏定义 如:#define MAX(a ,b ) ((a )>(b)?(a):(b))如果在程序中出现如下语句: S=MAX(4,6); 则被替换为:S=4>6? 4:6; 注意:带参数的宏定义与内联函数定义及其使用上的区别。
(2)文件包含命令 所谓“文件包含”是指将另一个源文件的内容合并到当前程序中。C++中,文件包含命令的一般格式为: #include 或 #include " 文件名" 注意:文件名一般是以“.h ”为扩展名,因而称它为“头文件”,文件包含的两种格式区别将文件名“”括起来,用来包含那些由C++系统提供的并放在指定子目录中头文件;将文件名用双引号括起来的,用来包含用户自己定义的放在当前目录或其他目录下的头文件或其他源文件。
文件包含可以将头文件中的内容直接引入,而不必再重复定义,减少了重复劳动,节省了编程时间。文件包含中“头文件”是指存放与标准函数相关信息的文件,或者存放着符号常量、类型定义、类和其他类型的定义以及与程序环境相关信息,该文件名的后缀是“.h ”。头文件由系统提供的。
注意:1)文件包含命令一般都被放在程序最前端,以便后面程序对它们的引用。
2)一条#include命令只能包含一个文件,若想包含多个文件,则应使用多条包含命令。
(3)条件编译命令 在一般情况下,源程序中所有语句都会参加编译,但是有时候会希望根据一定的条件编译源文件的部分语句,这就是“条件编译”。条件编译使得同一源程序在不同的编译条件下得到不同的目标代码。
在C++中,常用的条件编译命令有如下三种:
1)#ifdef 标识符
程序段 1
#else
程序段 2
#endif
该条件编译命令的功能是:如果在程序中#define中定义了指定的“标识符”时,就用程序段1参与编译,否则,用程序段2参与编译。
2)#ifndef 标识符
程序段 1
#else
程序段 2
#endif
该条件编译命令的功能是:如果在程序中未定义制定的“标识符”时,就用程序段1参与编译,否则,用程序段2参与编译。
3)#if 常量表达式 1
程序段 1
#elif 常量表达式 2
程序段 2
„„
#elif 常量表达式 n
程序段 n
#else
程序段 n+1
#endif
该条命令的功能是:依次计算常量表达式的值,当表达式的值为真时,则用相应的程序段参与编译,如果全部表达式的值都为假,则用else 后的程序段参与编译。
四.程序的多文件组织
(1)头文件 考虑标识符在其他文件中的可见性。使用头文件是很有效的方法。 例如,iostream.h 是系统定义的一个文件,这种以“.h ”命名的文件成为头文件。系统定义的头文件中定义了一些常用标识符和函数,用户只要将头文件包含进自己的文件,就可使头文件中定义的标识符在用户文件中变得可见,也就可以直接使用头文件中定义的标识符和函数。 除了系统定义的头文件外,用户还可以自定义头文件。具体地说,头文件中可以包含用户构造的数据类型(如枚举类型)、外部变量、函数声明(原型)、常量等具有一定通用性或常用的量,而一般性的变量和函数定义不宜放在头文件中。
(2)多文件结构 在开发较大程序时,通常将程序分解为多个源程序文件,每个较小的程序用一个源程序文件建立。程序经过建立、编译、连接,成为一个完整的可执行程序。多文件结构通过工程进行管理,在工程中建立若干用户定义的头文件“*.cpp”。头文件中定义用户自定义的数据类型,所有的程序实现则放在不同的源程序文件中。编译时每个源程序文件单独编译,如果源程序文件中有编译预处理指令,则首先经过编译预处理生成临时文件存放在内存,之后对临时文件进行编译生成目标文件“*.obj”,编译后临时文件撤销。所有的目标经连接器连接最终生成一个完整的可执行文件“*.exe”。
(3)在实际程序开发过程中,经常会使用多文件程序结构,例如会用到第三方提供的库函数和头文件,这时,需要对Visual C++ 6.0 IDE的头文件目录和库文件目录进行设置。