三种继承关系
我们都知道成员访问限定符总共有三种, public, protected, private.那么对应到继承关系上的时候也就有对应的三种继承关系, public, private, protected.
C++中的继承是为了实现代码复用的技术思想.简单来说就是一个对象可以不用定义直接使用另一对象的属性和方法.原始类称为基类,继承类称为派生类,它们是类似于父亲和儿子的关系,所以也分别叫父类和子类。同时也要注意, 一个子类也可以有自己的基类, 因此一个子类也可以是了一个类的父类. 而子类又可以当成父类,被另外的类继承。继承的方式有三种分别为公有继承(public),保护继承(protect),私有继承(private)。
公有继承
父类的公有成员和保护成员作为子类的派生成员时, 此时将保持原来的状态, 而父类的私有成员仍然是私有的但是它不能被子类所访问.
保护继承
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的.private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。
私有继承
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问.
私有和保护的区别
公有继承时基类中各成员属性保持不变,基类中private成员被隐藏。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象只能访问基类中的public成员。
私有继承时基类中各成员属性均变为private,并且基类中private成员被隐藏。派生类的成员也只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员。
保护继承时基类中各成员属性均变为protected,并且基类中private成员被隐藏。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员。
//公有继承 对象访问 成员访问
public --> public Y Y
protected --> protected N Y
private --> private N N
//保护继承 对象访问 成员访问
public --> protected N Y
protected --> protected N Y
private --> protected N N
//私有继承 对象访问 成员访问
public --> private N Y
protected --> private N Y
private --> private N N
总结
基类的私有成员在派生类中都不能被访问, 同时如果基类的成员不想被基类对象访问, 但是又想再派生类中能够被访问, 此时就将其定义为保护
不管是哪种继承关系, 在派生类的内部都可以访问基类的公有成员和保护成员, 同时注意基类的所有成员是存在的, 只是在派生类中不可见而已
继承与转换
子类对象可以赋值给父类, 但是父类对象不能赋值给子类
因为子类中本身就包含了父类,所以将子类赋值给父类的时候,此时就将子类中包含父类的那一部分交给父类就可以了(切片赋值)
父类指针可以指向子类对象但是子类指针不能指向父类
因为子类中包含了父类, 当将父类指针指向子类对象的时候,此时子类中就包含了父类,当然可以指向, 但是当将子类指向父类的时候,此时子类中有的东西父类没有,当然不能指向
继承中的作用域
基类和派生类都拥有自己独立的作用域
当子类和父类的成员名相同的时候,此时子类成员会屏蔽对父类的成员访问
同时注意父类和子类的析构函数会被编译器优化为同名函数
在构造对象的时候,先构造父类再构造子类,析构的时候先析构子类在析构父类,同时子类的析构不会被显示调用
菱形继承处理数据冗余问题
来看一段代码
#include <iostream>
#include <stdio.h>
#include <string>
using namespace std;
class A
{
public:
int _a;
};
class B:public A
{
public:
int _b;
};
class C:public A
{
public:
int _c;
};
class D:public B, public C
{
public:
int _d;
};
void Test()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
int main()
{
Test();
return 0;
}
此时单步调试, 打开内存
仔细分析会发现确实将_a保存了两份,此时就会出现数据的冗余以及数据二义性.而且还浪费了空间.为了解决数据的冗余以及数据二义性, 此时便引入了虚继承
修改原来的代码
#include <iostream>
#include <stdio.h>
#include <string>
using namespace std;
class A
{
public:
int _a;
};
class B:virtual public A
{
public:
int _b;
};
class C:virtual public A
{
public:
int _c;
};
class D:public B, public C
{
public:
int _d;
};
void Test()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
int main()
{
Test();
return 0;
}
此时打开内存,单步跟踪就会出现下图
通过分析, 就会发现,系统在存a的时候分别存了两个便宜地址,因此在找a的时候,此时就拿着这个偏移地址去找,从而就会找到对应的数据了,这样便解决了数据冗余,二义性以及空间浪费的问题了