继承机制在C++里面是程序设计使代码可以重复利用的一种重要的手段,它允许在原有类的基础上进行扩展、丰富程序的功能。这样产生的类被称为派生类(子类),这个被继承的类就是基类(父类)。
这里的继承类型和类里面的数据访问权限很类似:
虽然在理论上有三种继承方式,但是在实际应用中常用的是public继承。
class Base
{
public:
int _pub;
private:
int _pri;
protected:
int _pro;
};
class Derive:public Base
{
public:
int _pubD;
private:
int _priD;
protected:
int _proD;
};
在继承里面基类和派生类的作用域是不一样的,即在派生类里面是不可以直接访问基类里面的私有和保护的成员。如果要在派生类里面访问基类里面的数据可以使用基类名::成员变量名的形式来访问。
举个栗子:
int main()
{
Derive d;
d.Base::_pub = 1;
cout<<d.Base::_pub<<endl;
return 0;
}
这里面创建了一个Derive类型的对象d,在这个d里面有用Base::来访问Base里面的public成员,因为这里是public成员所以这里是可以改变其值。
运行机制:
在类的继承里面,同样含有类的六个默认的函数,例如下面的代码:
class Base
{
public:
Base()
{
cout<<"Base()"<<endl;
}
~Base()
{
cout<<"~Base()"<<endl;
}
int _pub;
private:
int _pri;
protected:
int _pro;
};
class Derive:public Base
{
public:
Derive()
{
cout<<"Derive()"<<endl;
}
~Derive()
{
cout<<"~Derive()"<<endl;
}
int _pubD;
private:
int _priD;
protected:
int _proD;
};
运行结果:
这里看起来是先执行的是基类的构造函数,然后在执行的是派生类的构造函数。但是事实上是在执行派生类的构造函数列表的时候直接进入到了基类的构造函数里面,并没有进入派生类的构造函数体里面,这里以为先执行的基类的构造函数是不正确的。
正确的函数调用顺序应该是:派生类构造函数列表----->基类构造函数------>派生类构造函数体
同样析构函数的调用顺序是:派生类的析构函数------->派生类包含的对象的析构函数------->基类析构函数
继承的对象在内存里面的存储形式:
在内存空间的后面存储的是派生类的特有成员,这里涉及多继承的问题,同样如果是多继承的话,在这里按照从前到后的顺序依次继承,存储顺序也是依次类推。
注:友元关系是不可以继承的
关于指针的问题:
1、派生类的指针不可以指向基类(但可以进行强制类型转换,后果不安全)
2、基类的指针可以指向派生类,但是只能访问基类数据的数据段。
当基类的数据成员用static修饰的时候可以用派生类名::静态成员或者是对象名.静态成员名的方式调用访问的地址都是一样的,这也说明当基类的成员用static修饰的时候在内存里面存储的只有一份,每次访问的数据都是由这个内存提供。形如:
void test()
{
Derive d;
cout<<Derive::_pub<<endl;
cout<<d._pub<<endl;
}
这段代码的结果是相同的。
菱形继承:
关于菱形继承的概念如图:
箭头的指向就是继承的方向。
代码:
class B
{
public:
int _pub;
private:
int _pri;
};
class c1:public B
{
public:
int _pubc1;
private:
int _pric1;
};
class c2:public B
{
public:
int _pubc2;
private:
int _pric2;
};
class D:public c1,public c2
{
public:
int _pubD;
private:
int _priD;
};
菱形继承在内存里面的存储形式:
同名隐藏:
如果在基类和派生类里面含有相同的成员变量和同名的成员函数,那么在派生类里面调用基类的派生类里面同名的函数和成员变量时候会优先调用派生类里面的变量和函数,(这个同名成员和函数跟变量的类型、函数的原型无关)。
从这个图里面可以看出来,在D里面有两份B类型的成员变量,这样就带来了一个问题,在D里对B里面的public成员进行访问的时候编译器就会不知道是对c1里面的成员进行访问还是对c2里面的成员进行访问,这就是二义性问题。
虚拟继承:
虚拟继承就是解决二义性问题的一种办法。虚拟继承所用的关键字是virtual,当我们定义虚拟继承的时候需要将关键字virtual放在单继承的前面。
举个栗子:
class B
{
public:
int b;
};
class c1:virtual public B
{
public:
int _c;
};
class c2:virtual public B
{
public:
int c_;
};
当我们在求这个虚拟继承类大小的时候就会发现比普通的继承多出4个字节,这4个字节是用来存放偏移量标地址的,
偏移量表:
在虚拟继承里面每一个基类里面的成员在派生类里面都是唯一存在的,其原因就是有偏移量表的存在,在偏移量表里面存放着相对于自己的偏移量和相对于基类的偏移量,有这两个偏移量的 存在就可以根据这个偏移量来找带这个成员的位置。
当每次访问基类里面的成员的时候先会去访问偏移量表里面的偏移量,然后再去访问成员变量,这样就保证在每次访问基类里面的成员的时候不会产生二义性的问题。