
第16章 ANSI C++标准语法补充
大多数C++语言系统都支持ANSI/ISO C++标准。本章介绍该标准中的部分内容,作为前面各章节的语法补充。本章将介绍逻辑型bool、命名空间namespace、两个修饰符explicit和mutable、运行时刻类型信息RTTI和typeid运算符、以及4种新型的强制类型转换运算符。本章各部分之间相对,相互之间没有严格次序。
16.1 逻辑型bool
C语句中没有逻辑类型,而C++标准有逻辑型bool。逻辑型也被称为布尔型。逻辑值只有真true和假false两个值,而且只能进行逻辑运算。C语言用整数int来表示逻辑值,0值表示false,非0为true。整数表示逻辑值的缺点是算术运算的结果可直接作为逻辑值,而且“逻辑值”也可进行算术运算,这不符合高级编程语言的要求。
C++可定义bool类型变量,可直接赋值true或false,可作为关系表达式和逻辑表达式的计算结果。bool类型变量支持逻辑运算非!、与&&、或||运算。一个bool值在内存中占1字节,故此sizeof(bool)为1。但内部采用整数值0表示false,1表示true。
例16-1逻辑型bool的例子。
#include #include using namespace std; void main(){ bool boolean = false; int x = 0; cout<<"boolean is "< cin >> x; cout<<"integer " < <<" and interpreted as "; if (x) cout<<"true\\n"; else cout<<"false\\n"; bool b = true; boolean = false && b; cout<<"boolean is "< < boolean = x * 3 > 10; if (boolean) cout<<" x * 3 > 10"< cout<<" x * 3 <= 10"< 执行程序,输入45,输出结果如下: boolean is 0 Enter an integer:45 integer 45 is nonzero and interpreted as true boolean is 0 boolean output with boolalpha manipulator is false sizeof(boolean)=1 x * 3 > 10 本质上bool类型仍然是一个整型值。在cout<<输出时,仍然输出0或1,而不是false或true,除非用boolalpha作为格式控制符。另一个需要注意的是bool类型变量仍然可以进行算术运算,例如: bool b2 = true, b3 = false; bool b4 = b2 - b3; //bool型变量之间不应该允许算术运算。 对上面语句,编译器仅给出警告而不是错误。 但无论如何,引入bool类型本身就是一种改进,建议在C++程序中尽可能采用bool类型。 16.2 命名空间namespace 命名空间(namespace)是解决大程序中多个元素(如类、全局函数和全局变量)命名冲突的一种机制。当我们要把几个部分(往往来自不同的人员或团队)合并成为一个大程序时,往往就会出现命名冲突的问题:类名、全局函数名、全局变量名都可能重名。解决的方法就是把这些名字放在不同的命名空间中,在访问这些名字时使用各自的命名空间作为限定符。 16.2.1 命名空间的定义 命名空间类似文件系统中的目录,空间中的成员类似目录中的文件。全局空间相当根目录,一个目录名作为其中多个文件的命名空间,子目录作为嵌套空间,文件作为空间中的成员。同时一个命名空间也是一个作用域。 命名空间的基本规则如下: ●一个程序所用的多个命名空间在相同层次上不重名; ●在同一个命名空间中的所有成员名字不重复; ●在一个命名空间中可以嵌套定义其内层的多个子空间。 定义命名空间的语法格式如下: namespace [<空间名>]{一组成员} 其中,namespace是关键字,后面给出一个空间的名字标识符。后面用花括号括起来一组成员,可以是一组类、一组函数、一组变量。如果空间名缺省则为无名空间。无名空间中的元素类似于全局变量,只是在本文件中访问,而全局空间中的成员可以被其它文件访问。 命名空间可嵌套说明,就如同目录与子目录之间的关系。 例如下面代码: int myInt = 98; //全局空间中的变量 namespace Example{ //说明了一个Example空间 const double PI = 3.14159; //Example中的变量成员 const double E = 2.71828; //Example中的变量成员 int myInt = 8; //Example中的变量成员,隐藏了全局同名变量 void printValues(); //Example中的函数成员的原型 namespace Inner{ //嵌套空间,名为Inner enum Years{FISCAL1 = 2005, FISCAL2, FISCAL3}; //嵌套空间中的成员 int h = ::myInt; //用全局变量进行初始化,98 int g = myInt; //用外层成员进行初始化,8 } } namespace{ //无名空间 double d = 88.22; //无名空间中的变量成员 int myInt = 45; //无名空间中的变量成员 } 图16.1 空间的结构 图16.1表示了上面例子所说明的空间的结构。 在描述成员的全称时,要使用作用域运算符“::”。例如,Example是一个空间,其中各成员的全称为: Example::PI; Example::E; Example::myInt; Example::printValues(); Example::Inner是一个嵌套空间,Example被称为Inner的外层空间,嵌套空间中的成员的全称为: Example::Inner::Years Example::Inner::h Example::Inner::g 16.2.2 空间中成员的访问 如何访问某空间中的成员?这与访问文件系统中的文件相似。访问文件可用相对路径,也可用全路径。全路径就是把全部路径名作为文件名的限定符。 如何查找一个成员?在当前空间中对一个名字k的访问需要查找过程,在编译时确定被访问的实体。有以下3种形式: 1、限定名形式:“空间名::k”。先在当前空间的限定嵌套空间中查找k,相当于一个相对路径。在嵌套空间中如果未找到成员k,就将该空间名作为全路径的空间名再查找成员k,如果仍未找到,就给出错误信息。这种访问形式对相对路径的空间名优先。 2、全局限定形式:“::k”。在全局空间中查找,如果未找到就出错。 3、无限定形式:“k”。按局部优先原则,先在当前空间中查找。如果未找到,就向外层空间找,直到全局空间。如果未找到,就从导入空间中查找,如果找到一个就确定,如果在全局空间和导入空间中找到多于一个就指出二义性错误。如果仍未找到k就是一个错误名字。 如果要多次访问某个空间中的成员,你可能觉得成员名前面总是挂上一串空间名很麻烦。有一个简单方法就是用using namespace语句来导入(import)一个命名空间。格式如下: using namespace [::] [空间名::] 空间名; 其中,using和namespace是关键字,然后指定一个空间名,或者一个嵌套空间名。例如: using namespace std; 就是导入空间名“std”,所有标准C++库都定义在命名空间std中,也包括标准模板库STL,因此这条语句经常使用。 导入空间语句仅在当前文件中有效。在一个文件中可以有多条导入语句,来导入多个空间名。 实际上,一个无名空间的定义都隐含着以下两条语句: namespace unique{...} using namespace unique; 其中unique是系统隐含的一个唯一的空间名。导入命令使得无名空间中的成员仅能在本文件中访问,因此无名空间就是一种缺省导入的空间。 如果只想导入某空间中的某个成员,而不是全部成员,可按下面格式说明: using [::]空间名::成员名; 例16-2 命名空间的定义和访问。 #include using namespace std; int myInt = 98; namespace Example{ const double PI = 3.14159; const double E = 2.71828; int myInt = 8; void printValues(); namespace Inner{ enum Years{FISCAL1 = 2005, FISCAL2, FISCAL3}; int h = ::myInt; int g = myInt; } } namespace{ double d = 88.22; int myInt = 45; } void main(){ cout<<"In main"; cout<<"\\n(global) myInt = "<<::myInt; //A cout<<"\\nd = "< } void Example::printValues(){ cout<<"\\nIn Example::printValues:\\n" <<"myInt = "< <<"\\nInner::FISCAL3 = "< 执行程序,输出结果如下: In main (global) myInt = 98 d = 88.22 Example::PI = 3.14159 Example::E = 2.71828 Example::myInt = 8 Example::Inner::FISCAL3 = 2007 Example::Inner::h = 98 Example::Inner::g = 8 In Example::printValues: myInt = 8 d = 88.22 PI = 3.14159 E = 2.71828 ::myInt = 98 Inner::FISCAL3 = 2007 Inner::h = 98 Example::Inner::g = 8 函数main是全局函数,其中代码要访问某个空间中的成员就要给出绝对路径全称。注意到在全局空间和无名空间中都定义了myInt成员,因此在全局空间中如果直接用“myInt”来访问,就会造成二义性。假如A行用“myInt”来访问,就会产生二义性。用“::myInt”是就访问全局变量。B行用“d”来访问无名空间中的成员,此时无二义性。 函数printvalues是Example空间中的成员,其中代码可用绝对路径,也可用相对路径来访问成员。C行用“myInt”访问当前空间中的成员,尽管在全局空间和无名空间中都有同名成员,但当前命名空间中的成员具有优先权,故此无二义性。D行用“::myInt”限定全局变量。E行和F行采用了相对路径,而G行采用了绝对路径,给出了全称。 对于命名空间的使用,应注意以下要点: ●尽量使全局空间中的成员最少,这样发生冲突的可能性就会减少。 ●空间的命名应尽可能地表示自然结构,而不仅仅是为了避免命名冲突。 ●在一个源文件中应尽可能避免说明多个平行的空间。 ●二义性往往发生在全局空间和导入空间中含有同名元素,而程序中用无限定的形式来访问该元素。 16.3 修饰符explicit 我们在第10章介绍过,在一个类中,如果说明了单参构造函数,就可用于隐式的类型转换。这种隐式的类型转换可能会被误用,尤其是在函数调用时,实参到形参的隐式转换,可能在不经意之间就创建了对象。 例如,如果一个函数的形参是一个对象或对象引用,而该对象类型恰好有单参构造函数,那么调用方就可利用此形参隐式创建新对象,再传给函数做实参。用explicit修饰符就能避免这种无意的创建对象。 如果将一个单参构造函数修饰为explicit,该函数就不能用于隐式的类型转换,而只能用于显式地创建对象。 例16-3 分析下面例子。
