版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、C+语法要点记录1. 关于class的构造、析构、copy、赋值构造函数的原型定义,以及在什么场合使用什么样的函数。 如果对象在申明的同时马上进行的初始化操作,则称之为拷贝运算。例如: class1 A(af); class1 B=A; 此时其实际调用的是B(A),拷贝构造函数。 如果对象在申明之后,在进行的赋值运算,我们称之为赋值运算。例如: class1 A(af); class1 B; B=A;class StringTestpublic:StringTest( const char * charp=NULL);StringTest();StringTest( const StringT
2、est & st);StringTest & operator(const StringTest &sto);private:char * m_data;/*/* 构造函数 */*/StringTest:StringTest(const char *charp = NULL)if (charp = NULL)m_data = new char1;m_data0 = 0; elsem_data = new charstrlen(charp)+1;strcpy(m_data, charp);/*/* copy构造函数 */拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归/*/Stri
3、ngTest:StringTest(const StringTest &st)/delete m_data;对象这个时候还没有分配空间,区别于赋值构造函数m_data = new charstrlen(st.m_data)+1;strcpy(m_data, st.m_data);StringTest:StringTest()delete m_data;/*/赋值构造函数,需要返回一个对象 /*/StringTest & StringTest:operator(const StringTest &sto) if (&sto = this)return *this; delete m_data;
4、m_data = new charstrlen(sto.m_data)+1; strcpy(m_data, sto.m_data); return *this;如果没有赋值构造函数,采用默认的,那会出现这样的现象Class cla; Cla a; /调用构造函数 Cla b; /调用构造函数 b = a; /调用默认的赋值构造函数 如果这个函数结束后,就会执行两次析构函数,就是说,对于同一块内存释放了两次。所以需要来定义赋值构造函数,为对象重新分配内存从这个我可以总结:1)构造函数赋值构造函数copy构造函数都是先delete原来的内存,然分配合适的内存作为存储。2)赋值构造函数必须有个返回值
5、,这个返回值需要用到一个this指针。存储格式如下,一般的规则是基类存储在最上面,自己本身的变量存储在下面。对于基类函数和自身函数的存储,如果派生类中的成员和基类的成员同名,则在派生类中使用该名称时、或通过派生类对象或指向派生类对象的指针使用该名称时,实际访问的是派生类的成员,这种情况称为派生类成员覆盖了基类中同名的成员。当基类的成员被派生类中同名的成员覆盖时,可以使用基类名和域解析操作符:访问基类中被覆盖的成员#include stdafx.h#include #include using namespace std;class MyStringpublic: char* m_data; p
6、ublic: MyString(const char *str) cout调用构造函数endl; if (str = NULL) m_data = new char1; *m_data = 0; else int length = strlen(str); cout字符串长度为:lengthendl; m_data = new charlength+1; MyStrcpy(m_data,str); cout字符串内容为:m_dataendlendl; MyString() cout调用了析构函数。endl; delete m_data; char* MyStrcpy(char* dest,co
7、nst char* src) if (dest = NULL | src = NULL) return NULL; if (dest = src) return dest; char *tmp = dest; while(*src) *dest = *src; dest+; src+; *dest = 0; dest = tmp; return dest; MyString(const MyString &str) cout调用拷贝构造函数endl; int length = strlen(str.m_data); m_data = new charlength+1; MyStrcpy(m_d
8、ata,str.m_data); cout字符串内容为:m_dataendlendl; MyString& operator=(const MyString &str) cout调用赋值构造函数endl; if (this = &str) return *this; else delete m_data; int length = strlen(str.m_data); m_data = new charlength+1; MyStrcpy(m_data,str.m_data); cout字符串内容为:m_dataendl class MyClass size(8):1 +-1 0 | vfp
9、tr1 4 | var1 +-1 1 MyClass:$vftable:1 | &MyClass_meta1 | 01 0 | &MyClass:fun1 1 MyClass:fun this adjustor: 0从这段信息中我们看出,MyClass对象大小是8个字节。前四个字节存储的是虚函数表的指针vfptr,后四个字节存储对象成员var的值。虚函数表的大小为4字节,就一条函数地址,即虚函数fun的地址,它在虚函数表vftable的偏移是0。因此,MyClass对象模型的结果如图1所示。图1 MyClass对象模型MyClass的虚函数表虽然只有一条函数记录,但是它的结尾处是由4字节的0作
10、为结束标记的。adjust表示虚函数机制执行时,this指针的调整量,假如fun被多态调用的话,那么它的形式如下:*(this+0)0()总结虚函数调用形式,应该是:*(this指针+调整量)虚函数在vftable内的偏移()二、单重继承对象模型我们定义一个继承于MyClass类的子类MyClassA,它重写了fun函数,并且提供了一个新的虚函数funA。class MyClassA:public MyClass int varA;public: virtual void fun() virtual void funA() ;它的对象模型为:1 class MyClassA size(12):
11、1 +-1 | +- (base class MyClass)1 0 | | vfptr1 4 | | var1 | +-1 8 | varA1 +-1 1 MyClassA:$vftable:1 | &MyClassA_meta1 | 01 0 | &MyClassA:fun1 1 | &MyClassA:funA1 1 MyClassA:fun this adjustor: 01 MyClassA:funA this adjustor: 0可以看出,MyClassA将基类MyClass完全包含在自己内部,包括vfptr和var。并且虚函数表内的记录多了一条MyClassA自己定义的虚函数f
12、unA。它的对象模型如图2所示。 图2 MyClassA对象模型我们可以得出结论:在单继承形式下,子类的完全获得父类的虚函数表和数据。子类如果重写了父类的虚函数(如fun),就会把虚函数表原本fun对应的记录(内容MyClass:fun)覆盖为新的函数地址(内容MyClassA:fun),否则继续保持原本的函数地址记录。如果子类定义了新的虚函数,虚函数表内会追加一条记录,记录该函数的地址(如MyClassA:funA)。使用这种方式,就可以实现多态的特性。假设我们使用如下语句:MyClass*pc=new MyClassA;pc-fun();编译器在处理第二条语句时,发现这是一个多态的调用,那
13、么就会按照上边我们对虚函数的多态访问机制调用函数fun。*(pc+0)0()因为虚函数表内的函数地址已经被子类重写的fun函数地址覆盖了,因此该处调用的函数正是MyClassA:fun,而不是基类的MyClass:fun。如果使用MyClassA对象直接访问fun,则不会出发多态机制,因为这个函数调用在编译时期是可以确定的,编译器只需要直接调用MyClassA:fun即可。三、多重继承对象模型和前边MyClassA类似,我们也定义一个类MyClassB。class MyClassB:public MyClass int varB;public: virtual void fun() virtu
14、al void funB() ;它的对象模型和MyClassA完全类似,这里就不再赘述了。为了实现多重继承,我们再定义一个类MyClassC。class MyClassC:public MyClassA,public MyClassB int varC;public: virtual void funB() virtual void funC() ;为了简化,我们让MyClassC只重写父类MyClassB的虚函数funB,它的对象模型如下:1 class MyClassC size(28):1 +-1 | +- (base class MyClassA)1 | | +- (base clas
15、s MyClass)1 0 | | | vfptr1 4 | | | var1 | | +-1 8 | | varA1 | +-1 | +- (base class MyClassB)1 | | +- (base class MyClass)1 12 | | | vfptr1 16 | | | var1 | | +-1 20 | | varB1 | +-1 24 | varC1 +-1 1 MyClassC:$vftableMyClassA:1 | &MyClassC_meta1 | 01 0 | &MyClassA:fun1 1 | &MyClassA:funA1 2 | &MyClassC
16、:funC1 1 MyClassC:$vftableMyClassB:1 | -121 0 | &MyClassB:fun1 1 | &MyClassC:funB1 1 MyClassC:funB this adjustor: 121 MyClassC:funC this adjustor: 0和单重继承类似,多重继承时MyClassC会把所有的父类全部按序包含在自身内部。而且每一个父类都对应一个单独的虚函数表。MyClassC的对象模型如图3所示。 图3 MyClassC对象模型多重继承下,子类不再具有自身的虚函数表,它的虚函数表与第一个父类的虚函数表合并了。同样的,如果子类重写了任意父类的
17、虚函数,都会覆盖对应的函数地址记录。如果MyClassC重写了fun函数(两个父类都有该函数),那么两个虚函数表的记录都需要被覆盖!在这里我们发现MyClassC:funB的函数对应的adjust值是12,按照我们前边的规则,可以发现该函数的多态调用形式为:*(this+12)1()此处的调整量12正好是MyClassB的vfptr在MyClassC对象内的偏移量。四、虚拟继承对象模型虚拟继承是为了解决多重继承下公共基类的多份拷贝问题。比如上边的例子中MyClassC的对象内包含MyClassA和MyClassB子对象,但是MyClassA和MyClassB内含有共同的基类MyClass。为了
18、消除MyClass子对象的多份存在,我们需要让MyClassA和MyClassB都虚拟继承于MyClass,然后再让MyClassC多重继承于这两个父类。相对于上边的例子,类内的设计不做任何改动,先修改MyClassA和MyClassB的继承方式:class MyClassA:virtual public MyClassclass MyClassB:virtual public MyClassclass MyClassC:public MyClassA,public MyClassB由于虚继承的本身语义,MyClassC内必须重写fun函数,因此我们需要再重写fun函数。这种情况下,MyCla
19、ssC的对象模型如下:1 class MyClassC size(36):1 +-1 | +- (base class MyClassA)1 0 | | vfptr1 4 | | vbptr1 8 | | varA1 | +-1 | +- (base class MyClassB)1 12 | | vfptr1 16 | | vbptr1 20 | | varB1 | +-1 24 | varC1 +-1 +- (virtual base MyClass)1 28 | vfptr1 32 | var1 +-1 1 MyClassC:$vftableMyClassA:1 | &MyClassC_
20、meta1 | 01 0 | &MyClassA:funA1 1 | &MyClassC:funC1 1 MyClassC:$vftableMyClassB:1 | -121 0 | &MyClassC:funB1 1 MyClassC:$vbtableMyClassA:1 0 | -41 1 | 24 (MyClassCd(MyClassA+4)MyClass)1 1 MyClassC:$vbtableMyClassB:1 0 | -41 1 | 12 (MyClassCd(MyClassB+4)MyClass)1 1 MyClassC:$vftableMyClass:1 | -281 0
21、| &MyClassC:fun1 1 MyClassC:fun this adjustor: 281 MyClassC:funB this adjustor: 121 MyClassC:funC this adjustor: 01 1 vbi: class offset o.vbptr o.vbte fVtorDisp1 MyClass 28 4 4 0虚继承的引入把对象的模型变得十分复杂,除了每个基类(MyClassA和MyClassB)和公共基类(MyClass)的虚函数表指针需要记录外,每个虚拟继承了MyClass的父类还需要记录一个虚基类表vbtable的指针vbptr。MyClass
22、C的对象模型如图4所示。 图4 MyClassC对象模型虚基类表每项记录了被继承的虚基类子对象相对于虚基类表指针的偏移量。比如MyClassA的虚基类表第二项记录值为24,正是MyClass:vfptr相对于MyClassA:vbptr的偏移量,同理MyClassB的虚基类表第二项记录值12也正是MyClass:vfptr相对于MyClassA:vbptr的偏移量。和虚函数表不同的是,虚基类表的第一项记录着当前子对象相对与虚基类表指针的偏移。MyClassA和MyClassB子对象内的虚表指针都是存储在相对于自身的4字节偏移处,因此该值是-4。假定MyClassA和MyClassC或者MyCl
23、assB内没有定义新的虚函数,即不会产生虚函数表,那么虚基类表第一项字段的值应该是0。通过以上的对象组织形式,编译器解决了公共虚基类的多份拷贝的问题。通过每个父类的虚基类表指针,都能找到被公共使用的虚基类的子对象的位置,并依次访问虚基类子对象的数据。至于虚基类定义的虚函数,它和其他的虚函数的访问形式相同,本例中,如果使用虚基类指针MyClass*pc访问MyClassC对象的fun,将会被转化为如下形式:*(pc+28)0()虚基类表就是记录各自的虚函数表与基类虚函数表之间的offset。这个一定注意虚函数和虚继承之间的巨大差别;1 无继承的情况#include using namespace
24、 std;class Base public: virtual void f() cout Base:f() endl; virtual void g() cout Base:g() endl; virtual void h() cout Base:h() endl; ;int main() typedef void (*Fun)(); Base *b = new Base; cout *(int*)(&b) endl; /虚函数表的地址存放在对象最开始的位置 Fun funf = (Fun)(*(int*)*(int*)b); Fun fung = (Fun)(*(int*)*(int*)b
25、 + 1); Fun funh = (Fun)(*(int*)*(int*)b + 2); funf(); fung(); funh(); cout (Fun)(*(int*)*(int*)b + 3); / 最后一个位置为0,表明虚函数表的结束 return 0;注意:在上面这个图中,虚函数表中最后一个节点相当于字符串的结束符,其标志了虚函数表的结束,在Codeblocks下打印为0。 2 继承,无虚函数覆盖的情形#include using namespace std;class Base public: virtual void f() cout Base:f() endl; virtu
26、al void g() cout Base:g() endl; virtual void h() cout Base:h() endl; ;class Derive: public Base virtual void f1() cout Derive:f1() endl; virtual void g1() cout Derive:g1() endl; virtual void h1() cout Derive:h1() endl; ;int main() typedef void (*Fun)(); Base *b = new Derive; cout *(int*)b endl; Fun
27、funf = (Fun)(*(int*)*(int*)b); Fun fung = (Fun)(*(int*)*(int*)b + 1); Fun funh = (Fun)(*(int*)*(int*)b + 2); Fun funf1 = (Fun)(*(int*)*(int*)b + 3); Fun fung1 = (Fun)(*(int*)*(int*)b + 4); Fun funh1 = (Fun)(*(int*)*(int*)b + 5); funf(); / Base:f() fung(); / Base:g() funh(); / Base:h() funf1(); / Der
28、ive:f1() fung1(); / Derive:g1() funh1(); / Derive:h1() cout (Fun)(*(int*)*(int*)b + 6); return 0;从上表可以发现:1 虚函数按照其声明顺序放于表中。2 父类的虚函数在子类的虚函数前面。 3 继承,虚函数覆盖的情形#include using namespace std;class Base public: virtual void f() cout Base:f() endl; virtual void g() cout Base:g() endl; virtual void h() cout Ba
29、se:h() endl; ;class Derive: public Base virtual void f() cout Derive:f() endl; virtual void g1() cout Derive:g1() endl; virtual void h1() cout Derive:h1() endl; ;int main() typedef void (*Fun)(); Base *b = new Derive; cout *(int*)b endl; Fun funf = (Fun)(*(int*)*(int*)b); Fun fung = (Fun)(*(int*)*(i
30、nt*)b + 1); Fun funh = (Fun)(*(int*)*(int*)b + 2); Fun fung1 = (Fun)(*(int*)*(int*)b + 3); Fun funh1 = (Fun)(*(int*)*(int*)b + 4); funf(); / Derive:f() fung(); / Base:g() funh(); / Base:h() fung1(); / Derive:g1() funh1(); / Derive:h1() cout (Fun)(*(int*)*(int*)b + 5); return 0;从上表可以看出:1 覆盖的f()函数被放到了虚表中原来父类虚函数的位置。2 没有被覆盖的函数依旧。3 可通过获取获取成员函数指针来调用成员函数(即使是
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 浙江省台州市2024-2025学年高二年级上册1月期末统考英语试题(含答案)
- 沪粤版版八年级物理上册期末综合复习试题(含答案及详解)
- 城市排水系统维护与处理指南
- 车辆年检与维修操作手册(标准版)
- 保险理赔与服务流程手册
- 某化工印染厂污水净化办法
- 针织厂来料抽检制度
- 针织厂现场秩序细则
- 某针织厂设备点检细则
- 部编人教版七年级上册语文期末考试试卷(含答案)
- 市场营销基础第5版电子教案课件
- 公司水电安装工管理制度
- 2025年高考语文全国一卷试题真题及答案详解(精校打印)
- 废钢铁销售管理制度
- 《中国传统文化》课件:儒家思想及其人生模式
- 2025新版压疮防治指南解读
- 胃食管反流病
- 洗衣店和单位洗衣合同范本
- 高中英语单选题100道及答案
- 2025年江苏省南京市、盐城市高考数学一模试卷(含答案)
- 上海2024年高考英语试卷
评论
0/150
提交评论