概念:通俗的来讲多态就是多种形态,具体就是去完成某个行为,不同的对象去完成时会产生出不同的状况。
例如:当同样去买票时,学生买的是学生票,而普通人则只能买普通票。
多态的构成条件
- 必须通过基类的指针或引用调用虚函数。
- 被调用的函数必须是虚函数,而且派生类必须对基类的虚函数进行重写。
class Person { public: virtual void BuyTicket() { cout << "Person::全价" << endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout << "Student::半价" << endl; } };
虚函数
虚函数:被virtual修饰的成员函数称为虚函数。
注意:这里的virtual与虚拟继承的virtual没有关系,仅仅是关键字相同。
public: virtual void BuyTicket() { cout << "Person::全价" << endl; } };
虚函数的重写
虚函数的重写又称为覆盖,即派生类中有一个与基类完全相同的虚函数(完全相同是指返回值类型相同,函数名相同,函数参数列表相同。)
class Person { public: virtual void BuyTicket() { cout << "Person::全价" << endl; } }; /*在重写基类虚函数时,virtual也可以不用写,但一般不建议这样*/ class Student : public Person { public: virtual void BuyTicket() { cout << "Student::半价" << endl; } }; void test(Person& p) { p.BuyTicket(); } int main() { Person per; Student stu; test(per); test(stu); }
从上面可以看出在调用虚函数时,与指针或引用的类型无关,与指针或引用对象的类型有关。
在test函数中,参数的类型为什么是Person而不是Student类型呢?这就与继承有关了,因为子类的对象可以赋值给基类的对象、指针、引用。
虚函数重写的例外
协变(基类与派生类虚函数的返回值不同)
基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用,这种情况称为协变。
class A {}; class B : public A {}; class Person { public: virtual A* BuyTicket() { cout << "Person::全价" << endl; return nullptr; } }; class Student : public Person { public: virtual B* BuyTicket() { cout << "Student::半价" << endl; return nullptr; } };
析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,那么此时的派生类的析构函数无论是否有virtual关键字,都与基类的析构函数构成重写。从表面上看析构函数的名字不同,但是编译器会将析构函数统一处理为destructor。
//构成重写 class Person { public: virtual ~Person() { cout << "~Person()" << endl; }; }; class Student : public Person { public: virtual ~Student() { cout << "~Student()" << endl; } };
重载、重写、重定义的对比
- 重载
- 两个函数在统一作用域。
- 函数名、参数相同。
- 重写(覆盖)
- 两个函数分别在基类和派生类的作用域。
- 函数名、参数、返回值必须相同(协变例外)。
- 两个函数必须是虚函数。
- 重定义(隐藏)
- 两个函数分别在基类和派生类的作用域。
- 函数名相同。
- 两个基类和派生类的同名函数不构成重写就是重定义。
c++中的final和override
final:修饰虚函数,表示该虚函数不能再被重写。
class Person { public: //此时不能被重写 virtual void BuyTicket() final { cout << "Person::全价" << endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout << "Student::半价" << endl; } };
override:检查派生类虚函数是否重写了基类的某个虚函数,如果没有重写编译报错。
抽象类
纯虚函数:虚函数后面写上 =0 。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,因此纯虚函数规范了派生类必须重写。
class Car { public: virtual void Drive() = 0;//纯虚函数 }; class Benz : public Car { public: virtual void Drive() { cout << "Benz" << endl; } }; class BMW : public Car { public: virtual void Drive() { cout << "BMW" << endl; } }; void Test() { Car* pBenz = new Benz; pBenz->Drive(); Car* pBMW = new BMW; pBMW->Drive(); Car pBenz;//实例化不出来 }
接口继承和实现继承
接口继承:虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态。
实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数。
综上,如果不实现多态,就不要将函数定义为虚函数。
虚函数表
//在X86程序下 class Base { public: virtual void Fun() { cout << "Fun()" << endl; } private: int _b = 1; }; int main() { cout << sizeof(Base) << endl; }
在上述程序中发现Base是8byte。那为什么不是4byte呢?因为在Base类中除了有
_b
成员,还会有一个_vfptr
在对象的前面,这个_vfptr
称为虚函数表指针。一个含有虚函数的类中都至少有一个虚函数表指针,因为类中的虚函数地址都要放到虚函数表中。那么在派生类中的虚函数表又是什么样子呢?
class Base { public: virtual void Fun1() { cout << "Base::Fun1()" << endl; } virtual void Fun2() { cout << "Base::Fun2()" << endl; } virtual void Fun3() { cout << "Base::Fun3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Fun1() { cout << "Derive::Fun1()" << endl; } private: int _d = 2; }; int main() { Base b; Derive d; cout << sizeof(b) << endl; cout << sizeof(d) << endl; }
通过调试可以看出:
- 派生类的虚表继承自基类,当发生重写时基类的虚表和派生类的虚表是不一样的,
d
的虚表存的是重写的Fun1()的地址。- Fun3()也继承下来了,但由于它不是虚函数所以不会被放到虚函数表中。
- 虚函数表本质是一个存虚函数指针的指针数组,一般情况下数组的最后面放了一个
nullptr
。- 派生类虚表生成过程:
- 先将基类中的虚表内容拷贝一份到派生类的虚表中。
- 如果派生类重写了基类中的某个虚函数,则用派生类自己的虚函数覆盖虚表中基类的虚函数
- 派生类自己新增的虚函数按其在派生类中的声明次序增加到派生类虚表的后面。
动态绑定与静态绑定
标签:函数,派生类,多态,virtual,基类,重写,public From: https://www.cnblogs.com/zhiheng-/p/18224440静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称静态多态。
动态绑定又称后期绑定(晚绑定),在程序运行期间,根据具体的类型确定程序的举起行为,调用具体的函数,也称为动态多态。
只有虚函数有动态绑定,其他的都是静态绑定。
class Base { public: virtual void Fun1() { cout << "Base::Fun1()" << endl; } void Fun3() { cout << "Base::Fun3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Fun1() { cout << "Derive::Fun1()" << endl; } void Fun3() { cout << "Derive::Fun3()" << endl; } private: int _d = 2; }; int main() { Base b; Derive d; //静态绑定,发生在编译期 Base* ptr = &b; ptr->Fun3(); ptr = &d; ptr->Fun3(); //动态绑定,发生在运行期 ptr = &b; ptr->Fun1(); ptr = &d; ptr->Fun1(); }