做一个配送网站,福田网站设计公司哪家好,长春公司网站建设,市北网站建设一、继承对象的内存空间
构造函数调用顺序#xff0c;先调用父类#xff0c;再调用子类
#includeiostreamusing namespace std;//基类 父类
class Base{
public: //公有权限 类的外部 类的内部 Base(){coutBase()endl;}Base(int …一、继承对象的内存空间
构造函数调用顺序先调用父类再调用子类
#includeiostreamusing namespace std;//基类 父类
class Base{
public: //公有权限 类的外部 类的内部 Base(){coutBase()endl;}Base(int a):baseA(a){coutBase(int a)endl;}~Base(){cout~Base()endl;}void setData(int a){baseA a;}void show(){coutbaseA:baseA\t addr:baseAendl;}
protected: //保护权限 类的内部 子类private: //私有权限 类的内部int baseA;
};//子类 派生类
class Child: public Base
{
public: //公有权限 类的外部 类的内部 //在参数列表中指定 父类的构造函数Child():Base(100){coutChild()endl;}Child(int a,int b):Base(a),childA(b){coutChild(int a)endl;}~Child(){cout~Child()endl;}void show(){Base::show();coutchildA:childA\t addr:childAendl;}
protected: //保护权限 类的内部private: //私有权限 类的内部int childA; };int main()
{Child mya(100,200);coutmya size:sizeof(mya)\t addr:myaendl;mya.show();
}打印结果
gecubuntu:/mnt/hgfs/GZ22229/14C/05/1-code$ ./a.out
Base(int a)
Child(int a)
mya size:8 addr:0x7ffed6dc6630
baseA:100 addr:0x7ffed6dc6630
childA:200 addr:0x7ffed6dc6634
~Child()
~Base()注意 1、子类调用成员函数的时候会检测该成员函数在子类中是否存在如果存在就调用自己的 如果不存在就调用 父类的前提是父类要有这个函数 2、如果子类和父类存在同名函数那么在子类中 父类的函数成员会被隐藏默认调用的就是子类函数成员。如果要调用父类的函数成员必须添加类名和作用域。
mya.Base::showData();练习1 设计一个基类动物类属性体重颜色年龄 行为跑吃睡构造方法初始化属性 设计一个猫类继承动物类属性体重颜色年龄品种 行为跑吃睡抓老鼠叫 定义一个猫对象–咖菲猫调用猫的喊叫行为抓老鼠行为 输出猫的属性
练习2编写一个输出学生和教师数据的程序学生数据有编号、姓名、年龄、学号和成绩教师数据有编号、姓名、年龄、职称和部门。要求声明一个 person 类并作为学生数据操作类 student 和教师数据操作类 teacher 的基类
#include iostream
#include cstring
//将命名空间打开
/*
设计一个基类 动物类属性体重、颜色、年龄 、性别 行为吃喝拉撒
设计一个猫类 继承动物类属性体重、颜色、年龄 、性别、 行为吃喝拉撒、抓老鼠
定义一个猫对象 ---断尾猫调用猫的行为*/
using namespace std;
//动物类
class zoon{
public: //公有权限zoon(int weight,int age, const char * color){this-weight weight;this-age age;strcpy(this-color,color);}void show(){cout weight: weight endl;cout age: age endl;cout color: color endl;}
protected://私有属性int weight; //体重int age; //年龄char color[256]; //颜色
};
//猫类
class cat:public zoon
{
public: //公有权限cat(int weight,int age,const char * color,const char *type):zoon(weight,age,color) {strcpy(this-type,type);}void show(){cout weight: weight endl;cout age: age endl;cout color: color endl;cout type: type endl;}void behavior(){cout behavior endl; }
private://私有char type[256];// cat Broken_tail(10,6,white,BrokenTail);
};
int main()
{cat mya(10,6,white,BrokenTail);mya.show();mya.behavior(); //行为return 0;
}二、多继承
1、格式
class 子类:继承方式 基类1,继承方式 基类2
{变量成员函数成员
};2、例子
#include iostreamusing namespace std;//羊类
class Sheep{public:Sheep(){coutSheep()endl;}Sheep(int a):dataA(a){coutSheep(int a)endl;}~Sheep(){cout~Sheep()endl;}void show(){coutdataA value:dataA\t addr:dataAendl;}int dataA;
};//驼类
class Tuo{public:Tuo(){coutTuo()endl;}Tuo(int a):dataB(a){coutTuo(int a)endl;}~Tuo(){cout~Tuo()endl;}void show(){coutdataB value:dataB\t addr:dataBendl;}int dataB;
};//羊驼类
//多继承 的时候 基类的构造函数执行的顺序 跟 继承的先后顺序有关
class SheepTuo:public Sheep,public Tuo
{
public://在构造函数的参数列表中 指定 基类的构造函数SheepTuo(int a,int b):Tuo(b),Sheep(a){coutSheepTuo()endl;}~SheepTuo(){cout~SheepTuo()endl;}//在子类中重写 基类的同名函数那么基类中的同名函数就会自动隐藏void show(){coutdataA value:dataA\t addr:dataAendl;coutdataB value:dataB\t addr:dataBendl;coutdataC value:dataC\t addr:dataCendl;}int dataC;
};int main()
{//派生类对象的实例化SheepTuo mya(10,20);coutmya size:sizeof(mya)\tmya addr:myaendl;mya.show();//通过基类的类名显示调用//mya.Sheep::show();return 0;
}结果
mylinuxubuntu:/mnt/hgfs/13C编程/04/1-code$ ./a.out
A()
B()
C()
dataA addr:0x7fffd8f7f420 dataB addr:0x7fffd8f7f424 dataC addr:0x7fffd8f7f428
~C()
~B()
~A()总结
多继承的时候创建子类对象构造函数执行的顺序 羊—》驼----》羊驼 ,跟基类继承的先后顺序有关在派生类的构造函数初始化列表中指定基类的构造函数 3、内存结构图 思考如果多个基类中有同名的函数那么创建子类的对象然后进行调用会发生什么情况怎么解决
情况编译错误语法不通过 解决 1. 在子类中重新实现函数父类函数就会自动隐藏 2. 通过父类类名显示调用
三、多继承中的菱状继承环状继承
1、概念 多继承中的多级继承的时候父类的父类出现公共的基类 2、例子
A---D C--D B--A、C这个继承会使D创建两个对象
#include iostreamusing namespace std;//动物类
class Animal{
public:Animal(){coutAnimal()endl;}Animal(int a):dataD(a){coutAnimal(int a)endl;}~Animal(){cout~Animal()endl;}int dataD;
};//羊类 --继承于 动物类
class Sheep:public Animal{public:Sheep(){coutSheep()endl;}Sheep(int a):dataA(a){coutSheep(int a)endl;}~Sheep(){cout~Sheep()endl;}void show(){coutdataA value:dataA\t addr:dataAendl;}int dataA;
};//驼类
class Tuo:public Animal{public:Tuo(){coutTuo()endl;}Tuo(int a):dataB(a){coutTuo(int a)endl;}~Tuo(){cout~Tuo()endl;}void show(){coutdataB value:dataB\t addr:dataBendl;}int dataB;
};//羊驼类
//多继承 的时候 基类的构造函数执行的顺序 跟 继承的先后顺序有关
class SheepTuo:public Sheep,public Tuo
{
public://在构造函数的参数列表中 指定 基类的构造函数SheepTuo(int a,int b):Tuo(b),Sheep(a){coutSheepTuo()endl;}~SheepTuo(){cout~SheepTuo()endl;}//在子类中重写 基类的同名函数那么基类中的同名函数就会自动隐藏void show(){coutdataA value:dataA\t addr:dataAendl;coutdataB value:dataB\t addr:dataBendl;coutdataC value:dataC\t addr:dataCendl;// coutdataD value:dataC\t addr:dataDendl;}int dataC;
};int main()
{//派生类对象的实例化SheepTuo mya(10,20);coutmya size:sizeof(mya)\tmya addr:myaendl;mya.show();//发生二义性问题//mya.Animal::dataD 100;//通过基类的类名显示调用//mya.Sheep::show();return 0;
}分析 使用qtcreator的断点调试可看到 内存图如下 通过分析可得 创建B myb 对象的时候构造函数顺序为 D----A-----D—C----B
思考如果myb访问D类中的成员会发生什么情况
答案 会发生歧义编译错误 如何解决使用虚拟继承
四、虚拟继承虚基类
1、概念 为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题 将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个 拷贝同一个函数名也只有一个映射。这样不仅就解决了二义性问题也节省了内存避免了数据不一致的问题 2、格式
class 子类名称:virtual 父类名
{
};3、例子
#include iostreamusing namespace std;//动物类
class Animal{
public:Animal(){coutAnimal()endl;}Animal(int a):dataD(a){coutAnimal(int a)endl;}~Animal(){cout~Animal()endl;}int dataD;
};//羊类 --继承于 动物类 --虚基类 Animal
class Sheep:virtual public Animal{public:Sheep(){coutSheep()endl;}Sheep(int a):dataA(a){coutSheep(int a)endl;}~Sheep(){cout~Sheep()endl;}void show(){coutdataA value:dataA\t addr:dataAendl;}int dataA;
};//驼类
class Tuo:virtual public Animal{public:Tuo(){coutTuo()endl;}Tuo(int a):dataB(a){coutTuo(int a)endl;}~Tuo(){cout~Tuo()endl;}void show(){coutdataB value:dataB\t addr:dataBendl;}int dataB;
};//羊驼类
//多继承 的时候 基类的构造函数执行的顺序 跟 继承的先后顺序有关
class SheepTuo:public Sheep,public Tuo
{
public://在构造函数的参数列表中 指定 基类的构造函数SheepTuo(int a,int b):Tuo(b),Sheep(a){coutSheepTuo()endl;}~SheepTuo(){cout~SheepTuo()endl;}//在子类中重写 基类的同名函数那么基类中的同名函数就会自动隐藏void show(){coutdataA value:dataA\t addr:dataAendl;coutdataB value:dataB\t addr:dataBendl;coutdataC value:dataC\t addr:dataCendl;coutdataD value:dataC\t addr:dataDendl;}int dataC;
};int main()
{//派生类对象的实例化SheepTuo mya(10,20);coutmya size:sizeof(mya)\tmya addr:myaendl;mya.show();//发生二义性问题//mya.Animal::dataD 100;//通过基类的类名显示调用//mya.Sheep::show();return 0;
}现象
A:virtual public D
C:virtual public D分析 总结
在多继承情况下虚基类关键字的作用范围和继承方式关键字相同只对紧跟其后的基类起作用。虚拟继承在创建对象的时候在子类对象中会有一个虚基类表指针该虚基类表指针存储 虚基类 对象空间的最开始位置。类对象空间的大小 由 非静态成员数据虚基类指针字节对齐 组成。
五、虚函数、虚函数表
1、格式
virtual 函数返回类型 函数名参数表 {函数体};当一个类中有一个或多个虚拟函数那么这个类在编译的时候就会创建一个虚函数表简称虚表 。虚表就是一个存放虚函数指针的表格而这个表格的首地址存放在对象的地址开始位置。 2、例子
#include iostreamusing namespace std;class Data{
public:Data(){coutData()endl;}~Data(){cout~Data()endl;}private://在普通的函数前面加一个 关键字virtual ---虚函数virtual void setA(int val){coutsetAendl;//dataA val;}virtual void setB(int val){coutsetBendl;//dataB val;}virtual void setC(int val){coutsetCendl;dataC val;}int dataA;int dataB;int dataC;private:};int main()
{Data mya;coutmya size:sizeof(mya)mya addr:myaendl;//如何在类的外部 去调用类中的私有函数成员//可以将 私有函数成员 声明为 虚函数 通过虚表指针 去调用//mya.setA(10);// mya.setB(20);// mya.setC(30);//unsigned long 在window平台 是 4个字节 unsigned long long 是 8个字节//unsigned long 在linux平台 是 8个字节//求出虚表的地址unsigned long long *vptr (unsigned long long *)(*(unsigned long long*)mya);coutvptr:vptrendl;coutvptr1:vptr1endl;return 0;
}通过断点调试 可以看到
3、注意 1、如果一个类中包含一个或者多个虚函数那么这个类的对象中会包含一个虚表指针vptr 2、虚表指针保存在对象内存空间的最前面 3、虚表中存储的是 类中 虚函数的地址 4、对象调用类中的虚函数会查询虚表指针再去执行函数
4、虚表与虚表指针虚基类表与虚基类表指针的区别 https://blog.csdn.net/qq_42719751/article/details/104656429
六、通过虚表指针调用虚函数
1、虚函数 在类的成员函数声明前面添加virtual这个函数就被声明为虚函数 那么这个类的对象中包含一个指向虚表的指针 2、虚表 存储虚函数地址 3、通过虚函数指针调用虚函数
#include iostreamusing namespace std;class Data{
public:Data(){}~Data(){}private:int a;//virtual关键字还可以用来修饰函数 称之为 虚函数virtual int test01(){cout__FUNCTION__endl;}virtual int test02(){cout__FUNCTION__endl;}int test03(){cout__FUNCTION__endl;}
};int main()
{Data d1;//d1.test01();//d1.test02();coutsize:sizeof(Data)endl;//通过得到虚表中的函数的地址去调用函数//如果该函数是私有权限下的函数能够突破类中权限的限制//1)先获取虚表的地址//*(unsigned long long*)d1; 地址常量 0x100ff1aunsigned long long*vptr (unsigned long long*)*(unsigned long long*)d1;coutvptr:vptrendl;//求test01函数的地址//*vptr 等价于vptr[0] --得到test01函数的地址--但是该地址是一个常量必须转换成函数类型的地址typedef int (*PFunc_t)(void);
// PFunc_t test01_p (PFunc_t)vptr[0];
// test01_p();((PFunc_t)vptr[0])();// PFunc_t test02_p (PFunc_t)vptr[1];
// test02_p();return 0;
}4、总结
同一个类的不同实例共用同一份虚函数表, 它们都通过一个所谓的虚函数表指针__vfptr指向该虚函数表.虚表存储在进程的数据段.rodata中虚函数存储在代码段中的.text段中当类中有虚函数的时候编译器在编译的时候就会为该类创建一张虚表而且只存在一份。定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表。通过虚函数表能够突破虚函数访问权限的控制。比如我们可以将虚函数定义为private类型这样在类外面就不能通过对象调用private类型的函数了。但是在类的虚函数表中不论其访问类型是public还是private的其所有虚函数都会存放其中。