目录
一、继承格式
class son public: father
二、子类对父类的权限
1、private
子类不可直接访问
但是子类可以间接使用
例如调父类的get函数
2、protect
在类外面不能访问
但是子类可以访问
3、public
子类和父类的权限比较,那个小就取那个
例如子类为public方式继承,但是父类的A函数为protect
那么,最终子类对A函数的权限就是protect
总结:
父类的private子类都不能用,其余public和protect要和子类的继承方式比较,那个小取那个
三、 struct和class继承区别
struct默认继承方式和访问限定符都是共有
class默认继承方式和访问限定符都是私有
四、赋值兼容
类型相近的类型可以互相转换
例如,int和double可以互相转换
同理,依照上述例子,子类对象也可以赋值给父类对象
子类在public继承的情况下,子类可以转化为父类类型
可以理解为每个子类对象都是一个特殊的父类对象
怎么给?
共有的部分赋值给父类
但是子类独有的部分不会给到父类对象
这个过程叫做切割/切片
期间不产生临时变量
但是,父不能给子
很好理解
因为有些东西子类有,但是父类没有,无法赋值
五、继承中的作用域
1、子父类的作用域是独立的
因此不构成函数重载
2、子父有相同成员:隐藏
访问的是子类的,就近原则
子类会把父类的同名成员屏蔽,这种情况叫做隐藏
3、怎么访问父类?
指定作用域访问
子类对象指定访问父类域内的成员
son.father::father_func();
如果子类直接访问父类的成员,而没有指定类域,就会导致编译错误
因为子类域内找不到,但是你又没有告诉我要到那个域去找,所以我不知道,所以报错
六、继承类的默认成员函数
1、构造
子类的默认构造怎么处理父类?
子类分成了两个部分:
一个是父类,将父类看成一个类
一个是子类本身,内置类型不处理,自定义类型调用对应的构造
也就是说,如果子类在初始化的时候
会调用父类的默认构造
剩下的部分要调用子类自己的默认构造
但是,如果父类没有自己的默认构造,子类也会报错
相当于,子类的一个自定义类型没有其对应的默认构造
派生类一定会去调用父类的构造,因为要继承
这一块很简单,父类就是子类的一个自定义对象
2、拷贝构造
和构造相同
将父类视为一个简单的自定义类型
仅此而已
因此不需要自己写
但是如果要深拷贝,就要自己写
3、赋值重载
例如是operator=运算符重载
子类和父类都有一个,要注意构成隐藏
如果要调用父类的operator=需要指定域访问
4、析构函数
如果是自定义的析构,也会构成隐藏
但是,子类析构结束后,会自动调用父类析构函数
但是还是可以显式指定域访问父类析构
虽然析构函数看起来名字不相同,属于各自的类
5、静态成员的继承
父类的静态成员只有一份
所有的子类都共用这一个静态成员
总结
原则:
构造保证先父后子
析构保证先子后父
常见复用的体现:
1)函数逻辑的复用
2)模板的复用
3)继承的复用
七、单继承和多继承
单继承:只有一个直接父类
多继承:两个及以上的直接父类
1、菱形继承问题
什么叫做菱形问题?
这个问题是从继承区分单继承和多继承这个特性上衍生出来的
例如下面这个图:
那么,在这种情况下,这个D就会继承有Base的成员
这就是菱形问题:
a、数据冗余
b、数据二义性,访问错误,因为不知道到底访问的是谁的
2、如何解决菱形继承—虚继承
用关键字virtual解决(其实已经涉及到多态了)
格式:
virtual void func() {}
看一个具体例子:
#include<iostream>
using namespace std;
//grand
class grand
{
public:
virtual void func()//父类必须加vitual,加了vitual关键字,就叫做虚函数
{
cout << "grand->func" << endl;
}
private:
int _grand;
};
//父类(基类)
class father1 : public grand
{
public:
virtual void func()//父类必须加vitual,加了vitual关键字,就叫做虚函数
{
cout << "father1->func" << endl;
}
private:
int _father1;
};
//父类(基类)
class father2 : public grand
{
public:
virtual void func()//父类必须加vitual,加了vitual关键字,就叫做虚函数
{
cout << "father2->func" << endl;
}
private:
int _father2;
};
/子、父类虚函数,函数名相等,返回值相同,参数相同,构成重写
//子类(派生类)
class son : public father1 ,public father2
{
public:
virtual void func() //子类可以不加vitual,但是建议加上
{
cout << "son->func" << endl;
}
private:
int _son;
};
int main()
{
grand g;
father1 f1;
father2 f2;
son s;
g.func();
f1.func();
f2.func();
s.func();
}
这个可以重点理解一下
底层如何解决?可以看另一篇博主的文章
3、总结
在实践中,可以设计多继承
但是不要设计出菱形继承的结构
(iostream的io流就是使用菱形继承)
菱形继承是在2.0版本出现的
到了3.0才出现的
既然会出现歧义问题
为什么不删掉呢?
因为要兼容以前的历史版本,即向前兼容
所以,只有解决,不能删除
所以,尽管付出了解决的代价,即大大的增加的了设计的复杂度
但是,这是必须要付出的代价
这也是历史发展的包袱,历史发展的惯性,历史发展的代价
八、组合与继承(优先使用组合)
1、低耦合和高内聚
低耦合就是模块之间、类之间的联系低,互相影响程度低
例如说,人类和石头类,联系程度低,二者的组合就是低耦合
但是也是分使用场景
高内聚就是一个类内部的各种数据要尽可能的关联度紧密
例如,一个人对象的类,对于发量、交往对象的信息数据来说,大多数场景下,不是那么必要
所以,不是高内聚
而姓名、性别、年龄等信息就和人这个类关系紧密
继承耦合度高、组合耦合度低
耦合度低维护性更好
这个很好理解,因为组合相对于继承来说,类之间的紧密程度更低,关联影响更低
所以耦合度就低,耦合度低,容错率就高,更加稳定