继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类 。而以前我们接触的复用都是函数复用,继承是类设计层次的复用。
class A {
public:
int a = 10;
protected:
int b = 20;
private:
int c = 30;
};
class B :private A {
void print()
{
cout << a << endl;
cout << b << endl;
//cout << c << endl;不可访问
}
};
class A
{
public:
int _a;
};
class B : private A
{
public:
int _b;
};
class C : public A
{
public:
int _c;
};
int main()
{
B b;
C c;
A a(b);//出错
A aa(c);
}
父、子类中成员处理方式
需要自己写默认成员函数的情况
对于默认构造函数,如果父类没有默认构造函数,就需要我们自己去写,显式调用构造
对于拷贝构造和赋值重载,如果子类存在深拷贝的问题,这个时候就需要我们自己实现拷贝构造和赋值重载来实现深拷贝
对于析构函数,根据具体实际的情况,如果子类有资源需要释放,那我们就需要自己手动实现析构。
以下是显示写默认成员函数的例子
构造:父类中没有默认构造函数,需要在子类中显示调用
拷贝构造函数:必须调用基类的拷贝构造完成基类的拷贝初始化
派生类的赋值重载必须要调用基类的赋值重载完成基类的复制
class Person
{
public:
Person(const char* name)
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
delete[] p;
}
protected:
string _name; // 姓名
int* p = new int[10];
};
派生类中
1、构造函数,父类成员初始化必须调用父类的构造函数
class Student : public Person
{
public:
Student(const char* name, int num)
:Person(name)//调用父类的构造函数
,_num(num)
, _address("南京")
{}
Student(const Student& s)
:Person(s)//发生切片 子类对象赋值给父类对象
, _num(s._num)
, _address(s._address)
{}
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);//切片
_num = s._num;
}
return *this;
}
//析构函数
//子析构函数和父析构函数构成隐藏关系(由于多态关系需求,所有析构函数都会处理成destructor函数名)
//调用析构函数的时候,先析构子类,在析构父类;子类不需要显示调用父类析构,子类析构后会自动调用父类析构
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
string _address;
};
我们并不需要显式调用父类的析构函数,因为出了子类析构函数的作用域,编译器会自动调用父类的析构。手动调用父类析构将会造成重复析构。
class A
{
public:
int _a;
};
class B : public A
//class B : virtual public A
{
public:
int _b;
};
class C : public A
//class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
从上图可以看出,D中会有两份A成员,调用时存在二义性和数据冗余
未使用虚拟继承
通过调用内存,会发现对象d中存在两份的_a,存在二义性和数据冗余
在上述代码中,class B : virtual public A;class C : virtual public A
当使用了虚拟继承,通过调用内存,发现对象d中仅有一份_a,但是继承于B类和C类的_b和_c上方多了一串地址,查找这个地址,发现这个地址之后的位置存放一个数字0x14(十进制20),这个数字就是b中_a距离公共_a的偏移量,如【0x0115FE5C+0x00000014=0x0115FE70】这样就解决了菱形继承成员冗余的问题
这里的A类叫做虚基类,在对象d中,将虚基类的成员放到一个公共的位置,继承的B、C类需要找到A的成员,通过虚基表【存储偏移量的表】中的偏移量进行计算。可以看到虚基表中第一行还空置了4个字节,这块空间存放的也是一个偏移量,它是指向虚基表的指针或者偏移量表格指针