第09章 命名控制
一、来自C 语言中的静态成员
在C 和C + +中,static都有两种基本的含义,并且这两种含义经常是互相有冲突的:1) 在固定的地址上分配,也就是说对象是在一个特殊的静态数据区上创建的,而不是每次函数调用时在堆栈上产生的。这也是静态存储的概念。
2) 对一个特定的编译单位来说是本地的(就像我们在后面将要看到的,这在C++中包括类的范围)。这里static控制名字的可见性,所以这个名字在这个单元或类之外是不可见的。这也描述了连接的概念,它决定连接器将看到哪些名字。
二、静态对象的析构函数
静态对象的析构函数(包括静态存储的所有对象,不仅仅是上例中的局部静态变量)在程序从main() 块中退出时,或者标准的C 库函数exit()被调用时才被调用。多数情况下main()函数的结尾也是调用exit()来结束程序的。这意味着在析构函数内部使用exit()是很危险的,因为这可能陷入一个死循环中。但如果用标准的C 库函数abort()来退出程序,静态对象的析构函数并不会被调用。
三、控制连接
一般情况下,在文件范围内的所有名字(既不嵌套在类或函数中的名字)对程序中的所有编译单元来说都是可见的。这就是所谓的外部连接,因为在连接时这个名字对连接器来说是可见的,外部的编译单元、全局变量和普通函数都有外部连接。内部连接的一个好处是这个名字可以放在一个头文件中而不用担心连接时发生冲突。
那些通常放在头文件里的名字,像常量、内联函数(inline function ),在缺省情况下都是内部连接的(当然常量只有在C + +中缺省情况下是内部连接的,在C 中它缺省为外部连接)。注意连接只引用那些在连接/装载期间有地址的成员,因此类声明和局部变量并没有连接。
四、静态成员函数
像静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全体服务而不是为一个类的部分对象服务。这样就不需要定义一个全局函数,减少了全局或局部名字空间的占用,把这个函数移到了类的内部。当产生一个静态成员函数时,也就表达了与一个特定类的联系。静态成员函数不能访问一般的数据成员,它只能访问静态数据成员,也只能调用其他的静态成员函数。通常,当前对象的地址(this)是被隐含地传递到被调用的函数的。但一个静态成员函数没有this ,所以它无法访问一般的成员函数。这样使用静态成员函数在速度上可以比全局函数有少许的增长,它不仅没有传递this所需的额外的花费,而且还有使函数在类内的好处。因为静态成员对象的初始化方法,我们可以把上述类的一个静态数据成员放到那个类的内部。
五、静态初始化的依赖因素
有三种方法来处理这一问题:
1) 不用它,避免初始化时的互相依赖。这是最好的解决方法。
2) 如果实在要用,就把那些关键的静态对象的定义放在一个文件中,这样我们只要让它们在文件中顺序正确就可以保证它们正确的初始化。
3) 如果我们确信把静态对象放在几个编译单元中是不可避免的(比方在编写一个库时,我们无法控制那些使用该库的程序员)这时我们可用由Jerry Schwarz 在创建iostream库(因为cin ,cout和cerr的定义是在不同的文件中)时提供的一种技术。这一技术要求在库头文件中加上一个额外的类。这个类负责库中静态对象的动态初始化。(类似于Singleton)
六、转换连接指定
如果C + +中编写一个程序需要用到C 库,那该怎么办呢?如果这样声明一个C 函数:
float f(int a,char b); C++的编译器就会将这个名字变成像_f_int_int之类的东西以支持函数重载(和类型安全连接)。然而,C编译器编译的库一般不做这样的转换,所以它的内部名为_f。这样,连接器将无法解决我们C++对f()的调用。C++中提供了一个连接转换指定,它是通过重载extern关键字来实现的。extern后跟一个字
符串来指定我们想声明的函数的连接类型,后面是函数声明。 extern "C" float f(int a,char b);
这就告诉编译器f()是C连接,这样就不会转换函数名。标准的连接类型指定符有“C”和“C++”两种,但编译器开发商可选择用同样的方法支持其他语言。如果我们有一组转换连接的声明,可以把它们放在花括号: extern "C" { float f(int a); float g(int b);}