15(多重继承、虚继承的内存布局)
c++ 期末复习知识点整理

基本概念:1.c++是在20世纪80年代初期由贝尔实验室设计的一种在c语言基础上增加了对(面向对象)程序设计支持的语言。
2.include<iostream.h>是c++库文件。
cout<<"输入";//输出cin>>r; //输入3.一个c++程序由编译预处理指令,数据或数据结构定义和若干函数组成。
4.一个c语言程序可以包含若干函数,其中main表示主函数,每个函数的执行必须从main开始。
5.c++将数据分为基本数据类型,派生类型,符合类型=构造类型,VC6.0不支持bool型6.指数形式,E或(e)后面必须是整数7.c++不支持空字符常量['']是不合法,[""]是合法;endl表示换行。
8.如果需要在字符串中出现双引号,则必须用"\"表示。
例如:"please press\"F1\" to help"9.变量的默认类型为double10.使用const,define表示定义标识符常量。
11.条件运算符nNum=(a>b)?10:812.size of是用于返回操作数所占的内存空间大小13.break用于结束switch循环或从一个循环跳出。
continue用于结束本次循环进入到一个循环中。
14.c++是一种面向对象的程序设计语言,它与面向过程设计方法的最大不同是引入了“类和对象”的概念,而此时函数是构造“类”成员的一种手段。
15.函数调用时,实参与形参的个数应相等,类型应一致,且按顺序对应,一一传递数据16.在c++中,允许在函数声明或定义时给一个或多个参数指定默认值,这样在调用时,可以不给出参数,而按指定的默认值进行工作。
(1)函数既有原型声明又有定义时,默认参数只能在原型生命中指定。
(2)当一个函数中需要有多个默认参数时,则形参分配中。
C++ 第7章 继承2

class Vehicle {//...}; class Motor {//...}; class Car : public Vehicle { public: Motor motor; }; void vehicleFn(Vehiccle& v); void motorFn(Motor& m); void main( ) { Car c; vehicleFn(c); //ok motorFn(c); //error motorFn(c.motor); //ok }
23
7-1-4 类的分解及抽象类
声明一个函数是纯虚函数的语法,即让C++知 道该函数无定义,在Account类中示例如 下:
class Account { public: Account(unsigned accNo,float balan=0.0); int AccountNo(); float AcntBalan(); static Account * First(); Account* Next(); Static int NoAccounts(); void Display(); void Deposit{float amount}; virtual void Withdrawal(floa tamount)=0; //纯虚函数 protected: static Account* pFirst; Account* pNext(); static int count; unsigned acntNumber; float balance; };
24
7-1-4 类的分解和抽象类
一个抽象类不能有实例对象,即不能由该类抽象 来制造一个对象。所以,下面的声明是非法的:
C++ 99个常见错误

目录 常见错误 1:过分积极的注释
பைடு நூலகம்
常见错误 27:........................................................................................................................ 44 常见错误 28:........................................................................................................................ 44 第四章 强制型别转换问题 ......................................................................................................... 45 常见错误 29:........................................................................................................................ 45 常见错误 30:........................................................................................................................ 45 常见错误 31:.............................................................................................................
C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容

C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执⾏顺序和执⾏内容⼀、本⽂⽬的与说明1. 本⽂⽬的:理清在各种继承时,构造函数、复制构造函数、赋值操作符、析构函数的执⾏顺序和执⾏内容。
2. 说明:虽然复制构造函数属于构造函数的⼀种,有共同的地⽅,但是也具有⼀定的特殊性,所以在总结它的性质时将它单独列出来了。
3. 单继承、多继承、虚继承,既然都属于继承,那么虽然有⼀定的区别,但还是相同点⽐较多。
如果放在⼀块讲,但为了将内容制作成递进的,就分开了,对相同点进⾏重复,(⼤量的复制粘贴哈),但在不同点进⾏了标注。
注意:三块内容是逐步递进的如果你懂虚函数,那么单继承和多继承那块你就可以不看;如果你懂多继承,那单继承你就不要看了,⾄于虚继承就等你懂虚继承再回来看吧;如果你只懂单继承,那你就只看单继承就好。
⼆、基本知识1. 对于⼀个空类,例如:class EmptyClass{};虽然你没有声明任何函数,但是编译器会⾃动为你提供上⾯这四个⽅法。
class EmptyClass {public:EmptyClass(); // 默认构造函数EmptyClass(const EmptyClass &rhs); // 复制构造函数~EmptyClass(); // 析构函数EmptyClass& operator=(const EmptyClass &rhs); // 赋值运算符}对于这四个⽅法的任何⼀个,你的类如果没有声明,那么编译器就会⾃动为你对应的提供⼀个默认的(注意合成默认构造函数是⽤于没有编写构造函数编译器才会合成默认构造函数,其中复制构造函数也是构造函数)。
(在《C++ primer》中,这个编译器⾃动提供的版本叫做“合成的***”,例如合成的复制构造函数)当然如果你显式声明了,编译器就不会再提供相应的⽅法。
2. 合成的默认构造函数执⾏内容:如果有⽗类,就先调⽤⽗类的默认构造函数。
c语言三种继承方式

c语言三种继承方式C语言中的继承方式有三种,分别是单继承、多继承和多重继承。
1. 单继承在C语言中,单继承是指一个类只能继承自一个父类。
通过单继承,子类可以继承父类的成员变量和成员函数,并且可以在子类中对这些成员进行重写或扩展。
这种继承方式可以实现代码的重用和扩展,提高了代码的可维护性和可读性。
2. 多继承多继承是指一个类可以同时继承自多个父类。
通过多继承,子类可以继承多个父类的成员变量和成员函数。
在C语言中,可以通过结构体来实现多继承的效果。
子类可以通过结构体嵌套的方式,将多个父类的成员变量和成员函数组合在一起,从而实现多继承的效果。
多继承可以在一定程度上提高代码的复用性,但也增加了代码的复杂性和理解难度。
3. 多重继承多重继承是指一个类同时继承自多个父类,并且这些父类之间存在继承关系。
通过多重继承,子类可以继承多个父类的成员变量和成员函数,并且可以通过继承链的方式,依次调用父类的成员函数。
在C语言中,可以通过结构体嵌套的方式来实现多重继承。
多重继承可以实现更复杂的代码结构,但也增加了代码的复杂性和维护难度。
继承是面向对象编程中的重要概念,通过继承可以实现代码的重用和扩展。
在C语言中,可以通过结构体嵌套的方式来模拟继承的效果。
通过继承,可以将相关的代码组织在一起,提高代码的可读性和可维护性。
在实际的程序设计中,选择何种继承方式应根据具体的需求和设计考虑。
单继承适用于简单的继承关系,多继承适用于需要同时继承多个父类的情况,多重继承适用于父类之间存在继承关系的情况。
不同的继承方式在代码结构和功能实现上有所不同,需要根据实际情况进行选择。
在使用继承时,需要注意继承关系的合理性和代码的可维护性。
继承关系应符合面向对象编程的设计原则,避免出现过于复杂的继承链和多重继承导致的代码混乱。
同时,需要注意在子类中对父类成员的访问权限控制,避免破坏封装性和安全性。
C语言中的继承方式包括单继承、多继承和多重继承。
通过继承,可以实现代码的重用和扩展,提高代码的可维护性和可读性。
C++中虚函数工作原理和(虚)继承类的内存占用大小计算

C++中虚函数工作原理和(虚)继承类的内存占用大小计算一、虚函数的工作原理虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。
典型情况下,这一信息具有一种被称为vptr(virtual table pointer,虚函数表指针)的指针的形式。
vptr 指向一个被称为vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到vtbl。
当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的vptr 指向的vtbl,然后在vtbl 中寻找合适的函数指针。
虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。
如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。
而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。
所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。
故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。
虚函数需要注意的大概就是这些个地方了,之前在More effective C++上好像也有见过,不过这次在Visual C++权威剖析这本书中有了更直白的认识,这本书名字很牛逼,看看内容也就那么回事,感觉名不副实,不过说起来也是有其独到之处的,否则也没必要出这种书了。
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。
面向对象的三大特性(封装-继承-多态)

一丶封装1 权限修饰符可以用来修饰成员变量和成员方法,对于类的权限修饰只可以用public和缺省default。
被public修饰的类可以在任意地方被访问;default类只可以被同一个包内部的类访问。
权限由大到小:public protected default(不写) private被private修饰的成员只能在本类中访问,外界不能访问2 set()/get()方法(1)this关键字a.可以用来调用变量,方法,构造方法;b.this.xx 理解为调用当前类的xx。
(2)成员变量和局部变量1)在类中的位置不同a:成员变量:在类中,方法外b:局部变量:在方法声明上(形式参数),或者是在方法定义中2)在内存中的位置不同a:成员变量:在堆内存b:局部变量:在栈内存3)生命周期不同a:成员变量:随着对象的创建而存在,随着对象的消失而消失b:局部变量:随着方法调用而存在,随着方法的调用结束而消失4)初始化值不同a:成员变量:有默认值b:局部变量:必须初始化值,否则报错!(在使用它之前,没有初始化) (3)set()/get()方法当成员变量被private修饰时,不在本类中无法直接访问,便需要set()/get()方法来解决这个问题3 封装性封装:是面向对象的第一大特性,所谓封装,就是值对外部不可见(一般而言被private修饰),外部只能通过对象提供的接口(如set()/get()方法)来访问。
封装的好处:a.良好的封装能够减少耦合;b.类内部的结构可以自己修改,对外部影响不大;c.可以对成员进行更精准的控制(防止出现与事实不符的情况);d.影藏实现细节。
注意:在开发中,类的成员变量全部要进行封装,封装之后通过set()/get()方法访问。
二丶继承extends1 实现:通过 class Zi extends Fu{} 实现类的继承(1)子类继承父类,父类中声明的属性,方法,子类都可以获取到;当父类中有私有的属性方法时,子类同样可以获取到,由于封装性的设计,使得子类不能直接调用访问。
封装、继承和多态的概念

封装、继承和多态的概念
封装、继承和多态是面向对象编程中的三个重要概念,下面分别进行详细解释:
一、封装
封装是指将对象的属性和方法封装在一起,形成一个独立的单元,对外部隐藏对象的实现细节,只暴露必要的接口供外部使用。
封装可以有效地保护对象的数据和行为,避免外部的误操作和非法访问,提高了代码的安全性和可维护性。
在面向对象编程中,封装是实现信息隐藏和数据保护的重要手段。
二、继承
继承是指一个类可以从另一个类中继承属性和方法,从而可以重用已有的代码和功能。
继承是面向对象编程中实现代码复用的重要手段,可以减少代码的重复性,提高代码的可读性和可维护性。
继承可以分为单继承和多继承两种方式,单继承是指一个类只能从一个父类中继承,而多继承是指一个类可以从多个父类中继承属性和方法。
三、多态
多态是指同一个方法在不同的对象上可以有不同的行为,即同一个方法可以有多
种不同的实现方式。
多态是面向对象编程中的重要概念,可以提高代码的灵活性和可扩展性。
多态可以分为编译时多态和运行时多态两种方式,编译时多态是指方法的重载,即同一个类中可以有多个同名但参数不同的方法;而运行时多态是指方法的重写,即子类可以重写父类的方法,从而实现不同的行为。
通过多态,可以实现面向对象编程中的“开闭原则”,即对扩展开放,对修改关闭。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1.多重继承、虚继承的内存空间布局对多重继承、虚继承的内存空间布局进行研究,循序渐进的进行处理,主要关注以下几点:1)偏移表2)虚表3)数据成员4)它们的位置5)它们的大小及内容6)它们间的关系。
1.1 单继承,无虚函数的情况单继承、无虚函数的情况是:1)基类的数据成员2)派生类新增的数据成员派生类的大小是基类数据成员和派生类新增数据成员大小之和。
顺序是按照上面的基类、派生类的顺序进行布局。
1.2 单继承,有虚函数的情况单继承、有虚函数的情况:1)派生类的虚表指针2)基类的数据成员3)派生类新增的数据成员其中,派生类的虚表,是在基类的虚表基础之上所作的修改,有可能是:1)对基类中虚函数地址的覆盖2)派生类中新增的虚函数地址1)只要有虚函数,就有虚表产生。
2)虚表中条目的个数,是本类中虚函数的个数3)虚表中各条目的顺序,与类中声明(定义)的虚函数顺序一致多重继承、无虚函数的情况是:1)基类的数据成员2)基类的数据成员3)派生类新增的数据成员这里与1.1 单继承,无虚函数的情况的差别是——可能存在多个基类。
这里基类数据成员的排放,是按照继承的数据依次进行的。
1.4 多重继承,有虚函数的情况多重继承,有虚函数的情况是:1)基类的虚表指针2)基类的数据成员3)基类的虚表指针4)基类的数据成员5)派生类新增的数据成员这里与1.2 单继承,有虚函数的情况的差别是——虚表这里说基类的虚表指针,其实是不太恰当的,因为它们实际上是派生类虚表的一部分。
也就说,派生类的虚表是由多个基类的虚表所构成的。
不存在一个单一的派生类的虚表。
派生类的虚表条目是在各基类的虚表基础之上修改所得,可能包括:1)对基类中虚函数的覆盖,会更新各基类虚表中的条目2)派生类中新增的虚函数地址,会追加到第一个继承的基类的虚表中至此,上面1.1 单继承,无虚函数的情况1.2 单继承,有虚函数的情况1.3 多重继承,无虚函数的情况1.4 多重继承,有虚函数的情况是从单继承/多重继承,无/有虚函数的角度进行的梳理。
下面将以菱形继承为主线,来进行梳理。
(菱形继承中可能出现二义性,会逐步的引入虚继承,虚基类的概念)菱形继承(diamond-inheritance)Class A {};Class B: public A {};Class C: public A {};Class D: public B, public C {};菱形继承,无虚函数的情况是:1)基类B的数据成员a)基类A的数据成员b)派生类B新增的数据成员2)基类C的数据成员a)基类A的数据成员b)派生类C新增的数据成员3)派生类D新增的数据成员这里仍然是没有太大的变化,按照基类、派生类的顺序安放数据成员。
1.6 菱形继承,有虚函数的情况Class A {};Class B: public A {};Class C: public A {};Class D: public B, public C {};菱形继承,有虚函数的情况:1)基类B的虚表指针a)基类A的虚函数(未被覆盖的部分)b)基类B的虚函数(覆盖A的部分,新增的部分)c)派生类D的虚函数(新增的部分)2)基类B的数据成员a)基类A的数据成员b)派生类B新增的数据成员3)基类C的虚表指针a)基类A的函数(未被覆盖的部分)b)基类C的虚函数(覆盖A的部分,新增的部分4)基类C的数据成员a)基类A的数据成员b)派生类C新增的数据成员5)派生类D新增的数据成员仍然要说一点,这里说基类的虚表指针,其实是不太合适的,它们是派生类的虚表的一部分,是派生类在基类的虚表基础之上所做修改而来的:1)如果派生类中的虚函数与基类中的形成覆盖,则派生类会对基类的虚表中相应条目做覆盖处理2)派生类中新增的虚函数地址,追加至第一个继承的基类虚表中。
1.7 菱形继承,无虚函数,为虚继承的情况在上面的1.5 菱形继承,无虚函数的情况1.6 菱形继承,有虚函数的情况中,最基类A,在内存空间中有多份拷贝。
利用虚继承可以解决,此时最基类A成为虚基类。
所以,菱形继承,无虚函数,为虚继承的情况,也就是菱形继承,无虚函数,有虚基类的情况。
虚继承的引入,使得虚基类在内存中仅存一份拷贝,同时带来的影响还有内存空间布局的变化。
大概有:1)虚基类的数据成员在内存中的位置2)偏移表偏移表的存在,是因为——虚基类的单份存在,而虚基类A又被B, C所共享,所以对B,C 而言,它们就各自需要确定A的所在位置。
偏移表就是用于该问题。
偏移表的数目,就是直接继承自虚基类的派生类的数目。
现在来一一测试。
在看到这些信息后,我们猜测其内存空间的布局:1)B的偏移表,在ecx处2)B的数据成员,在ecx + 4处3)C的偏移表,在ecx + 8处4)C的数据成员,在ecx + 0C处5)D的数据成员,在ecx + 10处6)A的数据成员,在ecx + 14处下面先对偏移表进行跟踪正是通过这些入栈操作,来进行条件跳转的。
这是最后的内存空间布局。
现总结如下:1)基类B的偏移表指针2)基类B新增的数据成员3)基类C的偏移表指针4)基类C新增的数据成员5)派生类D新增的数据成员6)虚基类的数据成员1.8 菱形继承,有虚函数,为虚继承的情况相较于1.7,这里增加了虚函数,那么又有什么不同呢?根据这些,大概猜测其内存空间布局如下:1)基类B的虚表指针2)基类B的偏移表指针3)基类B的数据成员4)基类C的虚表指针5)基类C的偏移表指针6)基类C的数据成员7)派生类D的虚表指针(后证实,不是这样的,而是分割)8)派生类D的数据成员9)虚基类A的虚表10)虚基类A的数据成员下面来一一查看。
设置偏移表。
偏移表的设置,在虚表设置之前。
这里的偏移表的第二项,用于确定本类(B)对虚基类(A)的定位。
而第一项,像是本类的虚表指针相对于偏移表的偏移。
这里有分割线的概念,用于分割非虚基类和虚基类。
此时,对于两个虚表,有点疑惑至此,完成了对内存空间布局的更新,现总结如下:1)基类B的虚表指针a)B新增的虚函数b)D新增的虚函数2)基类B的偏移表指针3)基类B新增的数据成员4)基类C的虚表指针a)C新增的虚函数5)基类C的偏移表指针6)基类C新增的数据成员7)派生类D新增的数据成员8)分割9)虚基类的虚表指针a)A未被覆盖的虚函数b)D覆盖的虚函数10)虚基类的数据成员所以,这里各虚表的特点是——仅存放新增的虚函数地址。
至于那些覆盖的,则放在虚基类的虚表中。
上面这些,1.5 菱形继承,无虚函数的情况1.6 菱形继承,有虚函数的情况1.7 菱形继承,无虚函数,为虚继承的情况1.8 菱形继承,有虚函数,为虚继承的情况是以菱形继承为基础,控制有无虚函数,是否为虚继承,所进行的测试。
2. 总结现在来试着从更全面的角度来看,试图总结它们的规律。
2.1 无虚函数,仅有数据成员的情况1.1 单继承,无虚函数的情况1.3 多重继承,无虚函数的情况1.5 菱形继承,无虚函数的情况1.7 菱形继承,无虚函数,为虚继承的情况1.1,1.3,1.5的布局都很相似——基类数据成员、派生类新增的数据成员按照这样的顺序进行排放。
而在1.7的情境中,因虚基类的存在,仅存一份拷贝,引入偏移表。
2.2 有虚函数的情况这是1.2 单继承,有虚函数的情况1.4 多重继承,有虚函数的情况1.6 菱形继承,有虚函数的情况1.8 菱形继承,有虚函数,为虚继承的情况虚函数的存在,引入了虚表。
可见它们也大致遵循着类似的规则:1)虚表指针,偏移表指针,数据成员2)原数据成员,新增数据成员3)原虚函数,新增的虚函数2.3 其他情况上面大致描述了一些基本框架情况,在此基础上还可以有其他的变形。
比如:1)多重继承中,A,B ——> C A没有虚函数,B有虚函数这对内存空间布局的影响B的虚表指针,B的数据成员,A的数据成员,C新增的数据成员2)单单两个类间的虚继承A ——>virtual Ba)无虚函数的情况B的偏移表指针B新增的数据成员虚基类A的数据成员b)有虚函数的情况这还要看是否发生覆盖,如果没有覆盖:B的偏移表指针B新增的数据成员虚基类A的虚表指针虚基类A的数据成员如果有了覆盖:B的偏移指针B新增的数据成员分割虚基类A的虚表指针虚基类A的数据成员其他不再详述。
2.4 覆盖、新增在虚继承中,有分割这么一说——用0x00000000来分割非虚基类和虚基类。
但是,分割是否出现,这还取决于是否新增了虚函数。
其实,对于虚函数,都存在覆盖和新增的视角处理。
这涉及到对虚表的更新处理。
2.5 本类的虚表可以这么说,在继承中是不存在本类的虚表这么一说的。
都是在其基类的虚表基础之上,或进行覆盖,或进行新增。
当然了,一般的,新增的虚函数地址,是存放在第一个基类虚表里的。
2.6 偏移表,虚表偏移表中一般两项,第二项用于本类对虚基类的定位,是偏移相关。
而第一项,好像是本类的虚表相对于本类偏移表的偏移。
当本类没有虚表时,第一项就是0。
当本类的虚表在偏移之上时,该值为负,刚好是它们间的差值。
(一般如此)为正的情况呢?。