多态的定义和实现
构成多态的条件
- 必须满足继承条件
- 调用的相同函数必须是虚函数
- 该虚函数必须完成重写
- 调用时应该用基类的指针或引用来接收基类或派生类的指针或引用;再根据指针具体指向谁去调用那个类的函数
下面我们就通过题目来具体感悟一下多态
关于虚函数说法正确的是( B)
A.被virtual修饰的函数称为虚函数
B.虚函数的作用是用来实现多态
C.虚函数在类中声明和类外定义时候,都必须加虚拟关键字
D.静态虚成员函数没有this指针
- a:被virtual修饰的成员函数称为虚函数
- c:虚函数在类中声明加在类外实现时不能加
- d:静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名::成员函数名 直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数
以下程序输出结果是什么(B)
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确class A { public: virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;} virtual void test(){ func();} }; class B : public A { public: void func(int val = 0){ std::cout<<"B->"<< val <<std::endl; } }; int main(int argc ,char* argv[]) { B*p = new B; p->test(); return 0; }
``
下面我们来分析一下
首先我们第一次调用的是类B中继承下来的test(继承并不是在子类完全拷贝该函数而是限制了调用函数现在哪一个类中查找)所以func里面放的是类A的指针,接着进行func调用,此时我们因为基类指针可以接受子类指针此时我们传入的指针时类B的指针,又因为func里面的参数是类A的指针此时构成多态了,我们的p是类B的指针因此去调用类B的func但是虚函数得重写不会对不会改变函数头部只是改变函数体内的实现因此此时的val默认是0
我们调用test的时候传入的是类b的指针而我们调用的是类a的test,因此类a的指针来接收我们的类b的指针,因为类b没有重写test所以调的是a的test,接着调a的func因为此时我们传入的b指针。func完成重写了会执行类b的func函数。但是重写不会影响函数头部。
关于重载、重写和重定义的区别
- 重写即覆盖,针对多态, 重定义即隐藏, 两者都发生在继承体系中
- 重载只能在一个范围内,不能在不同的类里
- 只有重写要求原型相同
- 重写和重定义是两码事,重写即覆盖,针对多态, 重定义即隐藏
- 重写和重定义是两码事,重写即覆盖,针对多态, 重定义即隐藏
- 重写要求函数完全相同,重定义只需函数名相同即可
协变
派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。
override 和 final关键字
override,可以帮助⽤⼾检测是否重写。如果我们不想让派⽣类重写这个虚函数,那么可以⽤final去修饰。
例子:
virtual void Drive() override { cout << "Benz-舒适" << endl; }
//检查该虚函数是否被重写
virtual void Drive() final {}
//代表该函数不允许被重写final关键字如果加在类后面代表该类无法被继承
纯虚函数和抽象类
在虚函数的后⾯写上 =0 ,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写,但是语法上可以实现)但不是不可实现,只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了派⽣类重写虚函数,因为不重写实例化不出对象。
多态原理
- 对于一个有虚函数的类里面都有一个叫做一个指针指向一个虚函数表
- 虚函数表实际上就是一个虚函数指针数组里面存放的是虚函数的地址
- 对于派生类来说会去继承基类的虚表但继承下来的是各自独立的也就是基类有一个虚表,派生类也有一个虚表;如果实现了对虚函数的重写,重写的虚函数会覆盖掉基类被重写的虚函数指针,再添加自己本身的虚函数指针
虚函数表的存放以及虚函数存放
虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函
数的地址⼜存到了虚表中。
虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码可以
对⽐验证⼀下。vs下是存在代码段(常量区)下面我们用代码来看一下
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
};
class Derive : public Base
{
public:
// 重写基类的func1
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func1" << endl; }
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
int main()
{
int i = 0;
static int j = 1;
int* p1 = new int;
const char* p2 = "xxxxxxxx";
printf("栈:%p\n", &i);
printf("静态区:%p\n", &j);
printf("堆:%p\n", p1);
printf("常量区:%p\n", p2);
Base b;
Derive d;
Base* p3 = &b;
Derive* p4 = &d;
//在32位去前四个字节就是虚表指针
printf("Person虚表地址:%p\n", *(int*)p3);
printf("Student虚表地址:%p\n", *(int*)p4);
printf("虚函数地址:%p\n", & Base::func1);
printf("普通函数地址:%p\n", &Base::func5);
return 0;
}
vs运行如下:
Linux运行如下:
可以看到虚表和虚函数都在我们的常量区(代码段)