目录
1.虚函数
2. 设置为虚函数
3.多态
4.多态类型的调用
5.抽象类和纯虚函数
6.虚表
7.练习题
1.虚函数
虚函数是被virtual修饰的类成员函数
virtual关键字只在声明时加上,在类外实现时不能加。
static和virtual不能同时使用。
2. 设置为虚函数
首先,虚函数必须是类成员函数。
成员函数 VS 非成员函数
成员函数简单来说就是类中定义的函数,作为一个类的成员。
而非成员函数就是不在类中定义的函数,其中友元函数就属于非成员函数。(友元函数可以 直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类)
1.因此友元函数不能设置为虚函数。
2.静态成员函数不能设置为虚函数。
原因一:static和virtual不能同时使用
原因二:静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名::成员函数名 直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数
3.父类的析构函数建议设置为虚函数
这样动态释放父类指针所指的子类对象时,能够达到析构的多态
3.多态
1. 多态分为编译时多态和运行时多态,也叫早期绑定和晚期绑定,还被叫做静态多态和动态多态。
2.编译时多态通过函数重载和模板来实现的。
3.运行时多态,还被叫做覆盖,重写,基于虚函数机制实现多态功能
4.多态类型的调用
判断正误:
在编译期间,通过传递不同类的对象,编译器选择调用不同类的虚函数 (x)
解释:不是在编译期,而应该在运行期间,编译期间,编译器主要检测代码是否违反语法规则,此时无法知道基类的指针或者引用到底引用那个类的对象,也就无法知道调用那个类的虚函数。在程序运行时,才知道具体指向那个类的对象,然后通过虚表调用对应的虚函数,从而实现多态。
5.抽象类和纯虚函数
纯虚函数可以有函数体(函数实现),但是意义不大,因为抽象类没办法实例化对象。
抽象类可以定义为指针,而且经常这样做,其目的就是用父类指针指向子类从而实现多态
6.虚表
多继承的时候,可能会有多张虚表
虚表时在编译期间生成的。
当定义多个对象时,同一个类的虚表是共享的。
当子类的虚函数重写但是为私有(private)时,若构成多态,也能访问的了,因为多态仅仅是用子类虚函数的地址覆盖虚表,实际要调用的时候直接去虚表里找地址就行了,不受private的限制。
7.练习题
//以下程序输出结果是( )
class A
{
public:
A() :m_iVal(0) { test(); }
virtual void func() { std::cout << m_iVal <<endl; }
void test() { func(); }
public:
int m_iVal;
};
class B : public A
{
public:
B() { test(); }
virtual void func()
{
++m_iVal;
std::cout << m_iVal <<" ";
}
};
int main()
{
A* p = new B;
p->test();
return 0;
}
A.1 0
B.0 1
C.0 1 2
D.2 1 0
E.不可预期
F. 以上都不对
分析:new B时先调用父类A的构造函数,执行test()函数,在调用func()函数,由于此时还处于构造B对象阶段,多态机制还没有生效,所以,此时执行的func函数为父类的func函数,打印0,构造完父类后执行子类构造函数,又调用test函数,然后又执行func(),由于父类已经构造完毕,虚表已经生成,func满足多态的条件,所以调用子类的func函数,对成员m_iVal加1,进行打印,所以打印1, 最终通过父类指针p->test(),也是执行子类的func,所以会增加m_iVal的值,最终打印2, 所以答案为C 0 1 2
变式:将A*p=new B改为B*p=new B,结果又如何?
前面还是一样,先打印0,再打印1,然后通过子类指针p->test() ,但是test()是父类作用域里的,只是子类继承下来的,实际上test()函数里还隐藏一个t父类的this指针,再由这个this指针去调用func,而func是虚函数,并且子类也进行了重写,而且还是父类的this指针调用,满足多态,所以还是执行子类的func,增加m_iVal的值,最终打印2, 所以答案还是C 0 1 2