这里写自定义目录标题
重载、重写(覆盖)、隐藏
重载:在同一作用域,函数名相同,参数不同,返回值可以不同。
重写(覆盖):函数名相同,参数相同,返回值相同(协变除外);基类函数必须有virtual关键字;访问修饰符可以不同。
重定义(隐藏):在不同的作用域中(分别在基类和派生类);函数名相同;在基类和派生类中只要不构成重写就是重定义。
虚函数
虚函数的定义是:父类别的指针指向子类别的成员,然后用该指针调用子类成员的成员函数
纯虚函数:在成员函数的形参列表后面写上"=0",则成员函数为纯虚函数,包含纯虚函数的类叫做抽象类,也叫接口类,抽象类不能实例化出对象,纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
C++中的虚函数的作用主要是实现了多态机制,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。多态机制可以简单地概括为“一个接口,多种方法”。
父类指针指向子类对象,究竟指向子类对象的哪里?
指向父类空间开始的地方
父类指针指向子类对象,一定要确保父类的虚构函数是虚函数。
啥是多态?
多态就是多种形态,同一个行为,有不同的对象去完成则会产生不同的结果
父类指针指向子类对象,调用子类方法
多态分为静态多态与动态多态。静态多态包括函数重载,泛型编程<模板函数>。动态是虚函数的使用。
静态多态(又被称为早绑定)是指编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用的那个函数,如果有对应的函数就调用该函数,否则会出现编译错误。
动态多态,运行时期多态————晚绑定。代表是继承中的多态。
动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。动态绑定的条件是基类中的函数必须是虚函数,且派生类一定要重写基类的虚函数;第二点是通过基类类型的引用或者指针调用虚函数。
动多态的产生条件
- 必须有指针或者引用指向实例对象+必须是一个完整的对象(完整对象:构造函数已经完成,析构函数还没开始)
- 派生类的继承方式必须是公有public继承
- 基类中的同名函数必须定义为虚函数
动多态的过程
使用引用或者指针调用虚函数
在对象中找到vfptr
找到vftable
在表中找到对应的函数
vftable在什么时候产生?在哪里存储?
在编译期产生,放在rodata段
构造函数能不能写成虚函数
构造函数不能写成虚函数,
- 构造函数无法通过构造或者指针去调用,所以写成虚函数没有意义
- vfptr是在构造的时候写入对象 ,而动态调用虚函数需要用到vfptr.
静态函数能不能写成虚函数
只有类的非静态成员函数才能定义是虚函数,静态成员函数不能定义为虚函数。
静态函数不依赖于对象,不能产生动多态。
析构函数能不能写成虚函数
不要再构造函数和析构中调用虚函数,在析构函数和构造函数中对象是不完整的,可能会出现未定义的行为。
析构函数可以写成虚函数
虚函数能不能被处理成内联函数
不能,虚函数需要将函数指针放到vftable,而内联函数在编译期会展开,在release版本中没有地址。
什么情况下析构函数必须写成虚函数
当存在父类指针指向堆上的子类对象的时候就必须把父类的析构函数写成虚函数。
父类指针能不能指向子类对象?子类指针能不能指向父类对象?
父类指针可以指向子类对象,因为子类对象中含有父类空间,但是子类指针不能指向父类对象。
父子类/组合类的构造顺序
类的编译顺序
- 编译类名
- 编译成员名
- 最后编译成员函数体
什么是RTTI,RTTI什么时候产生?RTTI信息储存在哪里?
运行时的类型信息,是一个指向类型信息的指针。
编译时期产生,RTTI指针放在vftable中,类型信息保存在rodata段。
RTTI的实际作用:dynamic_cast 父类指针转为子类指针专用的类型强转(必须要有RTTI,且父类指针指向的对象中的RTTI确实是子类对象)
父类指针如何转换为子类指针
只要判断出父类指针指向的的确是一个子类对象,使用“dynamic_cast§”强转
class Base
{
Base()
{
cout<<"Base()"<<endl;
}
void fun(int a)
{
cout << "person fun1" << endl;
}
~Base()
{
cout<<"Base()"<<endl;
}
};
class Derive
{
Derive()
{
cout<<"Derive()"<<endl;
}
void fun(int a)
{
cout << "Derive() fun1" << endl;
}
~Derive()
{
cout<<"Derive()"<<endl;
}
};