分类: C/C++
2011-06-07 08:54:46
第8章 域和生命周期 域 C++中的一个名字可以指向不同的实体,只要编译器能够根据上下文区分出该名字的不同含义。用来区分名字含义的一般上下文就是域。C++支持三种形式的域:局部域,名字空间域,类域。
名字解析:是把表达式中的一个名字与某一个声明相关联的过程,也是给出这个名字意义的过程。
局部域内的名字解析是这样进行的:首先,查找使用该名字的域,如果找到一个声明,则该名字被解析。如果没有找到,则查找包含该域的域,这个过程会一直继续下去,直到找到一个声明或已经查找完整个全局域。如果后一种情况发生,即没有找到该名字的声明,这个名字的用法将被标记为错误。
因为在名字解析期间查找域的顺序由内向外,所以在外围域中声明被嵌套域中的同名声明所隐藏。
全局对象和函数
函数声明:指定了函数的名字以及函数的返回类型和参数表。
函数定义:为函数提供函数体,函数的实现。
在全局域定义对象时,默认情况下如果未显示指定初始值,则存储区被初始化为0。
在一个程序中,一个全局对象只能定义一次(否则为多次定义,编译错误)。在多文件构成的程序中,存在如下问题,如果头文件中定义对象(int j;),默认j被声明定义并初始化为0。这样,当不同的文件include该头文件时,j就会被多次定义,产生编译错误!因此,需要有一种方法声明一个对象,而不定义它!
extern就是这样功能的关键字,extern int j;这样就声明了j,而没做定义。可以在include该头文件的cpp中,做int j = 9;这样的对象定义。
如果extern声明时,显示做了初始化。后续的对该对象的定义都标记为错误。如:
注意:重复定义是编译错误,重复声明(声明的意思是有一个全局变量它的名字是j)是允许的。如:extern int j; extern int j;
不同文件之前声明的匹配
有如下情况:
以上在use.c函数中可能找不到func函数定义,或者编译成功函数执行时失败。
C++有一种机制,它可以把函数参数的类型和数目编码在函数名中,该机制为类型安全链接。这样,该机制捕捉到参数不匹配的情况。如将use.c中声明的函数标记为未定义函数。
但是对于不同文件,同一对象或函数声明的其它类型不匹配(如返回类型)的情况,编译和链接时可能不被捕捉到。所以一般使用头文件机制,避免此类问题。
关于头文件
头文件为extern对象声明,函数声明以及inline函数定义提供了一个集中的位置,这被称做声明的局部化。如果一个文件要使用或定义一个对象或函数时,它必须包含相应的头文件。
头文件提供了两个安全保证:
第一:保证所有文件都包含同一个全局对象或函数的同一份声明。
第二:如果需要修改声明,则只需要改变一个头文件。从而不至于再发生只修改了某一个特殊的文件中的声明。
注意:头文件中不应该含有非inline函数或对象的定义!如头文件中出现:
因为以上定义出现在头文件中,当头文件被多次包含时,它们都会被多次定义,有重复定义的编译错误。
其中,常量和inline函数是可以的。因为他们都是特殊的定义,可以被定义多次。
程序编译期间,可能的情况下,符号常量会代替其名字出现。这个替换过程为常量折叠。
如const int value = 0;的定义,当value被用在一个文件中时,编译器用0代替名字value。为了使编译器能够用一个常量值替换它的名字,该常量的定义必须在它被使用的文件中可见。所以符号常量可以在同一程序的不同文件中被定义多次。
关于inline函数,由于一些函数不适合内联,这时编译器在调用点不会内联该函数,而是为该函数生成一个定义,放到可执行文件中。如果在多个文件中生成同一函数的定义,那么会产生一个不必要的,过大的可执行文件,并且显然是不必要的。所以,应该慎重使用inline函数。
自动对象
自动对象的存储分配发生在定义它的函数被调用时,分配给它的存储区来自于程序运行栈,它是函数活动记录的一部分。未初始化的自动对象的存储区值是上次该区域被使用的结果,是一个随机值,称为未指定的。函数结束时,它的活动记录被从运行栈中弹出,自动对象的存储区被释放!
当一个自动变量的地址被存储在一个生命期长于它的指针时,该指针被称为空悬指针。一定要注意!避免此种情况发生!
寄存器自动对象
用register声明!编译器把此种对象装载到机器的寄存器中。因为寄存器存取速度比内存快的多,当某一变量被频繁使用时,用寄存器变量可以提高函数执行效率。
register对编译器来说只是一个建议,编译器可以忽略该建议,使用寄存器分配算法找出最合适的时候入到机器寄存器中。
静态局部变量
声明为static,程序运行期间一直存在,但其可视性在局部域(语句块函数块)内。在编译时初始化,并且只初始化一次。如不显示初始,则自动赋值为0。如下例子:
动态分配的对象
系统为每个程序提供了一个在程序执行时可用的内存池,它被称为程序的空闲存储区(freestore)或堆(heap)。动态分配对象允许程序员完全控制它的分配和释放,它被分配在堆上。
三种形式的new表达式:
支持单个对象的动态分配,支持数组的动态分配,定位new表达式
单个对象的动态分配与释放:new表达式由new关键字和其后面的类型指示符构成,如:new int;
new表达式的操作顺序为:从堆上分配对象,然后用括号中的值初始化该对象。调用new()库操作符分配内存,内存不足,不够分配对象内存,会抛出异常。
delete操作调用库操作符delete(),释放堆上分配的内存。如果指针为0,则C++会保证不调用操作符delete(),没有必要做非0测试。(C++本身有机制会做该测试)
注意:delete操作符只能释放new在堆上分配的内存,其它delete操作是非法的(对0指针的操作除外,虽不报错,也是不合理的)。
动态内存分配相关的错误:
数组的动态分配与释放
动态分配数组第一维不必是常量值,可以是任意表达式。
delete[] 方式来释放。
定位new表达式
允许程序员将对象创建在已经分配好的内存中。形式为:new (指针名) 类型
例如:
名字空间的定义
缺省情况下,在全局域中声明的每个对象,函数,类型等都引入一个全局实体,在全局域引入的全局实体必须有唯一的名字,否则产生名字冲突问题,即全局名字空间污染。
名字空间就是用来解决这种问题的有效方案!
用户声明的名字空间代表一个不同的名字空间域,它可以包含其它嵌套的名字空间定义。一个域中定义的名字是唯一的。
名字空间内成员的名字会自动被空间名限定修饰,如webcore::string, webcore::submitL()。
为了使用更方便,C++有名字空间别名、using声明、using指示符等机制。
用法:
名字空间的定义不一定是连续的,可以
在不同位置共同定义一个完整的名字空间,并可跨跃不同文件,所以名字空间的定义是可累积的。
这样可以分开编写代码的接口声明和实现部分,使代码结构清晰:如:
域操作符 ::
使用名字空间内的成员myclass,而不用域名限定修饰是错误的!因为编译器不知道成员名myclass指向的是哪个声明。
名字空间内的成员声明被隐蔽在其名字空间中,如不进行域名限定,编译器将在当前域中查找该名字的声明。如:
域操作符可以指向全局名字空间中的名字,用法如: ::myclass//显示使用全局myclass,可用于全局域的变量被局部域中同名变量隐藏时,访问全局域中的变量。
嵌套名字空间
名字空间cplusplus_primer包含两个嵌套的名字空间:MatrixLib和AnimalLib。调用方法为: cplusplus_primer::MatrixLib::inverse
在外围名字空间中声明的实体,被里面的名字空间中的同名声明实体隐藏。
名字空间成员定义
如:
应该注意:函数名必须要用域名做限定,局部域类型matrix返回值必须做限定,但是参数和函数实现块中matrix类的使用可以不用域名限定。因为operator+的名字被解析后,编译器已经找到其名字空间,可直接用名字空间域中的名字。
但是注意:只能在包含该名字空间的域内做这种方式的定义。如:可在全局域,cplusplus_primer和MatrixLib中定义。并且声明过才能被定义,否则是非法的。
未命名的名字空间
用法如:
这样声明定义的函数,只在somef.cpp中可见。
它同static声明函数有相同的性质,如:
static void fun() { …. }//也是只在somef.cpp中可见的。
名字空间别名
用法如:
之后使用IBM做域名限定即可。