1. 继承的概念
- 定义:继承是面向对象复用的重要手段,即面向对象的可重用性是通过“继承”来实现的。解决在已有的类中增加新的特性,减少重复的工作量的问题。
- 已经存在的类,叫“基类”或“父类”;建立的新的类,叫“派生类”或“子类”。继承是类型之间的关系模型,共享公有的东西,实现类内各自不同的东西。
- 继承的定义格式为:
class DeriveLcassName(派生类):acess-label(继承类型)BaseClassName,其中冒号后面的统称为派生类列表。 - 继承的分类:
继承分为单继承和多继承。
(1) 单继承是一个派生类只从一个基类派生,称为单继承。
示意如下:
图中,箭头表示继承的方向,从派生类继承于基类。
下面是简单的单继承代码:
class Person
{
public:
void Display()
{
cout<<_name<<endl;
}
protected:
string _name; //姓名
private:
int _age;
};
class Student: public Person
{
protected:
int _num; //学号
};
void Test()
{
Person p;
Student s;
//1.子类给父类
p = s;
//2.父类给子类
// s = p;
//3.父类通过指针/引用给子类
Person *p1 = &s;
//Person &p1 = s;
//4. 子类的指针/引用不能指向父类对象(可以通过强制转换完成)
Student *p2 = (Student *)&p;
//Student &s = (Student &)p;
//p2->_num = 10;//不可访问
//p2._num = 10;
}
(2)多继承:是一个派生类有两个或多个基类,称为多重继承。
简单示意如下:
简单的对多继承的理解,代码如下:
class Person
{
public:
friend void Display(Person &p,Student &s);
void buy() //虚函数
{
cout<<"买东西"<<endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
//virtual//
void display_1()
{
cout<<"买半价"<<endl;
cout<<Person::_name<<endl;
}
protected :
int _strNum;
protected:
//int _num;
};
void Display(Person &p,Student &s)
{
cout<<p._name<<endl;
cout<<s._strNum<<endl;
}
void Fun()
{
Person p;
Student s;
Display (p, s);
}
int main()
{
Person p;
Student s;
//cout<<p._name<<endl;
//Fun(s);
return 0;
}
2. 继承中的规则
- 继承关系中构造、析构函数调用顺序
(1)构造函数调用顺序
(2)析构函数调用顺序
- 继承与转换–赋值兼容规则
(1)子类对象可以全部赋值给父类;
(2)父类对象不能赋值给子类对象;
(3)父类的指针或引用可赋值给子类对象;
(4)子类对象的指针或引用不能赋值给父类对象,但可通过强制类型转换完成。
3. 菱形继承
菱形继承是由单继承和多继承组合的一种复杂的继承关系,存在二义性和数据冗余的问题。而菱形虚拟继承则是为了解决菱形继承所存在的问题。
菱形继承的简单示意图如下:
其中派生类D既继承于C1,又继承于C2,通过派生类调用基类中的成员或成员函数时会产生二义性的问题
模型如下图:
简单模拟代码如下:
//菱形继承
#include<iostream>
using namespace std;
class B
{
public:
int _b;
int _b1;
};
class c1:public B
{
public:
int _c1;
int _c;
};
class c2:public B
{
public:
int _c2;
};
class D:public c1,public c2
{
public:
int _d;
int _d1;
};
int main()
{
D d1;
d1._d = 1;
d1._d1 = 4;
// d1._b1; //这里不能通过编译,现象对D::_b1访问不明确,出现模糊调用的情况
d1.B::_b1 = 3; //这里能通过编译,但仍会显示对基类“B”访问不明确
return 0;
}
在对基类中的成员变量访问时,必须使用域访问限定符,否则编译器无法识别是哪个对象中的_b1赋值,虽然解决了二义性问题,但是产生了数据冗余.
为了解决菱形继承产生的二义性和数据冗余的问题,采用了菱形虚拟继承的方法,现对菱形虚拟继承概括:
4 菱形虚拟继承
- 定义:菱形虚拟继承是在继承类型前加入虚拟(virtual)关键字,且使用的是偏移量表格。
- 同名隐藏规则:
虚基类是只有一个拷贝;
作用分辨符(::)的访问方式:基类::成员(参数)/成员
同名隐藏规则:没有虚函数的情况下,派生类中新增的与基类中的函数同名的函数,即使参数个数不同,则基类中的同名函数被覆盖;如果派生类中的多个基类拥有同名成员函数,派生类也新增了同名函数,则基类都被隐藏(这种情况,对派生类的访问用对象名。成员名,对基类的用作用分辨符)。
- 菱形虚拟继承关系:
继承关系如下图所示:
- 菱形虚拟继承关系:
菱形虚拟继承模型如下:
如图所示:
Address1,Address2这里均存的是指针,指针指向偏移量表格。偏移量表格中先存该派生类相对于自己的地址,后存该派生类相对于基类的地址。
代码及分析如下:
//菱形虚拟继承
#include<iostream>
using namespace std;
class B
{
public:
int _b;
int _b1;
};
class C1:virtual public B
{
public:
int _cc;
int _c;
};
class C2:virtual public B
{
public:
int _c2;
};
class D:public C1,public C2
{
public:
int _d;
int _d1;
};
void test()
{
D d1;
d1.C1::_b = 2;
d1.C2::_b = 7;
d1._b1 = 3;
d1._d = 1;
d1._d1 = 4;
}
int main()
{
test();
return 0;
}
在调试过程中,先看看d1中存了些什么:
能看出d1存储了指针变量的地址,指针变量的内容是指向虚基类的地址,是4个字节,然后再看看这个地址中存放了什么。
1c转换成十进制是28,32位平台下一个地址是4个字节,因此,指向的是继承关系的模型中的_d1
好了,基本的先总结到这里~