继承
成员访问限定符有:
- public(公有)
- private(私有)
- protected(保护)
对应的继承有三种关系:
- public(公有继承)
- private(私有继承)
- protected(保护继承)
继承是一种复用手段。
三种继承关系下基类成员的在派生类的访问关系变化
总结:
1. 基类的私有成员在派生类中是不能被访问的,如果一些基类成员不想被基类对象直接访问,但需要在派生类中能访问,就定义为保护成员。可以看出保护成员限定符是因继承才出现的。
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3. protetced/private继承是一个实现继承,基类的部分成员并未完全成为子类接口的一部分,是has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
4. 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在子类中不可见(不能
访问)。
5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6. 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
继承与转换
1. 子类对象可以赋值给父类对象(切割/切片)
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
class Person
{
public:
void Display()
{
cout<<_name<<endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
int _num;
};
void test(){
Person p;
Student stu;
//父类的指针可以指向子类
Person* p1 = &stu;
//父类的引用可以指向子类
Person& r1 = stu;
//子类的指针/引用不能指向父类,但是强转可以
// Student* p2 = &p;
// Student& r2 = p;
Student* p2 = (Student*)&p;
Student& r2 = (Student&)p;
//子类可以赋值给父类
p = stu;
//父类不可以赋值给子类
// stu = p;
cout<<stu._num<<endl;
p2->_num = 10;
cout<<stu._num<<endl;
r2._num = 20;
cout<<stu._num<<endl;
}
我们发现指针指向父类的指针不再可以给子类的成员变量赋值。
在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认的成员函数。
单继承:一个子类只有一个直接父类时,这个继承关系叫做单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承
Assistant中有两份person,菱形继承存在二义性和数据冗余的问题。
class Person
{
public:
string _name;
};
class Student : public Person
{
protected:
int _num;
};
class Teacher : public Person
{
protected:
int _id;
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse;
};
int main()
{
Assistant a;
//需要显示的指定访问父类的成员
a.Student::_name = "stu1";
a.Teacher::_name = "stu2";
cout<<a.Student::_name<<endl;
cout<<a.Teacher::_name<<endl;
return 0;
}
那么怎们解决菱形集继承的二义性和数据冗余呢?
虚继承是一种解决方案:
1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
2. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体
系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。
这里要弄清楚什么是虚基表