一、什么是继承
1、当我们解决问题时,先查看现有的类能否解决一部分问题,如果有则继承该类,在此类的基础上进行拓展来解决问题,来缩短解决问题的时间(代码复用)
2、当遇到一个复杂问题时,可以先把复杂问题分解成若干个小问题,如何为每个问题设计一个类进行解决,最终通过继承语法把这些类汇总到一个类中,达到解决问题的目的,以此降低解决问题的难度,同时也可以让多个程序员同时解决该问题
二、继承的语法
1、继承表
class Test : 继承表[继承方式 父类]
{
成员变量;
public:
成员函数;
}
2、继承方式
public protected private
三、继承的特点
1、C++中的继承可以有多个父类
2、子类会继承父类的所有内容
3、子类对象可以向父类转换,但是父类对象不能向子类转换
4、子类会隐藏父类的同名成员,在子类直接访问的话是子类成员
可以用域限定符访问父类成员
5、子类和父类的同名函数无法构造重载,因为不在一个作用域下
6、在执行子类的构造函数前,先执行父类的构造函数
默认执行的是父类的无参构造,可以在子类的构造函数初始化列表中显式调用父类的有参构造
7、在子类的析构函数执行完成后会再调用父类的构造函数,会按照继承表继承顺序,逆序执行父类的析构函数
8、当子类执行拷贝构造时,默认也是调用父类的无参构造,要在初始化列表中显式调用父类的拷贝构造
Test(Test& t):A(t);
9、当子类执行赋值函数时,默认不会调用父类的赋值函数
如果需要调用父类的赋值函数,加域限定符和赋值函数名
作业:对象的创建和销毁过程
四、继承方式影响访问结果
访问控制属性决定了成员的访问范围
public 可以在任何位置访问
private 只能在类内访问
protected 只能在类和子类中访问
继承方式的影响
1、父类的成员能否在子类中访问,是设计父类时访问控制属性决定的
继承方式能决定父类成员被子类继承后在子类中变成了什么样的访问控制属性
2、只有public方式继承父类,父类的指针或引用才可以指向子类对象(该语法是多态的基础)
父类中的属性 | public继承 | protected继承 | private继承 |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | private | private | private |
五、多重继承和钻石继承
1、多重继承
当一个类继承多个父类时,会按照继承表的顺序在子类中排列父类,子类会标记每个父类的位置,当把子类指针转化为父类指针时,编译器会自动计算父类所在的位置,指针会自动偏移
2、钻石继承
当子类和父类有共同的祖先,这种继承被称为钻石继承
1、两个父类中都有祖先的内容
2、子类会继承两个父类中的所有内容,也就是说会继承两份祖先中的内容
3、子类访问祖先中的成员时,此时就会冲突,因为有多份
3、虚继承
当使用virtual修饰继承时子类中会多出一个虚指针用于指向父类中的内容
当这个子类被继承时,孙子类会通过父类中的虚指针比较是不有多份祖先类,如果有多份则只保留一份。
这样的钻石继承在访问祖先成员时就不会有冲突。
六、虚函数、覆盖
1、虚函数
当成员函数前加virtual关键后,这样的函数就被称为虚函数,该类就会像虚继承一样多了一个虚指针。
2、虚函数表
虚指针中记录的是一个表格的首地址,而该表格中记录的类中所有虚函数的地址。
((void(*)(void))** (int**)p)(); 调用的就是虚函数表中的第一个虚函数。
3、覆盖(重写)
如果子类中有与父类虚同名的成员函数,编译器会比较这两个同函数的格式,如果相同就把子类中同名的地址覆盖虚函数表中的记录(这种情况就叫函数覆盖或重写),如果不同则构成隐藏。
4、构成函数覆盖的条件
1、在父子之间
2、父类中的函数为虚函数
3、函数名、参数列表、常属性必须相同
4、返回值类型相同,或者子类函数的返回值可以向父类函数的返回作隐式转换且有继承关系。Test* func()
常考面试题:函数重载、覆盖、隐藏、重写的区别。
- 函数重载发生在相同作用域
- 函数隐藏发生在不同作用域
- 函数覆盖就是函数重写。准确地叫做虚函数覆盖和虚函数重写,也是函数隐藏的特例
重载:同一作用域下的同名函数,参数列表不同(类型、个数、顺序、常函数等),构成重载关系。
覆盖:符合一系列条件。
隐藏:父子类之间的同名成员如果没有形成覆盖,且能通过编译,必定构成隐藏。
七、多态
什么是多态:
指的是指令的多种形态,当调用一个指令时,它能根据参数、环境的不同作出相应的操作,这种情况就叫作多态。
根据确定执行操作的时间多态分为:编译时多态、运行时多态。
编译时多态:
当调用重载函数,编译器根据参数的类型,在编译时就能确定执行哪个版本的重载函数,这就是编译时多态,还有模板技术等。运算符重载
运行时多态:
当子类覆盖了父类中的同名函数,然后父类指针或引用访问虚函数时,它既可能调用父类中的函数也可能调用子类中的函数,个体调用哪个版本是根据指针和引用目录决定的,而这需要在运行期间才能确定,因此这种作运行时多态。
构成运行时多态的条件:
1、父子类之间有覆盖关系。
2、子类是以public方式继承。
3、通过父类指针或引用访问虚函数。
八、虚构造和虚析构
虚构造:
构造函数不能是虚函数,假如构造函数定义为虚函数子类的构造函数就会自动覆盖父类的构造,当创建子类对象时,子类构造执行前会先调用父类构造,而父类构造已经被覆盖此时会调用子类的构造,这样就形成死循环,因为编译器禁止把构造函数定义为虚函数。
虚析构:
析构函数可以定义为虚函数,当使用类多态时,通过父类指针或引用释放子类对象时,默认情况不会调用子类析构。
只有把父类的析构函数定义为虚函数(子类析构会自动覆盖它),当通过父类指针或引用释放子类对象时,会先调用子类的析构(多态),子类析构执行完成后会自动调用父类析构(继承的规则)。
注意:当使用多态时且子类的析构函数中需要释放的资源,则父类的析构函数一定要设置为虚析构。
九、纯虚函数和纯抽象类
纯虚函数的格式:
virtual 返回值 函数名(参数列表) = 0;
1、纯虚函数可以不实现(一般人都不会去实现)。
2、有纯虚函数的类不能创建对象。
3、父类中如果有纯虚函数子类必须覆盖,否则也无法创建对象。
4、纯虚函数是强制子类实现某功能的方法。
5、有纯虚函数的类叫抽象类。
6、析构函数不能定义为纯虚函数。
纯抽象类:
所有的成员函数都是纯虚函数,这种类叫纯抽象类,这种类一般用于设置功能接口,也叫作接口类型。
作业: 了解一下什么是工厂模式,并实现一个简单的工厂模式。
#include <iostream>
using namespace std;
enum ClassType
{
ClassA,
ClassB,
ClassC,
ClassD
};
class Base
{
public:
virtual void whoami(void)
{
cout << "我是类Base的功能函数" << endl;
}
};
class A:public Base
{
public:
void whoami(void)
{
cout << "我是类A的功能函数" << endl;
}
};
class B:public Base
{
public:
void whoami(void)
{
cout << "我是类B的功能函数" << endl;
}
};
class C:public Base
{
public:
void whoami(void)
{
cout << "我是类C的功能函数" << endl;
}
};
class D:public Base
{
public:
void whoami(void)
{
cout << "我是类D的功能函数" << endl;
}
};
Base* create_class(const ClassType& type)
{
switch(type)
{
case ClassA: return new A;
case ClassB: return new B;
case ClassC: return new C;
case ClassD: return new D;
default: return new Base;
}
};
int main(int argc,const char* argv[])
{
Base* base = create_class(ClassB);
base->whoami();
}
生产者消费者模型
速度不匹配