2C++预备知识

上传人:t****d 文档编号:243000636 上传时间:2024-09-13 格式:PPT 页数:64 大小:206.50KB
返回 下载 相关 举报
2C++预备知识_第1页
第1页 / 共64页
2C++预备知识_第2页
第2页 / 共64页
2C++预备知识_第3页
第3页 / 共64页
点击查看更多>>
资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,二. C/C 预备知识,变量的存储类型,函数参数的传递,封装 类及其成员,继承 基类与派生类,派生类对象访问基类成员,一致性难题,多态 虚函数,一致性与功能,虚函数实现机制,object slice,多重继承,多重继承的虚表,二义性,RTTI,类型转换,显示转换(强行转换),动态转换,静态转换,动态链接库,名字改编,函数模板,类模板,友元函数.,操作符重载,1,1 变量的存储类型,全局变量:在任何函数之外定义,可以被任何代码所访问.在其他的文件中使用这些变量时,使用,extern,来声明之,使得编译器了解这些变量的类型和名字,但是不重新分配内存.,Static,全局变量:只在文件范围内可见. (使得开发者可以任意地使用变量名字,不必担心重名.),自动变量:函数体内部.在,stack,中,Static,局部变量: 在函数体内部定义,编译器为之生成永久存储单元,在调用之前就存在.只在函数内部可见.,动态分配变量:,new delete,在,heap,中.,类的,static,成员变量:只与类有关.属于类而不属于对象的变量.(静态成员函数,没有,this,指针,其中只能访问静态成员变量.),以下例子见,virtualfun,工程,2,2 函数参数的传递,引用运算符&用来定义一个引用.,int num;,int ,refn,是,num,的别名.此语句并没有在内存中创建一个新的变量,refn,它只不过通知编译器,num,有了一个新的名字叫,refn. Refn,和,num,是同一个变量.,1.传值,void swap1(int x,int y), int temp;,temp=x;,x=y;,y=temp;,C+,中缺省的参数初始化方法是把实参的值拷贝到参数的存储区中. 形参的值可以改变, 但此函数并不能改变实参的值.,3,2,void swap2(int *px,int *py), int temp;,temp=*px;,*px=*py;,*py=temp;,;,同上,函数不能改变实参的值(即地址值),在这种意义下, 传地址和传值是等价的.,虽然不能改变实参的的值,但是改变了实参(即地址)所存储的值.,如果要改变一个指针的值,那么就要使用双重指针.,3,void swap3(int &,x,int,&y),int,temp;,temp=x;,x=y;,y=temp;,与前两个不同,形参是实参的别名.实参形参是同一个变量.,4,调用方法:,1.,int a=2, b=5;,swap1(a,b);,coutab;,/2 5,2.,int a=2,b=5;,swap2(,coutab;,/5 2,3.,int a=2;b=5;,swap3(a,b);,coutab;,/5 2,若希望通过一个函数改变一个指针,那么可以使用指针的引用或者更常用地使用双重指针作为函数的参数.,5,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);,;,6,C+,编译器对于数据成员和函数成员的处理:,数据成员:,静态的: 定义了类,就会给静态数据分配内存.,非静态的:,new,了实例,每个实例都为之分配.,函数成员:,定义了类,实现了该函数后,就有该函数的入口指针.对于新,new,的,实例并不产生新的指针,.必须通过实例来调用它,不同的实例都是调用同一个函数. 因此存在者重入性和并发性的安全问题,如果函数体里使用了静态的变量(函数级的或者是外部的)的话.,如果是,非静态,的成员函数,则每一个实例调用它时,会传入一个指向实例本身的指针,this.,实例借此可以访问其他的成员(变量或函数),如果是,静态,的成员函数,则在调用时无,this,指针,因此不能访问类的非静态的成员(变量或者函数).,如果是,虚函数,则会为此类建立一个虚表,每个表项是一个指针,指向此函数的实现.这是额外的开销. 见后文.,虚函数不能是静态的。,7,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”NVWhoAmI ();,/,结果为:,I am a EE,pmtpbvar=;,/,也可以直接访问基类的公有的数据成员:假设,pbvar,是,EE,的公共数据成员,基类保护成员的访问方法:,对于基类的保护的数据成员或方法则只能在派生类的成员内部访问,比如:,void MT:MTSetPrice (int price),就近,EE:price =price,(形参),;,/,派生类成员函数访问基类的保护数据成员,客户使用方法:,MT *pmt=new MT;,pmt-MTSetPrice(5);,/,客户调用派生类的成员函数,函数内部访问基类 的保护数据成员.,pmt-price=5;,/,出错.不能直接访问,.,10,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 ();,/,/另一个派生类访问基类的公有的方法。,11,结果为:,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);,;,12,void MT:NVWhoAmI (), coutI am a MTendl;,类似地,:,void CA:NVWhoAmI (), coutI am a CANVWhoAmI ();,/ I am a MT,pca-NVWhoAmI ();,/ I am a CA,比刚才有进步.,但是问题是,如果针对别的电子设备,我们不得不写下这样的代码:,ptv-NVWhoAmI ();,/ I am a Television,ppc-NVWhoAmI ();,/ I am a Personal Computer,.,prd-NVWhoAmI ();,/ I am a Radio,。,维护困难。,13,0,因此, 为了追求统一性,我们往往把基类指针指向子类(派生类)的对象. 在直观上当然是合理的. (彩屏手机也是手机啊!). 子类对象比基类对象更“大”, 子类对象中含有一个基类的子对象 “,subobject”.,基类的指针将指向这个子对象. (不包括子类定义的成员). 如下图所示:,电子设备 基类,subobject,彩屏手机 子类对象,手机 基类,subobject,电子设备类指针,14,对于以下代码:,EE *pee2 ; /,每个元素都是指针定义的是父类,pee0=new MT;,/,基类指针指向子类的对象,pee1=new CA,;,for(int i=0;iNVWhoAmI ();,/,基类指针只能调用基类定义的成员,结果为:,I am a EE,I am a EE,我们用一个基类的指针指向派生类的对象时(,MT,当然是一个,EE,CA,也是一个,EE),,由基类指针来调用,NVWhoAmI(,我们蒙上眼睛,双手各拿着一个,EE(,电器设备),它到底是什么?手机还是照相机?),结果是令人失望的,它们都说:我是一个,EE。,Dilemma:,要么睁大眼睛,分别使用不同的派生类指针来询问其身份(如果有10个不同的派生类,我们要写10句雷同的,p*-,NVWhoAmI,() ) ;,要么虽然很优雅,使用基类指针通过一个循环来调用. 但只能得到一个笼统的回答。,15,纯虚函数:,没有实现的虚函数成为纯虚函数。包含有纯虚函数的类称为抽象类。抽象类的虚表中纯虚函数的表项为空指针。由此,一个包含纯虚函数的类是不能实例化的.否则,它的实例调用此函数该怎么办?,纯虚函数的意义在于规定一个方法的签名,而由其派生类去实现它。,EE,与,MT,的虚表示意图如下:,16,虚表与虚表指针的示例:,EE,对虚函数的实现,WhoAmI,EEs other vf1,已实现,EEs other vf2,纯虚 未实现 为空,WhoAmI,vf1,EE,的虚表,MT,对虚函数的实现,WhoAmI,被覆盖,EEs vf1,未覆盖,仍然使用基类的,EEs vf2,在此实现,WhoAmI,vf2,MT,的虚表,MTs own vg,自己实现,vg,vptr,price,rent,this,EE,类,由于有纯虚函数.不能实例化,MT,类,MT,类的实例对象,17,使用虚函数实现多态。,EE *pee2;,指针赋值,pee0=new MT;,pee1=new CA,;,for(int i=0;iWhoAmI ();,基类的指针指向不同的子类对象。这些子类对基类所定义的虚函数有不同的实现。当通过基类指针调用这些函数的时候,调用的是子类的带有个性的实现。,如下图所示:,18,MT,对虚函数的实现,WhoAmI,被覆盖,EEs vf1,未覆盖,仍然使用基类的,EEs vf2,在此实现,WhoAmI,vf2,MT,的虚表,MTs own vg,自己实现,vg,vptr,price,rent,this,MT,类,MT,类的实例对象,Pee0,CA,对虚函数的实现,WhoAmI,被覆盖,EEs vf1,未覆盖,仍然使用基类的,EEs vf2,在此实现,WhoAmI,vf2,CA,的虚表,CAs own vh,自己实现,vh,vptr,price,pixel,this,CA,类,CA,类的实例对象,Pee1,19,5.3,object slice,当基类指针指向派生类对象时,通过基类指针调用虚函数时,编译器会找到其所指向的实际对象对虚函数的实现.,通过基类指针或引用间接指向派生类的对象时,多态性才起作用.使用基类对象并不能保留住派生类的类型身份.,MT mt;,EE ee;ee,是个对象 计算机实现的是值的拷贝 覆盖 执行一次构造函数,ee=mt;,/object slice,非正常直接赋值,ee,已经被构造了即已经被初始化了,EE *pee=,/,正常,指向指针不调用构造函数,EE ,/,正常,EE,的引用型,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,工程,20,6 多重继承,6.1 多重继承的虚表,以下例子见,multiinh,工程. 把以上三个类整理如下:,/ 1.,电器,class EE,protected:int price;,public: virtual void Work()coutEE is workingendl;,virtual void WhoAmI()cout I am a EEendl;,EE(void); EE(void); ;,/2,手机,class MT : public EE,protected:int rent;,public:void WhoAmI()cout“I am a MT”endl;,/,改写实现了,WhoAmI,virtual void Call()cout“MT is calling”endl;,/,打电话,MT(void); MT(void); ;,21,/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()coutI am a MTCAendl;,/,继承自,EE.,改写.,virtual void Call()cout MTCA is callingendl;,/,继承自,MT,改写,virtual void TakePhoto()coutMTCA is TakePhotoWhoAmI ();,/ I am a MTCA,调自己的,/没有二义性。因为,MTCA,对其进行了改写,消除了,MT,和,CA,带来的差异性。通过,MTCA,指针调用,WhoAmI,将指向自己的实现,pmtca-Work();,/ MTCA,有两个,Work,二义性, 调用不明确,编译出错,MTCA,没有对,Work,进行覆盖,其虚表中有两项,Work,分别指向,MT,和,CA,对其的实现.无法通过编译. 编译器不知道要调用哪一个.,pmtca-CA:Work();,/ CA is working,/,将调用,CA,的实现,而,CA,覆盖了它;,pmtca-MT:Work();,/ EE is working,/将调用,MT,的实现,而,MT,没有覆盖它,直接使用,EE,的实现.,(类似地,,pmtca-price,也有二义性。必须象,pmtca-CA:price pmtca-MT:price,来使用),26,2.,转换二义性,:(,强制转换造成的二义性),EE *pee3;,pee0=(EE*)pmtca;,/,存在二义性, 转换不明确,编译出错,两个值得拷贝,pee1=(EE*)(MT*)pmtca;,/,转换途径1,只有一个,pee2=(EE*)(CA*)pmtca;,/,转换途径2,这种二义性正是多重继承遭到诸多非议的原因.使用虚继承可以消除这种二义性.但是虚继承和多重继承在语义上已经有了差别。,27,7 RTTI,(,对象指向的到底是什么,),为了一致性,我们往往,把基类指针指向子类对象,.而且这样做总是安全的。我们甚至可以通过此指针(,基类指针,)调用到子类所实现的虚函数。但是除此之外,我们通过这个指针并不能做更多的工作。,基类指针对子类其他独特的个性是一无所知的(白色不可访问的部分但是是存在的),。 面对一个基类指针,在行动之前,要准确地探明它指向的是什么.即,RTTI,(,页,),.,我们定义的,WhoAmI,就是一个简单的,RTTI.,每个类定义的都不一样,MFC,中使用类型识别网的方法来实现,RTTI。,C,提供了两个操作符来支持,RTTI。,typeid,和,dynamic_cast,28,运算符,typeid,(,在运行的时候能知道是什么,)是一个,重载的,运算符,它可以有一种以上的形式,它的参数可以是类名称或对象的指针或对象变量,它能在运行期判断变量的类型。,运算符的返回值是一个,type_info,的引用类型。,type_info,是一个类,定义如下:,class type_info ,public: virtual type_info();,int operator,=,(const type_info,int operator!=(const type_info,int before(const type_info,const char*,name,() const;,const char* raw_name() const;,private: . ;,类,type_info,(,可以用来判断是相等还是不相等,) 重载了,=,和!=,用以对两个类型进行比较操作,它的成员函数,name,可以输出类型的字符串型的名字.,29,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,PE,所指向的对象,cout typeid( pm ).name() endl;,/ class MT,cout typeid( *pm ).name() Call();,可以调,MT,的对象(看不见的指针来调用看不见的函数),if (typeid(CA)=typeid,(*pe),(CA*)pe-TakePhoto();,pe,是指针可以指向任何类型,为什么要判断?二义性 若不判断出现的错误:执行的时候找不到函数,不能直接使用,pe,-Call();,编译期出错。,也不能直接使用,(MT*),pe,-Call();,潜在的运行期错误,可以使用我们定义的,WhoAmI,来代替,typeid,完成其功能,if(pe,-,WhoAmI,().) (MT*),pe,-Call();,在准确地了解了对象的类型后,就可以正确地转换,从而进行正确的调用.,31,这样我们可以安全地使用基类指针来操作子类对象。,void main() ,EE * pe1=new MT;,EE * pe2=new CA;,DoYourWork(pe1);,DoYourWork(pe2);,32,8 类型转换,EE * pe1=new MT;,使用基类指针指向子类对象实际上是一个类型转换。称为,向上转换,,向上转换是为了,统一性,。,if (typeid(MT)=typeid(*pe) (MT*)pe-Call();,把基类指针再转换为子类指针,称为,向下转换,。向下转换是为了,差异性,。,在,C,中有多种转换方法:,隐式转换,显示转换,静态转换,动态转换。,所谓隐式转换即直接使用操作符。不做任何处理。比如,EE * pe1pmt;,隐式转换只能用于向上转换。而且总是能成功。,8.1 显示转换(强行转换):,8.1.1 向上转换,向上显式转换总可以成功(等价于隐示转换).,MTCA *pmtca=new MTCA;,MT *pmt= (MT*) pmtca;,/,等价于,MT *pmt=pmtca;,转换以后的效果如下图所示:( 蓝色加亮部分),33,WhoAmI,被覆盖,Call,被改写,MTCA,的虚表,Work,WhoAmI,被覆盖,TakePhoto,被改写,Work,继承自,MT,继承自,CA,vptr2,price,vptr1,price,rent,pmtca,MTCA,类,MTCA,实例对象,pmt,pixel,34,8.1.2 向下转换,向下的隐式转换无法通过编译,向下的显示转换虽然可以通过编译,但是应用程序无法得到转换的信息,而且有可能发生运行时错误(实际上,显示转换表面上总是可以成功,哪怕根本不相关的转换. 有可能是假象,从而造成运行时错误):,MT *pmt1=new MT; MT *pmt2=new MTCA;,MTCA *pmatca0=pmt1;,/,向下的隐式转换出错,MTCA *pmtca1,*pmtca2;,pmtca1=(MTCA*)pmt1;,/pmt1,指向的是一个,MT,成功,pmtca2=(MTCA*)pmt2;,/ pmt2,指向的是一个,MTCA.,成功,pmtca1-TakePhoto ();,/,危险!此调用虽然通过编译,将引发运行时错误!因为,pmtca1,根本就指向的是,MT,没有,TakePhoto,方法!,pmtca2-TakePhoto (),;/,这次,碰巧成功了.但是用户完全无法控制.,pmtca1-WhoAmI ();,/ I am a MT,pmtca2-WhoAmI ();,/ I am a MTCA,用户只有通过“,RTTI”,才能分清.,向下转换如下图所示:,35,向下的显示的转换,从,pmt-pmtca.,即(,pmtca=(MTCA*)pmt;,用户,无法从转换结果中判断,pmt,最初指向的,是,一个完整的子类对象(白色加蓝色),还是,一个基类对象(蓝色).,当,pmt,是一个完整的对象时(蓝色+白色). 转换真正成功.,当,pmt,是纯蓝时,是一个假象.使用,pmtca,调用白色区域.产生运行期错误.,无论哪种情况,客户都将得到一个指针,pmtca,.,除非使用,RTTI,客户,莫辨,真伪.,WhoAmI,被覆盖,Call,被改写,MTCA,的虚表,Work,WhoAmI,被覆盖,TakePhoto,被改写,Work,继承自,MT,继承自,CA,vptr2,price,vptr1,pixel,rent,pmtca,MTCA,类,MTCA,实例对象,pmt,price,36,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.),交叉转换如下图所示:,理想的转换结果希望得到绿色.实际上这正是下文介绍的动态转换.,37,而实际上显示的,cross cast,的结果,见棕色部分.尤其注意,TakePhoto,函数和,Pixel,变量,在它们的内存位置实际上保存的是,Call,的函数指针和,rent,变量.,WhoAmI,被覆盖,Call,被改写,MTCA,的虚表,Work,WhoAmI,被覆盖,TakePhoto,被改写,Work,继承自,MT,继承自,CA,vptr2,vptr1,price,rent,pmtca,pmt,pca,pixel,price,pca,WhoAmI,被覆盖,TakePhoto- Call,MTCA,的虚表,Work,WhoAmI,被覆盖,TakePhoto,被改写,Work,继承自,MT,继承自,CA,vptr2,price,vptr2,price,piexel- rent,pmtca,pmt,pixel,38,使用显式的交叉转换并不安全.虚表中的可见部分不能被改变.,Pca,所指向的对象的虚表仍然是原来的虚表.(即,MT,类的) 编译器根据名字,TakePhoto,在虚表中找到第三项,但是其中的指针指向的是,MT,类的,Call,其实现是,MTCA,类的对它的实现,MTCA is takephoto.,所以声明虚函数成员的时候,其次序也很重要!,如果在,MT,中再声明一个虚函数,virtual void dd()cout“dfdfdfdfd”TakePhoto ();,的结果将为:,dfdfdfdfd,这种特性,有时也有其独特的用途.(见,COM,聚合模型),39,8.2.,动态转换,运算符语法:,dynamic_cast ( expression ),type-id,是事先定义的类的指针或引用类型.,Expression,是一个指针或一个左值.,这里讨论,type-id,是一个指针的情形。,8.2.1 Upcast,:,void f(MTCA* pmc) ,MT* pm = dynamic_cast(pmc);,/,转换到基类,pm,指向,pmc,的,MT,型子对象,EE* pe = dynamic_cast(pm);,/,转换到基类,pe,指向,pm,的,EE,型子对象,实际上,upcast,不需要使用,dynamic_cast,,直接使用隐式转换也可。,40,8.2.2 Downcast,:,void f(),MT* pmt1 = new MTCA;,/,基类指针指向一个派生类对象,MT* pmt2 = new MT;,/,基类指针指向一个基类对象,MTCA* pmtca1 = dynamic_cast(pmt1);,/,成功!,pmt1,本来指的就是一个派生类对象,MTCA* pmtca2 = dynamic_cast(pmt2);,/,失败!,pmt2,指的是一个基类对象,无法转换为子类,,pmtca2 = NULL,.,用户可以根据,返回值,来决定下一步的走向.使用,dynamic_cast,可以安全地进行向下转换.避免使用显示的向下转换出现的问题.(见前文显示的向下转换),41,8.2.3,cross cast :,MT *pmt= new MTCA;,CA *pca=(CA*)pmt;,/,交叉显示转换可以成功,但是.,pca-WhoAmI ();,/ I am a MTCA,pca-TakePhoto ();,/ MTCA is calling !,/,不是,MTCA is takephoto!,/,这里,pca,的虚表指针所指向的虚表的可见部分是,MT,的虚表,pca=dynamic_cast(pmt);,pca-WhoAmI ();,/ I am a MTCA,pca-,TakePhoto,();,/ MTCA is takephoto!,转换成功,/,pca,的虚表指针所指向的虚表的可见部分是,CA,的虚表!,见前图,p34,42,8.3 静态转换,语法:,static_cast (,expression,),静态转换.功能类似于显示的类型转换.不能提供安全性.,8.3.1.,Upcast,总是成功.同显示转换.,8.3.2 DownCast,:,MT *pmt1=new MT;,MT *pmt2= new MTCA pmtca;,/,向上隐式转换,pmtca1=,dynamic_cast,(pmt1);,/pmtca1=NULL,应用可借此判断,pmt1,不是指向的,MTCA,对象, 不应继续使用,pmtca1.,pmtca2=,dynamic_cast,(pmt2);,/pmtca2!=NULL,pmtca2,成功地指向一个,MTCA,对象,可以使用!,pmtca1=,static_cast,(pmt1);,pmtca1-TakePhoto ();,/,同显示转换,虽通过编译,但会引发运行时错误.,pmtca2=,static_cast,(pmt2);,/,在这种情况下与,dynamic_cast,,显示转换等价.,pmtca2-TakePhoto ();,43,8.3.3 交叉转换:,class a;,class b;,a* pa=new a;,b* pb=(b*) pa;,/,编译通过,但是之后的调用会引起运行时错误.,pb=static_cast(pa);,/,编译时出错,/,static_cast,不支持交叉转换,避免了运行时出错.这种特性也是,static_cast,的安全性较显示转换较高的原因,是静态转换存在的价值所在.(产生编译期的错误,而不是运行期不可预见的错误.),static_cast,的实现方式是在对象与子对象之间进行偏移计算而得出. 而,dynamic_cast,有时会改变虚表指针所指向虚表的可见部分.,安全性对比:,显示 静态 动态 递增.,隐式转换总是安全的,但是功能有限,只能进行向上转换.,44,9 动态链接库,动态链接库(,Dynamic Link Library DLL).,是一个可执行程序.它包含一些库函数、变量、或者是一些资源(对话框、图标等等)。自己不能单独运行,必须依附在其他的可执行程序中运行。运行时期动态地加载到其他进程的地址空间中,而不是在编译时刻链接到应用程序中,这是它的名字的由来(相对于静态链接库)。,DLL,可以向外引出(,export),变量或函数。,1。静态库 (.,lib),或.,o,2。,动态库 (.,dll),或.,so,简化了项目的管理。,节省内存。(访问同一个,DLL,的进程代码页面共享),资源共享。(图标等资源),多种语言编程,45,例子:在,VS 2003,中新建一个,DLL,工程,mangle,,选择输出符号。,extern “C” _declspec(dllexport),int myfun();,/,头文件,_declspec(dllexport),int fnmangle();,int myfun()return 10;,/,实现文件中,int fnmangle() return 100;,关键字_,declspec(dllimport),指示编译器这个函数将对外输出。也可以使用,DEF,文件的方式列出输出的函数和变量。,LIBRARYmangle,EXPORTS,myfun 1,语法见,msdn,文档。,可以使用,dumpbin.exe,工具查看一个,dll,的输出符号。,(在,IDE,中工具外部工具添加, vc7 bin,目录下找到,dumpbin.exe,选择使用输出窗口和提示输入参数。),Dumpbin,输入参数 目标路径 $(,TargetPath),选项 /,exports,结果为:,1 0 0001126C ?0CmangleQAEXZ,2 1 00011212 ?4CmangleQAEAAV0ABV0Z,3 2 00011082,?fnmangleYAHXZ,4 3 00036B40 ?nmangle3HA,5 4 000112C1,myfun,46,动态库的链接,1.静态地、隐式地链接,可执行文件中链接一个引入库(.,lib),,加载可执行文件的同时加载了,dll,在,CBuilder,下的使用方法:,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,放在应用程序能找到的路径下。,47,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。,编译执行。,这种方法更加灵活,效率更高,只是编码稍稍复杂一点。,48,10 名字改编(name mangling),但是,fnmangle,在,CBuilder,下,无法顺利地链接。,这是因为编译器对函数进行了名字改编(,name mangling,)。,C,支持函数重载。同一个函数名可以有不同的参数形式。函数名不再是区分函数的唯一标志。为了实现函数重载,以区分名字相同但参数不同的函数,编译器在函数的名字尾部添加了一段文字,这段文字记录了函数的参数类型、参数个数等方面的信息。这样对于重载函数的调用,编译器就能方便地找到应执行的重载函数了。,使用,dumpbin,或者,impdef(BCB,提供的工具,输入为,dll,输出为,def),我们可以看到,_declspec(dllimport),int myfun(),被改编后的名字为“,?fnmangleYAHXZ,“。,函数,int fnmangle(),被字符串,“,?,fnmangleYAHXZ”,所标识。,从函数的参数信息到字符串后缀的变化是一个映射。称为名字改编方案。这种方案可以很灵活,只要能建立起一个一一映射即可。并没有一个国际标准规定应该如何建立映射。实际上,各家编译器厂商采用了各自的名字改编方案。,49,比如同样的这个函数在,bc,环境下被改编为: ,fnmangle$qv (,使用,bc,建一个,DLL,工程,输出同样的函数,使用,dumpbin,查看),A,编译器改编后的名字不能被,B,编译器所识别。因此对于经过名字改编后的函数,(在以上例子中,在,VC,环境下可以顺利调用,但是在,CBuilder,环境下则遇到了困难。),临时的解决方法:,为了使得,vc,编译的,dll,能被,bc,使用,可以使用别名. 在,def,文件中加上:,fnmangle$qv=?fnmangleYAHXZ,这种方法不具有通用性,繁琐,笨拙。,我们可以使用,extern”C”,通知编译器不要对某函数进行名字改编。以便能在,C,环境下顺利使用(,C,不支持函数重载)。,但是类的成员函数都是经过改编了的。即使使用,extern”C”,也无法防止这种改编操作。,50,11 函数模板,虚函数:运行时多态性。模板:编译时多态性,或参数式多态性。,函数模板,我们可以分别就求绝对值写两个函数,对应,int,型和,double,型的参数,int Abs(int N), return N0? -N:N;,double Abs(double N), return N0? -N:N;,但是更方便地可以写一个函数模板:,template T Abs(T N), return N0? -N:N;,当给函数模板传递参数时,编译器能根据不同的参数类型生成不同的函数:,比如:,coutabsolute value of -5 is Abs(,-5,);,编译器会生成,T,为,int,的函数即,int Abs(int N), return N0? -N:N;,而针对,coutabsolute value of -1.2 is Abs(,-1.2,);,编译器会生成,T,为,double,的函数即,double Abs(double N), return N0? -N:N;,51,语法:,template .f(T1.,T2.,T1.),.,关键字,template ,class,指任意合法的类型,并不一定是“,class”,,包括系统的和自定义的。,内,,class,关键字可以出现多次,即,T1,T2,但不能重复.,函数的返回值可以是,T,类型,当然也可以不是.,函数的参数列表中,必须出现类型列表中的每一个。每一个参数类型至少在参数类表中出现一次。,在函数体中,类型参数可以出现在任何可以出现合法类型的地方。,52,覆盖模板,模板生成的每个函数版本包含基本相同的代码,只是类型参数不同,但是也可以对特定的参数类型做特殊处理,为此,只要定义与函数模板名同名的普通,C,函数,用具体类型而不是类型参数。普通函数将覆盖函数模板,即如果传递普通函数所指的参数类型,编译器将调用这个函数而不是根据模板生成函数。,比如:,int Abs(int i),return i;,将覆盖模板函数,coutabsolute value of -5 is Abs(-5)endl;,absolute value of -5 is 5,而,int Abs(int i,int j) return i;,则无法覆盖它,因为参数个数不一样。,53,12 类模板,以下是存放100个整数的类。,class IntList, public:,IntList();,int SetItem(int Index,const int ,int GetItem(int Index,int ,private:,int Buffer100;,问题是,如果我们现在要存储200个整数,或者要存放100个,double,型的数。以上类就无能为力了。从以上类中派生新类也无济于事。,我们定义如下的类模板:,54,template class CList, public:,CList(T intVal),int SetItem(int Index,const T,int GetItem(int Index,T,private:,T BufferI;,;,T,是类型参数,,I,是常量参数。,T,和,I,的值在生成具体类的实例时指定。,内可以包括任意个类型参数和常量参数。当然也不能重复。,类型参数可以是任何有效类型。,在类定义中,可以在任何可以使用类型指定的地方使用类型参数,在任何使用类型常量的地方使用常量参数。,模板类的成员函数:,55,template int class CList:SetItem(int Index,const T &Item), if(IndexI-1) return 0;,BufferIndex=Item;,return 1;,1。,以关键字开头:,template,2。类名后应接上模板参数名单,CList,根据模板类生成对象:,实例化,专门化。,CListIntList;,IntList,是,CList,的实例。所有的,T,换成,int,,所有的,I,换成100。又如:,CList StringList;,模板类的对象也可以动态生成(放在,heap,中),CList * DoubleList;,DoubleList=new CList;,也可以给模板类加入构造函数:,56,template CList:CList(T iniVal), for (int i=0;ivalue=v, setV,是非成员函数,没有,this,指针,它需要一个对象作为其参数.,ob.value=v,友元函数只是一个普通的函数,只不过在类中说明,可以访问类的对象的私有
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 大学资料


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!