帝国cms手机网站制作,做网站花了三万块,用sql网站建设基本流程,如何创建网站制作平台一 继承的由来 我使用类也有一段时间了#xff0c;慢慢觉得我们做一件事时#xff0c;就是要先描述#xff0c;例如写一个管理系统登记学校成员的信息#xff0c;我们就要先对在学校内的老师和学生做描述#xff0c;学生要有年龄#xff0c;班级#xff0c;姓名#xf…一 继承的由来 我使用类也有一段时间了慢慢觉得我们做一件事时就是要先描述例如写一个管理系统登记学校成员的信息我们就要先对在学校内的老师和学生做描述学生要有年龄班级姓名学号和身份证描述老师要有年龄姓名工号和身份证分别用一个类来将这些信息封装起来描述完后实例化出一个对象可以存一个学生的信息存多个学生那就new一块大的空间来存然后就可以写成员函数来对一个个学生信息做增删查改我们可以发现在描述学生和老师的时候他们有一些信息是重合的那在老师类和学生类中都要写对应的接口函数对这些相同的信息做处理这就有些冗余所以我们希望能把这些公共信息分离出来再封装入类命名为person类然后我们学生类想要添加person类的成员函数和成员变量就继承person类这就是继承的由来当时我有点疑惑为什么不直接在student类中定义一个person类的成员变量后来才知道这叫做组合。
组合和继承的区别我理解比较深刻的就是继承中子类和父类的关联度更高因为父类(person类)的公有和保护成员子类在类内都可以使用但是组合就只能使用persong类的公有成员也就只会被公用成员的修改影响只能说最好就是用组合如果要实现多态就必须用继承(或许实际工作中我们可以理解地更深刻)。
二 继承的使用
class A
{
public:int _a;
};
class B
{
public:void Print(){cout B:Print() endl;}int _b;A a1;
};
class C: public B public A C要继承B就在类名后加冒号(:),然后写继承方式,public表示公有继承再加类名B要继承多个类就用逗号隔开C称为子类A,B称为父类
{
public:int _c;
};
void test1()
{C c1;c1.Print();//继承
} 继承一个父类就对应写一个继承方式如果不写会有有默认的继承方式如果子类是class定义的那继承方式默认为private如果为struct定义的子类继承方式默认为public, 等会会统一总结继承方式带来的区别。(如下图) 三种继承方式遇到基类(或称父类)的三种成员使得基类成员的访问方式有九种变化其实理解记忆也很好记。 基类的私有成员任意继承方式在派生类(或称子类)都是不可见不可见就是在子类类内都无法访问但在内存中还是存在也就是用地址可以强制访问。 而基类的protected和public成员与不同继承方式也有个规律就是取权限小的例如protected成员被public继承那它就是子类的保护成员被私有继承那就是子类的私有成员。 是不是都记住了呢?理解记忆即可根本就不用死记硬背。
二 子类对象和父类对象的赋值(向上转换) 子类和父类对象是不同的类型是如何支持赋值的呢?答案是切片 子类对象是有继承父类对象成员的当要赋值给父类对象时就把属于父类的切出来赋值给父类对象。 而对于父类的指针p1来说它仅仅指向子类中的父类对象 父类的引用也只是子类中属于父类的那一部分的别名。 那好如果反过来呢父类对象能赋值给子类对象(又称向下转换)吗?父类的指针和引用可以(会存在越界访问)对象不行可能是大佬觉得父类对象赋值给子类对象更危险因为剩下的子类成员不知道该放什么数据。
注意:切片就意味着要访问子类中的父类成员如果是私有和保护继承那在赋值的时候会报错。
三 子类和父类成员函数的作用域
当子类和父类的成员函数重名时此时子类的成员函数就会对成员函数进行隐藏。也就是当子类对象调用一个同名成员时会优先调用子类的除非显示调用。
class C
{
public:void Print(){cout C:Print() endl;}int _c3;
};
class E : public C
{
public:void Print(){cout E:Print() endl;}int _c4;
};
void test2()
{E e1;coute1._cendl;cout e1.C::_c endl; 显示调用同名的父类成员e1.Print();e1.C::Print(); 显示调用父类的同名成员函数
}
运行结果: 四 继承时默认成员函数 这里用的都还是原来类的成员函数的知识如果那部分没掌握对于继承时成员函数更不容易理解。 由于子类其实是把继承来的父类成员当成一个整体当子类对象调用自己的构造函数的时候初始化列表会先调用父类的构造函数(不写就调用默认构造显示就自己决定)初始化子类中的父类成员再初始化子类的。拷贝构造函数也是如此所以拷贝构造函数我们要自己在初始化列表显示调用父类的拷贝构造函数不然就会调成默认构造函数了。父类被继承认为声明在子类成员之前而我们之前在类和对象中学过了先声明的会先在初始化列表初始化。 子类赋值成员函数必须要先调用父类的赋值成员函数对子类中的父类成员做处理(这个顺序性是为了保证父类提供给子类的一定是初始化好或者赋值好的)而且是显示调用因为赋值成员构成隐藏如果不显示调用父类的那就会一直调用子类的导致栈溢出。 而析构函数是被编译器做了处理的名字统一为destructor(原因在多态)所以也构成隐藏但却不需要我们显示调用父类的因为编译器需要先用子类的析构函数清理子类资源再自己调用父类的析构函数。 析构顺序原因有二: 1.遵循后定义的先析构 2.子类中可以使用父类成员如果先调用父类析构函数我们又在该函数后访问父类成员会出问题所以要防止这种情况出现。 五 继承时的友元函数静态成员 父类的友元函数继承时是不会变成子类的友元函数的就像是父辈的朋友不一定是你朋友一样除非在子类内对该函数进行友元声明。
至于静态成员则更加特殊在没有提及继承之前静态成员是属于整个类的就一份那继承给子类后会多一份吗?上代码!
class C
{
public:void Print(){cout C:Print() endl;}static int _c;
};
int C::_c 3;
class E : public C
{
public:void Print(){cout E:Print() endl;}int _e4;
};
void test2()
{E e1;C c1;cout C: c1._c endl;cout E: e1._c endl;e1._c 4;cout C: c1._c endl;cout E: e1._c endl;c1._c5;cout C: c1._c endl;cout E: e1._c endl;
} 当我们用子类和父类对象去打印这个静态成员的时候他们值是相同的但这不能说明他们是一个变量有可能是复制了父类的值所以我们用子类和父类对象去改这个变量结果如下图结论是子类和父类共用这个静态成员。但要强调的一点是e1,c1对象内都不包含静态成员变量因为我用sizeof发现子类E继承父类C后大小不变。 六 多继承
多继承这个部分的知识点不少就先来说说多继承的弊端。
1.多继承弊端 上图是多继承中的菱形继承代码如下。
class A
{
public:int _a;
};
class B:public A
{
public:int _b;
};
class C:public A
{
public:int _c;
};
class E:public C,public B
{
public:int _e;
};
从内存窗口看我们可以发现e1中有两份A类型一份是在继承C得来的一份是继承B得到的。 特别要说明的是内存排列顺序E是从左往右继承所以先继承的C再继承B而C显示在上可以认为先继承的显示在上显示在上意味着什么呢?这就要再说结构体成员的内存分布了e1时的地址是整个结构体的最低地址这应该是规则所以显示在上意味着C位于结构体内存块的低地址处。这说明结构体内部是先使用低地址再使用高地址的但栈帧是先用高地址。
既然e1中有两份A那是不是有点冗余呢?当然啦之前说子类继承父类是为了更好的描述如果继承的成员有重复那对自己的描述不就出现重复了吗就像有两个身份证号用哪个呢?二义性不就来了吗?
2 解决办法:虚拟继承 继承真正的难点才刚刚开始请做好心理准备。
先来看看虚拟继承的使用
class A
{
public:int _a1;
};
class B:virtual public A
{
public:int _b2;
}; B虚拟继承A类内存图 如果B是正常继承A内存窗口如下。 想必大家发现了监视窗口是没有发生变化的这是因为监视窗口是编译器想让你看到的并不是实际的内存窗口才是真实的怪不得有句话说大佬的境界是这样的:看代码不是代码而是内存。 总结虚拟继承对子类内存空间的改变 1.虚拟继承的成员放在存储空间的地址最低处公共的东西当然放最开始或者最后面了。 2.增加了一个指针称为虚基表指针它指向虚基表。 这是对应的虚基表。 虚基表内容后面详谈。
然后我们再来看看一开始提的多继承弊端中的菱形继承如何通过虚拟继承改进。
class A
{
public:int _a;
};
class B: virtual public A
{
public:int _b;
};
class C: virtual public A
{
public:int _c;
};
class E:public C,public B
{
public:int _e;
};
如下图当CB类均被E继承时他们的公共部分A会只保留一份如果仅仅只是B虚拟继承A类对于B类对象还没什么变化但是当两份虚拟继承而来的成员再被E继承时此时在E类对象只会保留一份这就是虚拟继承的作用。 大部分的博客都说在继承腰部的位置加个virtual关键字可是腰部在哪什么是腰部?下面这个图还好说其它情况呢? 如下图解决A类的数据冗余和二义性应该在哪加virtual关键字? 要回答这个问题我们就要回顾刚刚说的虚拟继承的作用了我理解就是虚拟继承是将继承而来父类成员都放到一块地方当出现同样的成员时取其中一份即可这两份的数据一定是相同的因为我们现在是在继承类的声明不是继承某个类对象所以要想解决A类的冗余在BC继承A时加virtual关键字即可如果是在继承DC时加那就把BC类的所有成员(包括BC自己的成员和继承而来的A类的成员)变成公共的了可我们只需要将A类变为公共的即可个人认为不要做多余的事。
3 虚基表组成:
第一行内容有的说是虚表指针指向自己这个指针的距离所以为零但我尝试中却发现该值并不都是0最后我认为是虚表指针距离子类最低地址(e1)的距离比如上面那个虚基表当B虚拟继承A就会在B类型对象内增加一个虚表指针此时虚基表第一行就是距离B最低地址的距离所以为零当我设计其它场景时代码如下:
class B
{
public:int _b2;
};class C
{
public:int _c3;
};class E :virtual public C, public B
{
public:int _e4;
}; 从下面的内存窗口可以看出虚拟继承的C类成员虽然先继承但虚拟继承的成员统一放在最后面至于讨论虚基表指针和B类成员谁先先后是没什么意义的在这里我觉得只需知道一般继承而来的父类成员放子类对象中的低地址处然后放子类成员最后放虚拟继承而来的成员就可以啦。我们可以看到的是虚基表中的第一行变成了-4(内存显示的数据是补码), 那虚基表第一行是虚基表指针指向自己的距离就是不成立的可以我们发现-4不就是虚基表指针距离子类E最低地址的距离吗。(我在此只写了两个例子证明实际上我自己写了不少更复杂且无用的继承场景来证明如果你觉得我是错的可以私聊非常欢迎) 第一行以外的都是虚基表指针距离父类成员的偏移量。
我写这么多探究虚基表第一行内容是什么是觉得其他人的博客说虚基表第一行是虚基表指针距离自己的距离不太对才打算写出来反驳一下他们。
最后我再谈谈对一些问题的看法例如为什么要用一个表去存偏移量而不是直接存偏移量到对象呢?首先偏移量是指向虚继承中的父类成员的如果子类虚继承了多个父类那偏移量是不止一个的如果把偏移量都塞进每个子类对象中一百个子类对象就有一百份偏移量而如果放在虚基表中而虚表指针大小是固定的并且让所有子类对象都可以共用这张表对我说的是共用每个对象的大小是一样的结构是一样的当然可以共用这张表格空间上来说当偏移量超过两个就可以提现出这种设计是节省空间。
下图是两个子类对象的内存窗口图他们的虚基表地址都是一样的。 至于为什么存偏移量这我也不知道啊大佬就是这么设定的可能以后随着学习的深入就理解了。