C++语言程序设计 7第七讲——名空间和RTTI_第1页
C++语言程序设计 7第七讲——名空间和RTTI_第2页
C++语言程序设计 7第七讲——名空间和RTTI_第3页
C++语言程序设计 7第七讲——名空间和RTTI_第4页
C++语言程序设计 7第七讲——名空间和RTTI_第5页
已阅读5页,还剩67页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

命名空间和RTTI,C+语言程序设计,本章主要内容,命名空间 RTTI的概念 RTTI的两种使用方法 合理使用RTTI,在无“命名空间“的年代,记得在五六十年代,火车站的候车室(还有候船厅)里都有块“旅客留言板”。人们在上面留下自己的纸条。“小李,我已乘xx次列车走了。”、“老王,我已抵连,现住在xx旅馆。” . 开始,旅客较少时很方便。但随着旅客的增多,由于简称、同名等造成的误解,带来的不是方便,而是混乱。 可见公共资源的是不可滥用的,必须加以限制。,“命名空间“的诞生,在一大片土地上生活着一群人。开始人数不是太多时尚无同名问题,可是后来,人越来越多,同名的现象越来越突出。 于是,人们就把这大片的地域划分成较小的块,每个块给予命名。由于块的分割,人名都加上了前缀,同名的现象得到了控制。若还区别不开,则进一步将块划小。 就这样,命名空间诞生了。,C/C+的命名原则,C/C+/C#/Java等语言都遵循的命名原则:在同一作用域内,不得命名相同名称的同种类标识符,否则视为“名冲突”语法错误。 “命名冲突”:在不同文件模块中使用了相同的名字来表示不同的事物,当这些模块一旦由头文件导入凑到了一起,则会引起名称的混乱。 名空间如同磁盘的子目录,标识符如同各子目录下的文件。名空间如同子目录一样可以嵌套。使用名空间如同使用文件时要加路径,不过那是用于文件的,名空间不能用,名空间有特定的使用方式。,命名空间(namespace),“命名空间”:为语言再设定一个命名的作用域(named scope)又加了一层隔离带,将所用的标识符集合其内,域外使用时便多了一层名称来标明归属。“名空间”在UML 中统称为“包”。 声明一个命名空间NS: namspace NS class File; void Fun (); . / 所有空间成员的声明必须写在名空间内 ,名空间的性质,“名空间”是跨越文件的。它不是简单的包含了文件,而是更灵活的超越了文件。是个逻辑概念,就如同活动在校内外的学生依然从属于某系某班级一样。,名空间的追加定义,namspace A class File; void Fun (); namspace B class File; void Fun (); namspace C /名空间可以嵌套。 class File; void Fun (); ,namspace A /合并进了A void Func(); ,匿名空间,“匿名空间”:设定一个无名的命名空间,将标识符集合其内,由于无名,本文件的域外可以无限制地使用该空间的标识符,省却了定义时的static,使用时也节省了空间名前缀。但别的文件不可使用。因此匿名空间的作用相当于C时代的静态全局变量 。如: namspace class File; void Fun (); 匿名空间也可以跨文件追加。 一个编译单元只可有一个匿名空间。,1. 将使用的标识符前加名空间名凡用就加: NS: File obj; NS: Fun (); 这属于“单个一次性开放”,且每次使用都得加。尽管繁琐,但小巧灵活。此法用于使用的名空间标识符个数、次数较少时。 2. 用using声明,一劳永逸地指定,免去了每次必须指定的繁琐。属于“单个一劳永逸开放”,: 例如,经过以下声明: using NS:File; 在当前作用域中就可以不再加标识地直接引用File。 此法用于使用的名空间标识符次数频繁时。,使用名空间的方法:,1规则的示例: 在名字空间之外使用时要时刻带空间名: namespace Parser double prim( bool); double term( bool); double Parser:prim( bool get) double Parser:term( bool get) ,2规则的示例: /用using std:声明,访问标准库的标识符 #include #include void main() using std:set:iterator; / 单个永久打开 int a100 = 10; std:set iSet(a,a+99); / 每每要打开set iterator it = iSet.find(25); / iterator不用再打开了 std:cout“ “*itstd:endl; /要每每打开cout ,3. “全面开放,一劳永逸” 用指示符using: using namespace std;语句后,名空间std中的所有标识符都在本作用域中开放,可直接使用。此法将引起标识符的“全面冲突”。 此法用于使用的名空间标识符个数、次数都很多时。 此法将因别的名空间的引进而产生“名冲突”。编译器的仲裁准则是“外来服从本层”。它的在外层,你的在内层,内层屏蔽外层。被屏蔽的同名者可用空间名:标识符 来区分。 用2方法打开时可能发生的“个别冲突”,也用此法解决。,欲引进多个名空间时,可在当前区域内使用如下语句: using namespace 空间名1; using namespace 空间名2; 此时在两个名空间有同名标识符时会产生二义性。区分的方法同上。,当前区域是指using指令所在的位置,如函数内、文件内等等,主函数不可放入任何名空间中。 在新的C+标准程序库中,系统所用的标识符都声明在命名空间std中,而未使用名空间的程序,都使用带.h的头文件,则意味着其标识符都是全局的。 使用using namespace std;语句的同时要求标准库的头文件都不得使用扩展名.h ,但用户自定义的头文件可以带.h 。,/用using namespace std;打开标准库的标识符 #include #include void main() using namespace std; /一劳永逸地打开 using std:set:iterator; / 永久打开了iterator int a100 = 10; set iSet(a,a+99); iterator it = iSet.find(25); cout“ “*itendl; ,namespace U void f(); void s(); namespace V void f(); /可以有多个f,它们是重载关系 void s(); void func() using namespace U; using V:f; / 注意,只写f,不写参数和返回类型 f(); / 必然是V空间的f() U:f(); / 这才是U空间的f() ,将引进重载的所有的f(),名空间可以指定别名。注意不可使用typedef. 要使用 namespace ANS = NS; namespace American_Telephone_and_Telgraph namespace ATT = American_Telephone_and_Telgraph ATT是名空间别名。,include 与 using namespace 的区别和联系: 前者是将某文件搬到本地来,搬来了未必好用,是否能用全在后者。 using . 只是告诉编译器,扫描源代码时,凡遇到非本作用域的标识符该到哪去找。,#include namspace A void F () cout“F():from namespace A”endl; void G () cout“G():from namespace A”endl; namspace B void F () cout“F():from namespace B”endl; namspace C void F () cout“F():from namespace C”endl; void G () cout“G():from global namespace”endl; ,void main () G(); using namespace A; F(); B:F(); B:C:F(); A:G(); ,G():from global namespace F():from namespace A F():from namespace B F():from namespace C G():from namespace A,1. 在VC6.0中,名空间与友元冲突,编译器会报错。 只好舍弃其中的一个。 2. 名空间还会与模板冲突。 这只是VC不成熟的表现,到后来的VC7.0、8.0就完全解决了。但这时你只能忍疼割爱,放弃其中的一个。,VC6.0的名空间不成熟,名空间设计规范,首先,根据问题,分析分解产生“函数调用关系图”。该图应标明三个层次的模块划分(程序级、文件级、函数级)。注意要按调用关系而非依赖关系划分。 按规则转换为“文件组模块”。 按模块从宏到微的次序,将个模块的输出归纳于各头文件中,并以各名空间命名。 再以模块(最外层)为单位,归纳为上一级名空间,产生更抽象的头文件。 最后分别实现这些资源。,main,f1,f2,f4,g1,g2,h3,h1,k1,m3,m1,k3,m4,若某程序的“函数调用关系图”如下:,g3,f3,k2,n1,h2,n2,k4,k5,文件1,文件2,文件5,文件3,文件4,文件6,模块1,模块2,模块3,转化为 “文件组模块”如下:,模块2,h1,n2,n1,g1,g2,g3,g1,g2,g3,g1,g2,g3,文件2,n1,n2,文件4,g1,g2,g3,/ file1.h namespace f1 void f1(); void f2(); void f3(); void f4(); ,/ file2.h namespace f2 void g1(); void g2(); void g3(); ,/ file3.h namespace f3 void h1(); ,/ file4.h namespace f4 void n1(); void n2(); ,模块3,m1,h1,k2,k3,k1,文件5,k4,k5,k1,m1,m2,m3,m1,文件6,/ module1.h namespace m1 #include “file1.h” /using namespace f1;,/ module2.h namespace m2 #include “file2.h” #include “file3.h” /using namespace f2; /using namespace f3;,然后,在源程序开发时,就可以将这三个模块文件作为资源使用。当然,要对各头文件中所涉及的函数予以实现。 名空间尽管发源于模块的划分和隔离,服务于模块设计的开发模式,但从中受益的不仅是模块,还有数据的使用。当然数据要求更细致的服务,名空间就显粗糙了些,更精细的是类。,四个强制转换运算符 RTTI的概念 RTTI的两种使用方法 合理使用RTTI,31,“强制”的含义: 在告诉编译器: “我知道你不愿意这样做,可是你必须做。尽管执行吧,后果由我负责!” const_cast 除去对象的常属性。转换的是表达式而非对象自身. 形式:const_cast ( object ) static_cast 用来进行非多态的任何转换。拒绝了运行时的类型检查。形式:static_cast ( object ) 。是个杂物袋,不属于其它三种转换的转换都归它,最像C风格的 ( ) 转换。在多重继承下,它会遍历继承树,正确地调整指针的值(C风格的强转则不调整),但仅限于静态编译,故称为“静态”。,新增的四个强制类型转换符,32,reinterpret_cast 将一个整数转变为完全不兼容的指针,或者将一种指针从根本上变为另一种完全不兼容类型的指针。此种转换不产生表达式,只是强制避开编译器的检查。 形式:reinterpret_cast ( object ) 该转换符大概能算作优点就是,能将指向一种函数的指针强转成指向另一种函数。这种转换不安全,不要轻易使用。 dynamic_cast 这是唯一的运行时转换符。可完成类族中的向下类型转换将父类的指针变为子类的指针。 形式:dynamic_cast ( object ),33,string const B(“Hello world !”); / 创建了一个常对象 string / alsoB是B的别名,是个非常对象! 这显然是个隐患。 const_cast(p) / 把const p变成非const的; const_cast(p)/ 把p变成const类型的; p的静态类型必须是T类型的;,使用const_cast的例:,34,double d = 12456.987; int a = static_cast (d); int b = (static_cast (a) + 34.1)/2; int * p = static_cast ( / error static_cast不能违背private和protected继承以及const的限制; static_cast不做运行时类型检查,所以可能是不安全的。,使用static_cast的例:,此处按说还应加强转,35,double d = 12456.987; int * p = reinterpret_cast ( /error 可以在任意类型之间转换; reinterpret_cast是不安全的;,使用reinterpret_cast的例:,36,若有:class type . ; class gen : public type . ; class fct : public type . ; typedef type * ptype; typedef fct * pfct; void conv_op ( ptype pt ) / . pfct pf = pfct( pt ); /在2.0版之前, /用( ) 做强制类型转换完全没问题 / . 它的潜在危险是:当pt指向的是gen类的对象时,灾难发生了。根源出在downcast 时没有分辨是否确属同一类族。新引进的 dynamic_cast具有判断是否确属同一类族的功能。,dynamic_cast的引进,37,dynamic_cast运算符可完成两个方向的转换: upcast : 能将派生类的指针、引用转换成基类类型。(可以用类型兼容规则隐含的进行) downcast :将基类的指针、引用转换成派生类类型。如果确属同一类族,且是公有派生,则转换成功,否则失败,失败将抛出系统的bad_cast类型异常。 注意: dynamic_cast只可转换指针或引用,不可作用于对象。,dynamic_cast的功能,38,为了支持dynamic_cast运算符,系统必须维护一棵继承树,即base class table 模型。通过遍历该继承树来确定被转换对象和目标类型间是否存在 is-a 关系。 具体说,就是通过该对象的vptr检查位于其类型的vtable第一槽位的type_info对象得知的。 这种“模糊匹配”的能力系统为之付出了时空代价,也正是RTTI的魅力所在。而typeid和虚函数则属“精确匹配”,无需额外开销。 dynamic_cast可以转换指针和引用,但不能转换对象。当目标类型是某类型的指针时,转换不成功会返回NULL;当目标类型是某类型的引用时,转换不成功会抛出std:bad_cast异常;,dynamic_cast的机制,39,1989年,由于异常处理的引入,C+必须具有运行时类型识别能力,于是导致了 RTTI 机制的诞生。RTTI 机制不仅满足了异常处理的需要,还解决了虚函数的难题。 “有了RTTI 机制之后,系统就能在运行时查询一个多态指针或引用指向的具体对象的类型了。”(Lippman 语) RTTI 机制的核心:typeid 运算符,关于 RTTI,先激活RTTI: 在Project 菜单中, 选Setting项, 单击C/C+标签, Category列表中, 选C+ Language, 单击Enable RTTI 后 OK.,运行时类型识别(Run-time type identification , RTTI)是指,在只有指向基类的指针或引用时,确定所指对象的准确类型的操作。 一般情况下,虚函数机制并不需要一个类的确切类型,就可以对那种类型的对象实施正确行为。但是,虚函数仅能识别而不能反映出确切类型。 和很多其他语言一样,C+是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求, C+中,RTTI的概念,的指针或引用(Reference)本身的类型,可能与它实际代表(指向或引用)的类型并不一致。我们往往需要将一个多态指针转换为其实际指向对象的类型,即对对象类型进行动态判断,也就是动态类型的侦测识别。这就需要知道运行时的类型信息,于是就产生了运行时类型识别的需求。为了解决这种问题,多数类库设计者会把虚函数放在基类中,使运行时返回特定对象的类型信息。我们可能见过一些名字为isA( )和typeOf() 之类的成员函数,这些就是开发商定义的RTTI函数。,42,使用typeid 运算符的前提: 必须有typeinfo类的支持。该类为所有的内置类型和多态类型的对象保存了运行时类型信息。它在头文件 中定义的。 常用该类的四个成员函数: 测试两个对象的类型是否相同: bool operator =(const typeinfo ,typeinfo类,43,typeid(对象名/类型名) : 返回一个记录着目标类的类型的typeinfo类的对象。可以用来检索非多态类型对象,甚至是基本类型对象的类型信息,但不会依靠vptr 和vtable,返回结果是目标对象的静态类型。 static_cast(源对象):将对象静态转换为目标类型。 dynamic_cast(源对象):若源对象与目标类型存在 is - a 关系,则完成转换,否则失败。 将父类的指针变为子类的指针。,所涉及的运算符,使用RTTI有两种方法。第一种是typeid(),它就像sizeof(),看上去像个函数,但实际上是由编译器实现的运算符。它带有一个参数,可以是一个对象引用或指针。它返回全局的typeinfo类的常对象的引用。可以用运算符“=”和“!=”来比较,也可以用name()来获得类型的名称。如果给typeid()传递一个shape*型参数,它会认为类型为shape*,所以如果想知道一个指针所指对象的精确类型,我们必须逆向引用这个指针:s是个shape* ,那么: cout typeid(*s).name()endl; 将显示出s所指向的对象类型。,RTTI的两种使用方法,为了保持代码的一致性,typeid()也可以用于内部类型。下面的表达式结果将为true: typeid(47) = typeid(int) typeid(0) = typeid(int) 当 int i; 时: typeid(i) = typeid(int) typeid(&i) =typeid(int*),RTTI的第二个用法叫“安全类型向下映射”。之所以用“向下映射”这个词,是由于类继承的排列顺序。如果映射一个circle*到shape*叫向上映射的话,那么将一个shape*映射成一个circle*就叫向下映射了。当然一个circle*也是一个shape*,编译器允许任意的向上映射,但一个shape*不一定就是circle*,所以编译器在没有明确的类型映射时并不允许我们实施一个向下映射任务。 向下映射的一般方法是:创建一个函数,试着将shape*指派为一个circle * ,检查执行过程中的数据类型。如果这个函数返回一个非空地址,则成功;如果返回NULL,说明shape* 并没有指向一个circle类的对象。,C+的RTTI的“安全类型向下映射”就是按照这种“试探映射”函数的格式,但它(非常合理地)用模板语法来产生这个特殊的动态映射函数(dynamic_cast)所以本例变成: shape* sh=new circle; circle* cp=dynamic_cast(sh); if(cp) cout“cast successful”; RTTI允许我们用匿名的多态指针来发现类型信息,所以它常常被初学者滥用,因为它可能在虚函数完成之前就有意义了。,如果想算出各种shape的数目,可以使用下面的框架: circle* cp=dynamic_cast(sh) square* sp=dynamic_cast(sh) triangle* tp=dynamic_cast(sh) 当然这是方法之一。我们还可以在基类类型中放置一个静态数据成员,并在构造函数中对它自增计数。这样我们可以使用静态数据成员和动态映射两种方法结合起来计算shape的个数。,49,void DeviceControl:ControlThem(HomeElectricDevice . ,使用typeid 的例:,是对象的应用,这是类名,用子类的指针访问子类的对象的成员函数。,判两个类型是否相同?,强转成子类的指针。,50,还可以用typeid 检查基本类型和非多态类型: #include #include #include using namespace std; typedef unsigned int UINT ; void func() cout typeid(UINT).name()endl; cout typeid(string).name()endl; ,typeid运算符的使用,显示: “unsigned int” “string”,#include #include /for dynamic_cast using namespace std; class Base virtual void vertFunc() /needed for dynamic cast ; class Derv1 : public Base ; class Derv2 : public Base ; bool isDerv1(Base* pUnknown) Derv1* pDerv1; if( pDerv1 = dynamic_cast(pUnknown) ) return true; else return false; ,用dynamic_cast查询类的类型,void main() Derv1* d1 = new Derv1; Derv2* d2 = new Derv2; if( isDerv1(d1) ) cout “d1 is a member of the Derv1 classn“; else cout “d1 is not a member of the Derv1 classn“; if( isDerv1(d2) ) cout “d2 is a member of the Derv1 classn“; else cout “d2 is not a member of the Derv1 classn“; ,#include #include /for dynamic_cast using namespace std; class Base protected: int ba; public: Base() : ba(0) Base(int b) : ba(b) virtual void vertFunc() /needed for dynamic_cast void show() cout “Base: ba=“ ba endl; ;,用dynamic_cast改变指针的类型,/ class Derv : public Base private: int da; public: Derv(int b, int d) : ba(b) ,da(d) void show() cout “Derv: ba=“ ba “, da=“ da endl; ;,void main() Base* pBase = new Base(10); /pointer to Base Derv* pDerv = new Derv(21, 22); /pointer to Derv /derived-to-base: upcast - points to Base subobject of Derv pBase = dynamic_cast(pDerv); pBase-show(); pBase = new Derv(31, 32); /normal /base-to-derived: downcast - (pBase must point to a Derv) pDerv = dynamic_cast(pBase); pDerv-show(); ,运行结果: Base: ba=21 Derv: ba=31, da=32,#include #include /for typeid() using namespace std; class Base virtual void virtFunc() /needed for typeid ; class Derv1 : public Base ; class Derv2 : public Base ;,用typeid()取得类型名,void displayName(Base* pB) /display name of class cout “pointer to an object of: “; cout typeid(*pB).name() endl; /pointed to by pB /- void main() Base* pBase = new Derv1; /“pointer to an object of class Derv1“ displayName(pBase); pBase = new Derv2; /“pointer to an object of class Derv2“ displayName(pBase); ,59,设有新增子类: class FamilyCinema : publicTelevision public: virtual void OPEN() virtual void CLOSE() virtual void ADJUST(bool updown) virtual void PLAY_VCD() / . private: /attributs . ;,使用dynamic_cast的例:,60,当给DeviceControl的ControlThem()函数传入FamilyCinema而非HomeElectricDevice对象时,问题就出现了: typeid(device) 返回的类型与 typeid(Television)返回的类型不相符,于是if()为假,则给出“ This device connot play VCD!”,这显然不对。每增加新子类,都会出问题。问题就出在 typeid(device)=typeid(Television)上。匹配太严格。 若改为: case PLAY_VCD: try Television tv = dynamic_cast (device); tv.PLAY_VCD(); catch(std:bad_cast ,指针的dynamic_cast,dynamic_cast(p) /把指针p转换成T类型。 如果p属于类T,或者拥有唯一的T类型的基类,那么dynamic_cast返回的是T*类型的指针;否则返回0 其中的p必须是多态类型的指针。 dynamic_cast转换不能违背私有继承和保护继承机制,以及const的限制。 dynamic_cast只能用于指针或引用类型的转换,且只能用于多态类族。,class A : public B, protected C, public D ; void f( A* p) B* p1 = p; / OK B* p2 = dynamic_cast(p); / OK C* p3 = p; / 错误:C是保护类型基类 C* p4 = dynamic_cast(p); / p4将是NULL void g( D* p) A* a = dynamic_cast(p);/ 当D不是多态类时错误, ,引用的dynamic_cast,dynamic_cast(p) 如果p不是T类型的,会抛出一个bad_cast异常 class B ; class D: public B ; void f( D ,64,先激活RTTI,如果编译器没打开RTTI支持,你要打开它; 对象所属类型必须是多态类族; 若使用dynamic_cast转换一个引用,则要使用异常处理机制,因为它可能抛出 std:bad_cast异常; 若使用typeid (*p)来检索对象的类型信息,又恰碰 p = NULL时,将抛出std:bad_typeid异常; 当使用dynamic_cast运算符转换一个指针时,定要检查结果是否为NULL.,使用RTTI的注意事项,65,RTTI的出现不仅解决了异常处理机制中的问题,还能完成虚函数不能完成的工作; RTTI的是个总的概念,它涵盖了如下内容: 运算符typeid 运算符dyna

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论