虚函数
类的某些函数,基类希望它的派生类各自定义这些函数各自的版本。
除了构造函数,任何函数都可以是虚函数。
让派生类继承借口和缺省定义(默认定义)。
定义&声明
class A
{
virtual int get(){};
};
class B:public A
{
int get() override
}
如果是普通的虚函数(不是纯虚函数),那么基类也是需要提供虚函数的定义的。
因为不是纯虚函数,那么该类就可以实例化,那就必须需要定义。
派生类必须在其内部重新定义的虚函数进行声明。
在基类中定义虚函数的时候需要在最开头加virtual
。
虚函数可以有默认实参,且派生类也可以定义不同的默认实参,但是最好相同。
重写
在子类函数重写的时候,可以选择加也可以选择不加virtual
。同时,如果可以在最结尾的时候加override
确认是重写。
在派生类中覆盖了某个虚函数时,可以再一次使用virtual
指出该函数的性质,也可以不加。但一个函数被声明为虚函数时,在他的所有派生类中,都是虚函数。
重写时,虚函数的返回值,形参必须完全相同。
但是,当基类返回的是自己类型的指针时。那么气派生类可以修改虚函数的返回值,但必须是返回派生类自身类型的指针。
另外,在分离定义的时候,不能加virtual
.
关键字
override
使用override
关键字,用来确认定义的函数确实是覆盖了基类的虚函数,如果没有覆盖,那么编译时候会报错。
使用
类的函数成员分类
- 普通函数成员:基类希望其派生类直接继承,而不要改变的函数
- 虚函数:基类希望其派生类进行覆盖的函数。
普通虚函数,可以派生类可以不用重写。
纯虚函数,必须重写。
普通成员函数,其解析过程发生在编译时。
虚函数的解析可能在编译时,也可能是在运行时。
当通过指针或引用调用对象的虚函数时。那么函数会在运行时才会被解析。
这里解析的含义是,当对象调用自己的成员函数的时候,是执行自己的成员函数,还是执行其子类的成员函数。
当将一个派生类对象的指针或引用赋值给基类对象指针时(此时会发生派生类向基类的转化),在运行时解析。执行的是派生类的成员函数。
如果我们想回避虚函数的动态绑定机制,而直接运行派生类基类的成员函数。(调用的函数仍然是一个虚函数,只是我们希望调用基类的)。
pa->A::set();
显式的指定。那么函数将在编译时完成解析。
抽象基类
接口类
纯虚函数
纯虚函数的目的是为了让派生类只继承借口,不继承定义。所以必须要定义。
定义
class A
{
virtual int get()=0;
};
=0
只能在类内出现。
同时仍然可以在类外提供这个函数的定义。唯一的调用方式是显式调用。
出纯虚函数可以不提供定义,因为这个类不能够实例化。但是可以定义指向这个类的指针。
同时这个类里面还是可以出现正常的虚函数,成员变量。
构造函数
派生类构造函数只初始化他的直接基类。也就是说派生类在构造函数中只能调用直接基类的构造函数,不能使用其间接基类的构造函数。
class C:public B
{
public:
C():B(),A()
{
};
}
这里报错
type 'A' is not a direct base of 'C'
C():B(),A(){};
虚析构函数
任何函数,只要带有一个虚函数,那么他的析构函数必将是虚函数。
如果一个类的析构函数不是虚函数,那么不能将派生类向基类转化
如果say()
函数不是虚函数
//忽略大部分
A *pa;
B *pb;
pa=pb;
pa->say();
执行的是类A的成员函数。
如果say()
函数是虚函数,那么执行的是 类B的函数成员。
所以,如果基类的析构函数不是虚函数,那么我们使用上述的转化的时候,析构的将是类A的析构函数。
那么其派生类部分的成员将无法析构。
所以,析构函数必须是虚函数。
析构时,从最外层向基层依次执行析构函数.
构造函数和析构函数中不能使用虚函数
构造函数中,构造不完全,调用的是基类的函数.
同样析构时也一样。
对象构造期间,虚函数不是虚函数。所以调用不会出错,只是不知道会发生什么。
也就是说,派生类构造时,如果正处于基类构造函数执行期间,那么类型是基类类型,也就是说谁的构造函数在执行,那类型就是什么。
因为,如果将基类函数定义为虚函数。那么,无法初始化。编译会报错。
网友评论