1.多态
C++是一门面向对象的编程语言,体现在: 封装,继承,多态多态: 指同一种类型的指针/引用 ,调用相同的函数 表现出来不同的形态
1.1多态的特性
(1)通过指向子类对象的基类指针/引用 只能调用基类的成员函数(2)通过指向子类对象的基类指针/引用 调用虚函数,那么实际上调用的是子类中的版本
(3)虚函数和虚函数覆盖
虚函数: 使用virtual 关键字修饰的成员函数
虚函数覆盖: 在子类中定义一个与基类中具有相同函数原型的成员函数,即构成虚函数覆盖,
该虚函数的覆盖版本也是虚函数(虽然没有使用virtual修饰)
1.2 虚析构
(1)虚析构函数实际上就是为了保证delete一个指向子对象的基类指针时,实际被调用的就是子类中的析构函数,该析构函数会自动调用基类中的析构函数
(2)一般而言,如果一个类中存在虚函数,那么该类就应该提供一个虚析构函数
1.3.多态的底层实现
如: 虚函数表可以看成是一个函数指针数组;
int arr[5]; arr的类型是int*
int* arr[5]; arr的类型是int**void (*pFun)(void) ---->函数指针,指针名为pFun
pFun p[2] = ()&a; ---->pp的类型是pFun*
pFun* pp[2] = ---->pp的类型是pFun**
typedef void (*pFun)(void);//定义函数指针,其别名为pFun
typedef pFun* ppFun;//虚函数中元素地址的数据类型
A a;ppFun pp = *(ppFun*)&a;//&a是A*类型的,所以做类型转换
class A
{public:
virtual void foo(void){.....}
};
class B : public A
{
void foo(void){......}
};
A* pa = new B;
pa->foo();
当编译器编译上述代码时,不知道pa所指向对象的真实身份,只有在运行时才会知道,
于是编译器所能做的就是用一段代码去替换上面的函数调用语句,这段代码依次指向下面的操作:
(1)首先弄清指针pa所指向对象的真实身份
(2)然后通过这个对象的虚函数表指针去访问其虚函数表,在虚函数表中找到与foo函数标识符对应的虚函数入口地址
(3)根据虚函数入口地址,调用该函数
1.4多态的必要条件:
(1)首先要有虚函数(2)通过指针或引用 调用虚函数
1.5扩展笔试题:
笔试题:
class A
{
public:
void foo(void)
{bar();//this = &b; this->bar();//产生了多态,调用了B类中的bar函数
}
virtual void bar(void){}
};
class B : public A
{
public:
void bar(void){}
};
int main(void)
{
B b;
b.foo();//请问foo 函数中调用的哪个bar函数???
}
(2)构造函数、析构函数、运算符函数、静态成员函数,哪些不可以是虚函数?为什么?
构造函数不可以是虚函数,因为虚函数表是依赖对象的,构造函数是创建对象之前工作的
静态成员函数不可以是虚函数,因为它属于整个类
1.6 纯虚函数、抽象类、纯抽象类
(1)纯虚函数形如: virtual 返回值类型 函数名(形参表) = 0;
(2)抽象类
至少包含一个纯虚函数的类,抽象类不能实例化对象
(3)纯抽象类
完全由纯虚函数组成的抽象类
注意:
纯虚函数在基类中是没有定义的,要求在子类中加以实现,如果一个抽象类的子类没有对基类中的全部虚函数提供覆盖版本,那么该子类也是抽象类;