继承
什么是继承:根据另一个类来定义另一个类,是代码复用的重要手段,继承是类设计层面的复用
继承作用:允许在保持原有类属性的基础上进行扩展,产生新的类,新产生的类叫做派生类
继承定义
class A
{
public:
func_1()
{}
protected:
int a;
char b;
};
class B : public A //A为父类(基类),B为子类(派生类)
{
public:
int c;
};
继承方式:public继承,protected继承,private继承
继承父类成员访问方式变化:
类成员/继承方式 | public继承 | protected | private继承 |
---|---|---|---|
父类的public成员 | 子类的public成员 | 子类的protected成员 | 子类的private成员 |
父类的protected成员 | 子类的protected成员 | 子类的protected成员 | 子类的private成员 |
父类的private成员 | 子类中不可见 | 子类中不可见 | 子类中不可见 |
总结:
- 1.父类中的private成员在子类中不可见,指父类的私有成员实际上已经被继承到了子类中,但是
语法上限制子类对象在类中和类外均不可访问 - 2.如果父类成员不想在类外被直接访问,但是需要在子类中访问,就定义为protected
- 3.使用关键字class时默认继承方式为private,使用struct默认继承方式为public
父类和子类间对象赋值转换
1.子类对象可以赋值给父类对象/指针/引用
2.父类对象不能赋值给子类对象
3.父类的指针可以通过强制转换赋值给子类指针
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
int _No; // 学号
};
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
// sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_No = 10;
}
继承中的作用域
1.父类和子类都有各自独立的作用域
2.子类和父类中存在同名成员,子类成员将屏蔽父类对同名成员的直接访问(隐藏(重定义))
在子类成员函数中可以使用 父类::父类成员 显式访问
3.成员函数的隐藏:只要函数名相同就构成隐藏
隐藏实现
class A
{
public:
int num_1 = 1;
void func()
{
cout<<"func()"<<endl;
}
};
class B : public A
{
public:
int num_1 = 10; //与父类A中的同名成员num_1构成隐藏
void func(int i)
{
cout<<"num_1 = "<<A::num_1<<endl; // 1
cout<<"num_1 = "<<num_1<<endl;// 10
A::func(); //在子类中访问可以使用 "父类::父类成员" 显式访问
cout<<"func(int i):"<<i<<endl;//与父类中func函数名相同构成隐藏
}
};
int main()
{
B t;
t.func(10);
}
子类的默认成员函数
- 1.子类的构造函数必须调用父类的构造函数初始化父类的成员,若父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用
- 2.子类的拷贝构造函数,operator=,都要调用父类的operator=完成父类的拷贝初始化和复制
- 3.子类的析构函数会在子类调用完成后,再调用父类的析构函数清理父类成员(因为要先清理子类成员再去清理父类成员)
- 4.子类对象初始化先调用父类的构造函数再调用子类构造函数
- 5.子类对象析构清理先调用子类析构再调用父类析构
继承与友元关系
父类友元不能访问子类的私有保护成员,友元关系不能继承
继承与静态成员
父类定义了static静态成员,则整个继承体系中只有一个这样的成员,无论有多少个子类,都只有一个static成员实例
菱形继承
菱形继承:一个子类有两个或两个以上的直接父类时称这个继承关系为多继承,而菱形继承为多继承中的一种特殊情况
菱形继承带来的问题:数据冗余,数据二义性
如何解决:使用虚拟继承
class A{};
class B : virtual public A{};
class C : virtual public A{};
class D : public B,public C{};
虚拟继承作用:为了解决从不同途径继承来的同名成员在内存中有不同的拷贝而造成的数据不一致问题,将共同的基类设置为虚基类,
虚拟继承是如何解决的?(原理)
通过虚基表指针,虚基表指针指向一张虚基表,这个虚基表中存放着偏移量,通过这个偏移量可以找到对应的类中对象。每个对象中保存着一份父类的对象,多继承时通过虚基类指针引用该公共对象,从而避免菱形继承中的数据二义性问题
继承与组合的关系
1.public继承是一种is-a的关系,即每一个子类对象都是一个父类对象
2.组合是has-a关系,假设B组合A,则每个B对象中都有一个B对象,
3.白箱复用:通过生成子类的复用,在继承中,父类的内部细节对子类来说可见,所以继承在一
定程度上破坏了父类的封装
4.黑箱复用:通过组合来获得新的功能,对象组合要求被组合的对象具有好定义的接口,所以这种复用方式,对象的内部细节不可见,有助于保持每个类的封装性组合耦合度低,代码维护性好