1 / 59

二. C/C++ 预备知识

二. C/C++ 预备知识. 类型转换 显示转换(强行转换) 动态转换 静态转换 动态链接库 名字改编 函数模板 类模板 友元函数. 操作符重载. 变量的存储类型 函数参数的传递 封装 类及其成员 继承 基类与派生类 派生类对象访问基类成员 一致性难题 多态 虚函数 一致性与功能 虚函数实现机制 object slice 多重继承 多重继承的虚表 二义性 RTTI. 1 变量的存储类型.

nuri
Download Presentation

二. C/C++ 预备知识

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 二. C/C++ 预备知识 • 类型转换 • 显示转换(强行转换) • 动态转换 • 静态转换 • 动态链接库 • 名字改编 • 函数模板 • 类模板 • 友元函数. • 操作符重载 • 变量的存储类型 • 函数参数的传递 • 封装 类及其成员 • 继承 基类与派生类 • 派生类对象访问基类成员 • 一致性难题 • 多态 虚函数 • 一致性与功能 • 虚函数实现机制 • object slice • 多重继承 • 多重继承的虚表 • 二义性 • RTTI

  2. 1 变量的存储类型 • 全局变量:在任何函数之外定义,可以被任何代码所访问.在其他的文件中使用这些变量时,使用extern来声明之,使得编译器了解这些变量的类型和名字,但是不重新分配内存. • Static全局变量:只在文件范围内可见. (使得开发者可以任意地使用变量名字,不必担心重名.) • 自动变量:函数体内部.在stack中 • Static局部变量: 在函数体内部定义,编译器为之生成永久存储单元,在调用之前就存在.只在函数内部可见. • 动态分配变量: new delete 在heap中. • 类的static成员变量:只与类有关.属于类而不属于对象的变量.(静态成员函数,没有this指针,其中只能访问静态成员变量.) 以下例子见virtualfun工程

  3. 2 函数参数的传递 • 引用运算符&用来定义一个引用. int num; int &refn=num; refn是num的别名.此语句并没有在内存中创建一个新的变量refn,它只不过通知编译器num有了一个新的名字叫refn. Refn和num是同一个变量. • 1.传值 void swap1(int x,int y) { int temp; temp=x; x=y; y=temp;}; C++中缺省的参数初始化方法是把实参的值拷贝到参数的存储区中. 形参的值可以改变, 但此函数并不能改变实参的值.

  4. 2 void swap2(int *px,int *py) { int temp; temp=*px; *px=*py; *py=temp; }; • 同上,函数不能改变实参的值(即地址值),在这种意义下, 传地址和传值是等价的. • 虽然不能改变实参的的值,但是改变了实参(即地址)所存储的值. • 如果要改变一个指针的值,那么就要使用双重指针. • 3void swap3(int &x,int &y) { int temp; temp=x; x=y; y=temp; } • 与前两个不同,形参是实参的别名.实参形参是同一个变量.

  5. 调用方法: 1.int a=2, b=5; swap1(a,b); cout<<a<<b; //2 5 2. int a=2,b=5; swap2(&a,&b); cout<<a<<b; //5 2 3.int a=2;b=5; swap3(a,b); cout<<a<<b;//5 2 若希望通过一个函数改变一个指针,那么可以使用指针的引用或者更常用地使用双重指针作为函数的参数.

  6. 3 封装 类及其成员 • 类。mobile telephone 手机 class mt { private: int price; public: void work(); void setprice(int initp) {price=inip;}; void getprice(int *oldp) { *oldp=price;}; //数据成员是私有的,客户只能通过公有的方法,在方法内部来访问。 mt(void); ~mt(void); };

  7. C++编译器对于数据成员和函数成员的处理: • 数据成员: • 静态的: 定义了类,就会给静态数据分配内存. • 非静态的: new了实例,每个实例都为之分配. • 函数成员: • 定义了类,实现了该函数后,就有该函数的入口指针.对于新new的实例并不产生新的指针.都是使用同一个函数. 因此存在者重入性和并发性的安全问题,如果函数体里使用了静态的变量(函数级的)的话. • 如果是非静态的成员函数,则每一个实例调用它时,会传入一个指向实例本身的指针 this.实例借此可以访问其他的成员(变量或函数) • 如果是静态的成员函数,则在调用时无this指针,因此不能访问类的数据成员. • 如果是虚函数,则会为此类建立一个虚表,每个表项是一个指针,指向此函数的实现.这是额外的开销.见后文.

  8. 4 继承 基类与派生类 • 4.1 派生类对象访问基类成员 • 共有的性质提升到基类中。 class EE //电子设备 { protected: int price; //保护的数据成员可以被派生类的成员函数访问 public: void Work(); void SetPrice(int initp); void GetPrice(int *oldp); void NVWhoAmI() {cout<<“I am a EE”<<endl;}; //我是谁? 前缀NV表示非虚的意思. EE(void); ~EE(void); }; 派生类继承了基类公共的和保护的成员:

  9. class MT : public EE { protected: int rent; // 月租费. 派生类可以有自己的数据成员和方法。price无需再进行申明. public: void Call(); void MTSetPrice(int price); void MTGetPrice(int *oldp);//派生类成员函数可以访问基类的保护成员,这里故意加上前缀“MT”,已示区别,其实没有必要。 MT(void); ~MT(void); }; 基类公有成员的访问方法:

  10. 通过派生类可以直接访问基类的公有的方法和数据。通过派生类可以直接访问基类的公有的方法和数据。 MT *pmt=new MT; pmt->NVWhoAmI (); // 结果为:I am a EE pmt->pbvar=……; //也可以直接访问基类的公有的数据成员:假设pbvar是EE的公共数据成员 • 基类保护成员的访问方法: 对于基类的保护的数据成员或方法则只能在派生类的成员内部访问,比如: void MT::MTSetPrice (int price) {EE::price =price;} //派生类成员函数访问基类的保护数据成员 客户使用方法: MT *pmt=new MT; pmt->MTSetPrice(5); //客户调用派生类的成员函数,函数内部访问基类 的保护数据成员. pmt->price=5; //出错.不能直接访问.

  11. 4.2 一致性难题 单单使用普通的成员函数有力不从心之处: 这是另一个派生类: class CA : public EE //照相机 { protected: int pixel; //象素 public: void TakePhoto(int *pix); CA(void); ~CA(void); }; • 对于以下代码: MT *pmt=new MT; CA *pca=new CA; pmt->NVWhoAmI (); //派生类访问基类的公有的方法。 pca->NVWhoAmI ();//另一个派生类访问基类的公有的方法。

  12. 结果为:I am a EE I am a EE 结果当然是一样的.但是我们期望能得到更精细的信息,为此在派生类中覆盖此函数 class MT : public EE { protected: int rent; public: void Call(); void NVWhoAmI(); //签名虽然完全相同,但它是派生类定义的成员, 或者理解成为碰巧完全相同。当然我们可以再加上一个前缀MT, 但是会显得太罗嗦, C++支持重载. 虽然这种方式不好,但是是合法的. 其实现如下: void MTSetPrice(int price); void MTGetPrice(int *oldp); MT(void); ~MT(void); };

  13. void MT::NVWhoAmI () { cout<<"I am a MT"<<endl;} 类似地 : void CA::NVWhoAmI () { cout<<"I am a CA"<<endl;} 于是我们可以分别出不同的类对象: MT *pmt=new MT; CA *pca=new CA; pmt->NVWhoAmI (); // I am a MT pca->NVWhoAmI (); // I am a CA 比刚才有进步. • 但是, 为了追求统一性,我们往往把基类指针指向子类(派生类)的对象. 在直观上当然是合理的. (彩屏手机也是手机啊!). 子类对象比基类对象更“大”, 子类对象中含有一个基类的子对象 “subobject”. 基类的指针将指向这个子对象. (不包括子类定义的成员). 对于以下代码:

  14. EE *pee[2]; pee[0]=new MT; //基类指针指向子类的对象 pee[1]=new CA; for(int i=0;i<2;i++) pee[i]->NVWhoAmI (); //基类指针只能调用基类定义的成员 结果为: I am a EE I am a EE • 我们用一个基类的指针指向派生类的对象时(MT当然是一个EE,CA也是一个EE),由基类指针来调用NVWhoAmI(我们蒙上眼睛,双手各拿着一个EE(电器设备),它到底是什么?手机还是照相机?) 结果是令人失望的,它们都说:我是一个EE。 Dilemma: • 要么睁大眼睛,分别使用不同的派生类指针来询问其身份(如果有10个不同的派生类,我们要写10句雷同的p*->NVWhoAmI() ) ; • 要么虽然很优雅,使用基类指针通过一个循环来调用. 但只能得到一个笼统的回答。

  15. 5 多态 虚函数 5.1 一致性与功能 我们对于效率,美观,功能的需求导致我们需要多态.虚函数是实现多态的一种方式. 1.当然,还有别的方式. 2.我们付出了代价. 相对于普通函数而言,虚表及虚表指针是额外的开销,处理不当,可能会达到不能忍受的地步,而且我们还要付出脑力的代价 . class EE { protected: int price; public: void Work(); void SetPrice(int initp); void GetPrice(int *oldp); void NVWhoAmI(); virtual void WhoAmI(){cout<<“I am a EE”<<endl;}; //虚函数 关键字virtual EE(void); ~EE(void); }; 派生类的定义如下:

  16. class MT : public EE { protected: int rent; public: void Call(); void NVWhoAmI(); void WhoAmI(); //虽然没有加(也可以加.) virtual 也是虚函数。 MT(void); ~MT(void); }; void MT::WhoAmI () {cout<<“ I am a MT”<<endl;} //改写(override)此虚函数

  17. 对于另一个类CA我们也作类似的处理: void CA::WhoAmI () {cout<<" I am a CA"<<endl;} • 此时对于以下代码 EE *pee[2]; pee[0]=new MT; pee[1]=new CA; for(int i=0;i<2;i++) pee[i]->WhoAmI (); // 1. 一致,优雅 结果为: I am a MT I am a CA // 2. 信息准确,丰富.

  18. 5.2 虚函数实现机制 • 编译器为每个包含虚函数的类产生一个静态函数指针数组,即虚函数表vtbl,在这个类或它的基类中定义的每一个虚函数都有一个相应的函数指针,该指针指向它的实现。该类的每个实例包含一个不可见的数据成员,即虚函数指针vptr,这个指针被构造函数自动初始化,指向类的虚表。当客户调用虚函数的时候,编译器产生代码指向vptr,索引到虚表中,找到函数指针发出调用。 • 此虚表在类有了定义以后就由编译器分配了.它是与类(而不是对象)相关的静态的指针数组.类实例化为对象后,对象的虚表指针将指向此虚表.对象的虚表指针往往放在其他数据成员的前面.对象的this指针将指向此虚表指针. • 虚函数的额外开销: • 类: 包含此虚函数的类(定义的或继承来的)虚表(一个函数一个表项 4字节) • 对象: 每生成一个对象,有一个虚表指针 4字节. • 纯虚函数: 如果是纯虚函数(只是在自己的派生类中实现的),不论是自己定义的还是继承自它的某个基类(直接或间接地),那么,这一项地址为空; 如果在自己或自己以前已经实现,那么将指向这个函数的实现地址(二进制代码的存储地址). 由此,一个包含纯虚函数的类是不能实例化的.否则,它调用此函数该怎么办?

  19. EE类 由于有纯虚函数.不能实例化 EE对虚函数的实现 EE的虚表 WhoAmI WhoAmI EE’s other vf1 已实现 vf1 EE’s other vf2 纯虚 未实现 为空 MT类的实例对象 MT对虚函数的实现 MT的虚表 WhoAmI WhoAmI 被覆盖 this vptr price EE’s vf1 未覆盖 仍然使用基类的 vf2 rent EE’s vf2 在此实现 vg MT’s own vg 自己实现 MT类 • 虚表与虚表指针的示例:

  20. 5.3 object slice • 当基类指针指向派生类对象时,通过基类指针调用虚函数时,编译器会找到其所指向的实际对象对虚函数的实现. • 通过基类指针或引用间接指向派生类的对象时,多态性才起作用.使用基类对象并不能保留住派生类的类型身份. MT mt; EE ee; ee=mt; //object slice 非正常 EE *pee=&mt;//正常 EE &ree=mt; //正常 ee.WhoAmI(); // I am a EE pee->WhoAmI (); // I am a MT ree.WhoAmI ();// I am a MT ee=mt 进行了一次object slice. Mt被slice只剩下一半,而且WhoAmI函数还要使用mt的this指针,情况会如何?实际上编译器自动为EE生成了一个拷贝构造函数.这时ee是一个完全的EE对象.它调用WhoAmI时将使用自己的实现. 我们要避免使用object slice 以上例子见virtualfun工程

  21. 6 多重继承 • 6.1 多重继承的虚表 • 以下例子见multiinh工程. 把以上三个类整理如下: // 1. 电器 class EE {protected: int price; public: virtual void Work(){cout<<"EE is working"<<endl;}; virtual void WhoAmI(){cout<<" I am a EE"<<endl;}; EE(void); ~EE(void); }; //2 手机 class MT : public EE {protected: int rent; public: void WhoAmI(){cout<<“I am a MT”<<endl;}; //改写 virtual void Call(){cout<<“MT is calling”<<endl;};//打电话 MT(void); ~MT(void); };

  22. //3. 照相机 class CA : public EE {protected: int pixel; public: void Work(){cout<<“CA is working”<<endl;};//这里Work也改写了,作为对比,MT没有改写它. void WhoAmI(){cout<<“I am a CA”<<endl;}; //改写 virtual void TakePhoto() {cout<<“CA is takephoto”<<endl;}; //照像 CA(void); ~CA(void); }; • 假设现在有一款能拍照的手机: class MTCA : public MT,public CA // 4. 能拍照的手机 {public: virtual void WhoAmI(){cout<<"I am a MTCA"<<endl;}; //继承自EE. 改写. virtual void Call(){cout<<" MTCA is calling"<<endl;}; //继承自MT 改写 virtual void TakePhoto(){cout<<"MTCA is TakePhoto"<<endl;}; //继承自CA 改写 MTCA(void); ~MTCA(void); }; 其虚表及其虚表指针示意图如下:

  23. EE的虚表 EE对虚函数的实现 Work Work WhoAmI WhoAmI CA对虚函数的实现 CA的虚表 MT的虚表 MT对虚函数的实现 Work Work被覆盖 Work WhoAmI WhoAmI 被覆盖 WhoAmI WhoAmI 被覆盖 TakePhoto TakePhoto Call Call Work MTCA对虚函数的实现 继承自MT WhoAmI 被覆盖 WhoAmI Call被改写 MTCA的虚表 Call Work WhoAmI 被覆盖 TakePhoto 继承自CA TakePhoto被改写 注意两个Work的指向不一样,两个WhoAmI的指向一样! 多重继承 的派生类的虚表

  24. Work this vptr1 继承自MT WhoAmI 被覆盖 vptr2 Call被改写 price MTCA的虚表 Work rent WhoAmI 被覆盖 price pixel 继承自CA TakePhoto被改写 MTCA实例对象 MTCA类 • 虚表指针

  25. EE子对象1 EE子对象2 MT子对象 CA子对象 MTCA对象 6.2 二义性 1. 成员函数二义性: MTCA *pmtca=new MTCA; pmtca->WhoAmI (); // I am a MTCA pmtca->Work(); // MTCA 有两个Work, 二义性, 调用不明确,编译出错 pmtca->CA::Work(); // CA is working pmtca->MT::Work(); // EE is working • MTCA对WhoAmI进行了覆盖,通过MTCA指针调用WhoAmI将指向自己的实现. MTCA没有对Work进行覆盖,其虚表中有两项Work,分别指向MT和CA对其的实现.因此pmtca->Work().无法通过编译.因为编译器不知道要调用哪一个. pmtca->CA::Work(); 将调用CA的实现,而CA覆盖了它; pmtca->MT::Work(); 将调用MT的实现,而MT没有覆盖它,直接使用EE的实现. • MTCA对象中含有一个MT子对象和一个CA子对象, 这两个子对象分别含有一个EE子对象:(如图). 通过这两个EE子对象调用Work结果是不同的.

  26. 2. 转换二义性: EE *pee[3]; MT *pmt=pmtca; CA *pca=pmtca; pmt->Work (); // EE is working pca->Work (); // CA is working pee[0]=(EE*)(MT*)pmtca; //转换途径1 pee[1]=(EE*)(CA*)pmtca; //转换途径2 pee[2]=(EE*)pmtca; //存在二义性, 转换不明确,编译出错 for(int i=0;i<2;i++) pee[i]->Work (); // EE is working, CA is working 多态.仍由虚函数引起. 这种二义性正是多重继承遭到诸多非议的原因.使用虚继承可以消除这种二义性.

  27. 7 RTTI • 为了一致性,我们往往把基类指针指向子类对象. 面对一个基类指针,在行动之前,要准确地探明它指向的是什么.即RTTI. • 我们定义的WhoAmI就是一个简单的RTTI. • 运算符typeid是一个重载的运算符,它可以有一种以上的形式,它的参数可以是类名称或对象的指针或对象变量,它能在运行期判断变量的类型。 • 运算符的返回值是一个type_info的引用类型。type_info是一个类,定义在typeinfo.h中: class type_info { public: virtual ~type_info(); int operator==(const type_info& rhs) const; int operator!=(const type_info& rhs) const; int before(const type_info& rhs) const; const char* name() const; const char* raw_name() const; private: ... }; • 类type_info 重载了== 和!=,用以对两个类型进行比较操作,它的成员函数name可以输出类型的字符串型的名字.

  28. typeid有两种典型的用法: • 用法一: void main() { MT* pm = new MT; EE* pe = pm; cout << typeid( pe ).name() << endl; //class EE变量是EE类型 cout << typeid( *pe ).name() << endl; //class MT指向的却是MT cout << typeid( pm ).name() << endl; // class MT cout << typeid( *pm ).name() << endl; // class MT delete pd; }

  29. 用法二: void DoYourWork(EE * pe) { if (typeid(MT)==typeid(*pe)) (MT*)pe->Call(); if (typeid(CA)==typeid(*pe)) (CA*)pe->TakePhoto(); } //在准确地了解了对象的类型后,就可以正确地转换,从而进行正确的调用. void main() { EE * pe1=new MT; EE * pe2=new CA; DoYourWork(pe1); DoYourWork(pe2); }

  30. pmt pmtca Work vptr1 继承自MT WhoAmI 被覆盖 vptr2 Call被改写 price MTCA的虚表 Work rent WhoAmI 被覆盖 price 继承自CA TakePhoto被改写 pixel MTCA实例对象 MTCA类 8 类型转换 • 8.1 显示转换(强行转换): • 8.1.1 向上隐式转换总可以成功(等价于显示转换). 向上的转换是为了得到统一性,一致性. MTCA *pmtca=new MTCA; MT *pmt=pmtca; //等价于MT *pmt=(MT*)pmtca; 转换以后的效果如下图所示:( 蓝色加亮部分)

  31. 8.1.2 向下的隐式转换无法通过编译,向下的显示转换虽然可以通过编译,但是应用程序无法得到转换的信息,而且有可能发生运行时错误(实际上,显示转换表面上总是可以成功,哪怕根本不相关的转换.但是有可能是假象,从而造成运行时错误): 向下的转换是为了调用子类定义的成员.使用基类指针是不能调用的. MT *pmt1=new MT; MTCA *perr=pmt1;//向下的隐式转换出错 MTCA *pmtca1,*pmtca2; pmtca1=(MTCA*)pmt1;//向下显示转换可以成功 pmt1指向的是一个MT pmtca2=(MTCA*)pmt; // pmt指向的是一个MTCA. 见前页. pmtca1->WhoAmI ();// I am a MT pmtca2->WhoAmI ();// I am a MTCA 用户只有通过“RTTI”才能分清. pmtca1->TakePhoto (); // 更为危险的是,此调用虽然通过编译,将引发运行时错误!因为pmtca1根本就指向的是MT,没有TakePhoto方法! pmtca2->TakePhoto ();//这次,碰巧成功了.但是用户完全无法控制. 向下转换如下图所示:

  32. pmt pmtca Work vptr1 继承自MT WhoAmI 被覆盖 vptr2 Call被改写 price MTCA的虚表 Work rent WhoAmI 被覆盖 price 继承自CA pixel TakePhoto被改写 MTCA实例对象 MTCA类 • 向下的显示的转换,从pmt->pmtca. 即(pmtca=(MTCA*)pmt; 用户无法从转换结果中判断pmt最初指向的是一个完整的子类对象(白色加蓝色),还是一个基类对象(蓝色). • 当pmt是一个完整的对象时(蓝色+白色). 转换是真正成功. 否则,是一个假象. 无论哪种情况,客户都将得到一个指针pmtca. 除非使用RTTI, 客户莫辨真伪. • 当pmt是纯蓝时, 使用pmtca调用白色区域.产生运行期错误.

  33. 8. 1.3 交叉转换 交叉转换是为了执行目标类对象所能执行的功能. MT *pmt= new MTCA; CA *pca=pmt;//交叉隐式转换出错 CA *pca=(CA*)pmt; //交叉显式转换可以成功,但是...... pca->WhoAmI (); // I am a MTCA pca->TakePhoto (); // MTCA is calling !!!! //不是MTCA is takephoto!!!! 而且进一步, pca->pixel实际上仍然是MT所定义的rent的值! (如果,比如在MT的构造函数内对rent赋值为50, 在CA的构造函数对pixel赋值为300万. 我们看到pca->pixel=50.) 交叉转换如下图所示: 理想的转换结果希望得到绿色.实际上这正是下文介绍的动态转换.

  34. pmt Work vptr1 继承自MT pmtca WhoAmI 被覆盖 vptr2 Call被改写 price pca MTCA的虚表 Work rent WhoAmI 被覆盖 price 继承自CA TakePhoto被改写 pixel pmt Work vptr2 继承自MT pmtca WhoAmI 被覆盖 vptr2 TakePhoto-> Call price pca MTCA的虚表 Work piexel-> rent WhoAmI 被覆盖 price 继承自CA pixel TakePhoto被改写 • 而实际上显示的cross cast的结果,见棕色部分.尤其注意TakePhoto 函数和Pixel变量,在它们的内存位置实际上保存的是Call的函数指针和rent变量.

  35. 使用显式的交叉转换并不安全.虚表中的可见部分不能被改变. Pca所指向的对象的虚表仍然是原来的虚表.(即MT类的) 编译器根据名字TakePhoto在虚表中找到第三项,但是其中的指针指向的是MT类的Call,其实现是MTCA类的对它的实现 MTCA is takephoto. • 所以声明虚函数成员的时候,其次序也很重要! 如果在MT中再声明一个虚函数 virtual void dd(){cout<<“dfdfdfdfd”<<endl;};并且排在Call函数之前,那么: 以上pca->TakePhoto ();的结果将为: dfdfdfdfd • 这种特性,有时也有其独特的用途.(见COM 聚合模型)

  36. 8.2. 动态转换 运算符语法: dynamic_cast < type-id > ( expression ) • type-id是事先定义的类的指针或引用类型. Expression是一个指针或一个左值. 这里讨论type-id 是一个指针的情形。 8.2.1 Upcast: void f(MTCA* pmc) { MT* pm = dynamic_cast<MT*>(pmc); // 转换到基类 pm指向pmc的MT型子对象 EE* pe = dynamic_cast<EE*>(pm); //转换到基类 pe指向pm的EE型子对象 } 实际上upcast不需要使用dynamic_cast,直接使用隐式转换也可。

  37. 8.2.2 Downcast: void f() { MT* pmt1 = new MTCA; //基类指针指向一个派生类对象 MT* pmt2 = new MT; //基类指针指向一个基类对象 MTCA* pmtca1 = dynamic_cast<MTCA*>(pmt1); // 成功!pmt1本来指的就是一个派生类对象 MT* pmtca2 = dynamic_cast<MTCA*>(pmt2); // 失败!pmt2 指的是一个基类对象,无法转换为子类, pmtca2 == NULL ... } 用户可以根据返回值来决定下一步的走向.使用dynamic_cast可以安全地进行向下转换.避免使用显示的向下转换出现的问题.(见前文显示的向下转换)

  38. 8.2.3 交叉转换: MT *pmt= new MTCA; CA *pca=pmt; //交叉隐式转换出错 CA *pca; pca=(CA*)pmt; //交叉显示转换可以成功,但是...... pca->WhoAmI (); // I am a MTCA pca->TakePhoto (); // MTCA is calling !!!! //不是MTCA is takephoto!!!! //这里pca的虚表指针所指向的虚表的可见部分是MT的虚表 pca=dynamic_cast<CA*>(pmt); pca->WhoAmI (); // I am a MTCA pca->TakePhoto (); // MTCA is takephoto!!!! 转换成功 pca的虚表指针所指向的虚表的可见部分是CA的虚表! 见前图p34

  39. 8.3 静态转换 语法: static_cast < type-id > ( expression ) 静态转换.功能类似于显示的类型转换.不能提供安全性. 8.3.1. Upcast 总是成功.同显示转换. 8.3.2 DownCast: MTCA *pmtca=new MTCA; MT *pmt=pmtca;//向上隐式转换 MT *pmt1=new MT; pmtca1=dynamic_cast<MTCA*>(pmt1);//pmtca1==NULL,应用可借此判断pmt1不是指向的MTCA对象,应用不应继续使用pmtca1. pmtca2=dynamic_cast<MTCA*>(pmt);//pmtca2!=NULL,pmtca2成功地指向一个MTCA对象 pmtca1=static_cast<MTCA*>(pmt1); pmtca1->TakePhoto (); //同显示转换,虽通过编译,但会引发运行时错误. pmtca2=static_cast<MTCA*>(pmt); //在这种情况下与dynamic_cast等价. pmtca2->TakePhoto ();

  40. 8.3.3 交叉转换: MTCA *pmtca=new MTCA; MT *pmt=pmtca; CA *pca; pca=static_cast<CA*>(pmt); //编译出错, static_cast不支持交叉转换. pmt是 MT*型的,与CA*不兼容. 这种特性也是static_cast的安全性较显示转换较高的原因,是静态转换存在的价值所在.(产生编译期的错误,而不是运行期不可预见的错误.) class a; class b; a* pa=new a; b* pb=(b*) pa;//编译通过,但是之后的调用会引起运行时错误. pb=static_cast<b*>(pa);//编译时出错,避免了运行时出错. • static_cast的实现方式是在对象与子对象之间进行偏移计算而得出. 而dynamic_cast,有时会改变虚表指针所指向虚表的可见部分. • 安全性对比: 显示 静态 动态 递增. 隐式转换总是安全的,但是功能有限,只能进行向上转换.

  41. 9 动态链接库 • 动态链接库(Dynamic Link Library DLL).是一个可执行程序.它包含一些库函数、变量、或者是一些资源(对话框、图标等等)。自己不能单独运行,必须依附在其他的可执行程序中运行。运行时期动态地加载到其他进程的地址空间中,而不是在编译时刻链接到应用程序中,这是它的名字的由来(相对于静态链接库)。 DLL可以向外引出(export)变量或函数。 1。静态库 (.lib) 或.o 2。动态库 (.dll) 或.so 简化了项目的管理。 • 节省内存。(访问同一个DLL的进程代码页面共享) • 资源共享。(图标等资源) • 多种语言编程

  42. 例子:在VS studio.net 2003中新建一个DLL工程mangle,选择输出符号。 extern “C” __declspec(dllexport)int myfun(); //头文件 __declspec(dllexport)int fnmangle(); int myfun(){return 10;} //实现文件中 int fnmangle() {return 100;} 关键字__declspec(dllimport) 指示编译器这个函数将对外输出。也可以使用DEF文件的方式列出输出的函数和变量。 LIBRARY mangle EXPORTS myfun @1 语法见msdn文档。 可以使用dumpbin.exe工具查看一个dll的输出符号。 (在IDE中工具-》外部工具-》添加-》vs.net vc7 bin 目录下找到dumpbin.exe 选择使用输出窗口和提示输入参数。) Dumpbin 输入参数 目标路径 $(TargetPath) 选项 /exports 结果为: 1 0 0001126C ??0Cmangle@@QAE@XZ 2 1 00011212 ??4Cmangle@@QAEAAV0@ABV0@@Z 3 2 00011082 ?fnmangle@@YAHXZ 4 3 00036B40 ?nmangle@@3HA 5 4 000112C1 myfun

  43. 动态库的链接 1.静态地、隐式地 可执行文件中链接一个引入库(.lib),加载可执行文件的同时加载了dll 在C++Builder 下的使用方法: 1。新建一个项目bcbcli,加入mangle.h头文件 2。使用COFF2OMF.exe对mangle.lib进行转换 COFF2OMF mangle.lib mangle2.lib 在项目中加入mangle2.lib 3。#include “mangle.h” int res=myfun(); 4.编译 5。把mangle.dll放在应用程序能找到的路径下。

  44. 2. 动态地、显示地 程序执行loadlibrary加载dll 1。 新建一个项目bcbcli,加入mangle.h头文件 2。#include “mangle.h” 3。typedef int ( * MYFUN)(void); MYFUN myf; HANDLE h=LoadLibrary("mangle.dll"); myf=(MYFUN)GetProcAddress(h,"myfun"); int res=(*myf)(); //或res=myf(); 4。编译执行。 这种方法更加灵活,效率更高,只是编码稍稍复杂一点。

  45. 10 名字改编(name mangling) 但是对于未使用extern”C”保护的函数,比如fnmangle,在C++Builder下,无法顺利地链接。这是因为编译器对函数进行了名字改编( name mangling )。 在C++中,为了实现函数重载,以区分名字相同但参数不同的函数,编译器在函数的名字尾部添加了一段文字,这段文字记录了函数的参数类型、参数个数等方面的信息。这样对于重载函数的调用,编译器就能方便地找到应执行的重载函数了。 使用dumpbin或者impdef(BCB提供的工具,输入为dll输出为def)我们可以看到__declspec(dllimport)int myfun()被改编后的名字为“?fnmangle@@YAHXZ“。如前一小节的例子所示. • 然而,不同的编译器厂商对函数名字进行重载的方案是不兼容的。 • 比如同样的这个函数在bc环境下被改编为: @fnmangle$qv (使用bc建一个DLL工程,输出同样的函数,使用dumpbin查看) • A编译器改编后的名字不能被B编译器所识别。因此对于经过名字改编后的函数,(在以上例子中),在VC环境下可以顺利调用,但是在C++Builder环境下则遇到了困难。 • 为了使得vc编译的dll能被bc使用,可以使用别名. 在def文件中加上: • @fnmangle$qv=?fnmangle@@YAHXZ

  46. 我们可以使用extern”C”通知编译器不要对某函数进行名字改编。以便能在C环境下顺利使用(C不支持函数重载)。我们可以使用extern”C”通知编译器不要对某函数进行名字改编。以便能在C环境下顺利使用(C不支持函数重载)。 • 但是类的成员函数都是经过改编了的。即使使用extern”C”也无法防止这种改编操作。

  47. 11 函数模板 • 虚函数:运行时多态性。模板:编译时多态性,或参数式多态性。 • 函数模板 我们可以分别就求绝对值写两个函数,对应int型和double型的参数 int Abs(int N) { return N<0? -N:N;}; double Abs(double N) { return N<0? -N:N;}; 但是更方便地可以写一个函数模板: template<class T> T Abs(T N) { return N<0? -N:N;}; 当给函数模板传递参数时,编译器能根据不同的参数类型生成不同的函数: 比如: cout<<"absolute value of -5 is "<< Abs(-5); 编译器会生成T为int的函数即 int Abs(int N) { return N<0? -N:N;}; 而针对 cout<<"absolute value of -1.2 is "<< Abs(-1.2); 编译器会生成T为double的函数即 double Abs(double N) { return N<0? -N:N;};

  48. 语法: template<class T1,class T2,...> ....f(T1...,T2...,T1...) { ...... } • 关键字 template < class……> • class指任意合法的类型,并不一定是“class”,包括系统的和自定义的。 • <...>内,class关键字可以出现多次,即T1,T2……但不能重复. • 函数的返回值可以是T类型,当然也可以不是. • 函数的参数列表中,必须出现类型列表中的每一个。每一个参数类型至少在参数类表中出现一次。 • 在函数体中,类型参数可以出现在任何可以出现合法类型的地方。

  49. 覆盖模板 模板生成的每个函数版本包含基本相同的代码,只是类型参数不同,但是也可以对特定的参数类型做特殊处理,为此,只要定义与函数模板名同名的普通C++函数,用具体类型而不是类型参数。普通函数将覆盖函数模板,即如果传递普通函数所指的参数类型,编译器将调用这个函数而不是根据模板生成函数。 • 比如: int Abs(int i) { return i;} 将覆盖模板函数 cout<<"absolute value of -5 is "<< Abs(-5)<<endl; absolute value of -5 is –5, 而int Abs(int i,int j) {return i;}则无法覆盖它,因为参数个数不一样。

  50. 12 类模板 • 以下是存放100个整数的类。 class IntList { public: IntList(); int SetItem(int Index,const int &Item); int GetItem(int Index,int &Item); private: int Buffer[100];}; 问题是,如果我们现在要存储200个整数,或者要存放100个double型的数。以上类就无能为力了。从以上类中派生新类也无济于事。 我们定义如下的类模板:

More Related